diff --git a/.bash_aliases b/.bash_aliases
index 30e12da..173dc4e 100644
--- a/.bash_aliases
+++ b/.bash_aliases
@@ -12,6 +12,7 @@ alias ex2s="cd ${GITPOD_REPO_ROOT}/exercises/querying-workflows/solution"
alias ex2w="mvn exec:java -Dexec.mainClass='queryingworkflows.QueryingWorkflowsWorker'"
alias ex2st="mvn exec:java -Dexec.mainClass='queryingworkflows.Starter'"
alias ex2sg="mvn exec:java -Dexec.mainClass='queryingworkflows.SignalClient'"
+alias ex2q="mvn exec:java -Dexec.mainClass='queryingworkflows.QueryClient'"
alias ex3="cd ${GITPOD_REPO_ROOT}/exercises/async-activity-completion/practice"
diff --git a/exercises/sending-signals-client/README.md b/exercises/sending-signals-client/README.md
new file mode 100644
index 0000000..9b0dc87
--- /dev/null
+++ b/exercises/sending-signals-client/README.md
@@ -0,0 +1,66 @@
+## Exercise #2: Sending a Signal from the Client
+
+This exercise shows how to send a Signal from just the Client and not from another Workflow.
+
+During this exercise, you will:
+
+- Retrieve a stub on a running Workflow to Signal
+- Send a Signal from the Client
+
+In this part of the exercise, we have removed the `FulfillOrderWorkflow` Workflow from the previous exercise, since this exercise is just for sending a Signal from a Client, and not from another Workflow.
+
+We will skip the defining and handling of `FulfillOrderSignal` that were handled in `src/main/java/sendingsignalsclient/orderpizza`. This exercise will solely focus on the Client file.
+
+### GitPod Environment Shortcuts
+
+If you are executing the exercises in the provided GitPod environment, you
+can take advantage of certain aliases to aid in navigation and execution of
+the code.
+
+| Command | Action |
+| :------ | :--------------------------------------------------------------------------------------------------------------------------------- |
+| `ex2` | Change to Exercise 1 Practice Directory |
+| `ex2s` | Change to Exercise 1 Solution Directory |
+| `ex2w` | Execute the Exercise 1 Worker. Must be within the appropriate directory for this to succeed. (either `practice` or `solution`) |
+| `ex2st` | Execute the Exercise 1 Starter. Must be within the appropriate directory for this to succeed. (either `practice` or `solution`) |
+| `ex2sg` | Send the Exercise 1 Signal. Must be within the appropriate directory for this to succeed. (either `practice` or `solution`) |
+
+
+## Part A: Create a Stub on the Pizza Workflow
+
+In this part of the exercise, you will create a stub on the Workflow that you wish to Signal, which is `PizzaWorkflow`.
+
+1. Recall that the Workflow ID for this Workflow is `pizza-workflow-order-XD001`.
+1. Edit the `SignalClient.java` in the `src/main/java/sendingsignalsclient` subdirectory and modify the `newWorkflowStub()` method to get a stub of a running workflow.
+ 1. Change the Workflow ID from `CHANGE_ME` to the Workflow ID of the running Workflow.
+1. Save the file.
+
+## Part B: Sending a Signal to the Pizza Workflow
+
+Now that you have a stub on the Workflow you wish to Signal (`PizzaWorkflow`), we will now send `fulfillOrderSignal`.
+
+1. Continue editing the `SignalClient.java` in the `src/main/java/sendingsignalsclient` subdirectory.
+1. Using the stub defined above, send the signal by calling the `fulfillOrderSignal()` method, passing `true` as a parameter.
+1. Save the file.
+
+
+## Part C: Run the Workflow
+
+To run the Workflow:
+
+1. In all terminals, change directories to the Exercise 2 directory.
+ 1. If you're in the GitPod environment, you can run `ex2`
+1. In one terminal, start the Worker by running `mvn exec:java -Dexec.mainClass="sendingsignalsclient.SendSignalClientWorker"`.
+ 1. If you're in the GitPod environment, you can instead run `ex2w` to start the worker
+1. In another terminal, start the Workflow by running `mvn exec:java -Dexec.mainClass="sendingsignalsclient.Starter"`. This will start the Workflow, which you will see waiting for a Signal.
+ 1. If you're in the GitPod environment, you can instead run `ex2st` to start the Workflow
+1. In another terminal, send the signal by running `mvn exec:java -Dexec.mainClass="sendingsignalsclient.SignalClient"`
+ 1. If you're in the GitPod environment, you can instead run `ex2sg` to send the Signal
+1. You should see the following output in the terminal where you ran the Starter:
+
+```bash
+09:41:34.326 INFO - Created WorkflowServiceStubs for channel: ManagedChannelOrphanWrapper{delegate=ManagedChannelImpl{logId=1, target=127.0.0.1:7233}}
+Workflow result: OrderConfirmation{orderNumber='XD001', status='SUCCESS', confirmationNumber='P24601', billingTimestamp=1712500897, amount=3500}
+```
+
+### This is the end of the exercise.
diff --git a/exercises/sending-signals-client/practice/pom.xml b/exercises/sending-signals-client/practice/pom.xml
new file mode 100644
index 0000000..329ac36
--- /dev/null
+++ b/exercises/sending-signals-client/practice/pom.xml
@@ -0,0 +1,109 @@
+
+
+
+ 4.0.0
+
+ io.temporal.learn
+ sending-signals-client
+ 1.0.0-SNAPSHOT
+
+ sending signals client
+ https://learn.temporal.io/
+
+
+ UTF-8
+ 1.8
+ 1.8
+
+
+
+
+
+ io.temporal
+ temporal-sdk
+ 1.20.1
+
+
+
+ ch.qos.logback
+ logback-classic
+ 1.4.8
+
+
+
+ org.apache.commons
+ commons-lang3
+ 3.11
+
+
+
+ io.temporal
+ temporal-testing
+ 1.20.1
+ test
+
+
+
+
+ org.junit.jupiter
+ junit-jupiter
+ 5.5.2
+ test
+
+
+
+ org.mockito
+ mockito-junit-jupiter
+ 5.3.1
+ test
+
+
+
+
+
+
+
+
+
+ maven-clean-plugin
+ 3.1.0
+
+
+
+ maven-resources-plugin
+ 3.0.2
+
+
+ maven-compiler-plugin
+ 3.8.0
+
+
+ maven-surefire-plugin
+ 2.22.1
+
+
+ maven-jar-plugin
+ 3.0.2
+
+
+ maven-install-plugin
+ 2.5.2
+
+
+ maven-deploy-plugin
+ 2.8.2
+
+
+
+ maven-site-plugin
+ 3.7.1
+
+
+ maven-project-info-reports-plugin
+ 3.0.0
+
+
+
+
+
diff --git a/exercises/sending-signals-client/practice/src/main/java/sendingsignalsclient/Constants.java b/exercises/sending-signals-client/practice/src/main/java/sendingsignalsclient/Constants.java
new file mode 100644
index 0000000..9ef9bd1
--- /dev/null
+++ b/exercises/sending-signals-client/practice/src/main/java/sendingsignalsclient/Constants.java
@@ -0,0 +1,7 @@
+package sendingsignalsclient;
+
+public class Constants {
+
+ public static final String TASK_QUEUE_NAME = "pizza-tasks";
+
+}
diff --git a/exercises/sending-signals-client/practice/src/main/java/sendingsignalsclient/SendSignalClientWorker.java b/exercises/sending-signals-client/practice/src/main/java/sendingsignalsclient/SendSignalClientWorker.java
new file mode 100644
index 0000000..ff66ba4
--- /dev/null
+++ b/exercises/sending-signals-client/practice/src/main/java/sendingsignalsclient/SendSignalClientWorker.java
@@ -0,0 +1,26 @@
+package sendingsignalsclient;
+
+import io.temporal.client.WorkflowClient;
+import io.temporal.serviceclient.WorkflowServiceStubs;
+import io.temporal.worker.Worker;
+import io.temporal.worker.WorkerFactory;
+
+import sendingsignalsclient.orderpizza.PizzaActivitiesImpl;
+import sendingsignalsclient.orderpizza.PizzaWorkflowImpl;
+
+public class SendSignalClientWorker {
+ public static void main(String[] args) {
+
+ WorkflowServiceStubs service = WorkflowServiceStubs.newLocalServiceStubs();
+ WorkflowClient client = WorkflowClient.newInstance(service);
+ WorkerFactory factory = WorkerFactory.newInstance(client);
+
+ Worker worker = factory.newWorker(Constants.TASK_QUEUE_NAME);
+
+ worker.registerWorkflowImplementationTypes(PizzaWorkflowImpl.class);
+
+ worker.registerActivitiesImplementations(new PizzaActivitiesImpl());
+
+ factory.start();
+ }
+}
diff --git a/exercises/sending-signals-client/practice/src/main/java/sendingsignalsclient/SignalClient.java b/exercises/sending-signals-client/practice/src/main/java/sendingsignalsclient/SignalClient.java
new file mode 100644
index 0000000..bc677a2
--- /dev/null
+++ b/exercises/sending-signals-client/practice/src/main/java/sendingsignalsclient/SignalClient.java
@@ -0,0 +1,22 @@
+package sendingsignalsclient;
+
+import io.temporal.client.WorkflowClient;
+import io.temporal.serviceclient.WorkflowServiceStubs;
+import sendingsignalsclient.orderpizza.PizzaWorkflow;
+
+
+public class SignalClient {
+ public static void main(String[] args) throws Exception {
+
+ WorkflowServiceStubs service = WorkflowServiceStubs.newLocalServiceStubs();
+
+ WorkflowClient client = WorkflowClient.newInstance(service);
+
+ // TODO: PART A: Set Workflow ID
+ PizzaWorkflow workflow = client.newWorkflowStub(PizzaWorkflow.class, "CHANGE_ME");
+
+ // TODO: PART A: Call fulfillOrderSignal, passing `true`
+
+ System.exit(0);
+ }
+}
\ No newline at end of file
diff --git a/exercises/sending-signals-client/practice/src/main/java/sendingsignalsclient/Starter.java b/exercises/sending-signals-client/practice/src/main/java/sendingsignalsclient/Starter.java
new file mode 100644
index 0000000..6b9836a
--- /dev/null
+++ b/exercises/sending-signals-client/practice/src/main/java/sendingsignalsclient/Starter.java
@@ -0,0 +1,58 @@
+package sendingsignalsclient;
+
+import io.temporal.client.WorkflowClient;
+import io.temporal.client.WorkflowOptions;
+import java.util.concurrent.CompletableFuture;
+import io.temporal.serviceclient.WorkflowServiceStubs;
+
+import sendingsignalsclient.model.PizzaOrder;
+import sendingsignalsclient.model.Pizza;
+import sendingsignalsclient.model.Customer;
+import sendingsignalsclient.model.OrderConfirmation;
+import sendingsignalsclient.model.Address;
+import sendingsignalsclient.orderpizza.PizzaWorkflow;
+
+import java.util.Arrays;
+import java.util.List;
+
+public class Starter {
+ public static void main(String[] args) throws Exception {
+
+ WorkflowServiceStubs service = WorkflowServiceStubs.newLocalServiceStubs();
+
+ WorkflowClient client = WorkflowClient.newInstance(service);
+
+ PizzaOrder order = createPizzaOrder();
+
+ // Setup the Pizza Order Workflow Client
+ String pizzaWorkflowID = String.format("pizza-workflow-order-%s", order.getOrderNumber());
+
+ WorkflowOptions pizzaWorkflowOptions = WorkflowOptions.newBuilder()
+ .setWorkflowId(pizzaWorkflowID)
+ .setTaskQueue(Constants.TASK_QUEUE_NAME)
+ .build();
+
+ PizzaWorkflow pizzaWorkflow = client.newWorkflowStub(PizzaWorkflow.class, pizzaWorkflowOptions);
+
+ OrderConfirmation orderConfirmation = pizzaWorkflow.orderPizza(order);
+ //CompletableFuture orderConfirmation = WorkflowClient.execute(pizzaWorkflow::orderPizza, order);
+
+
+ System.out.printf("Workflow result: %s\n", orderConfirmation);
+ System.exit(0);
+ }
+
+ private static PizzaOrder createPizzaOrder() {
+ Customer customer = new Customer(8675309, "Lisa Anderson", "lisa@example.com", "555-555-0000");
+ Address address = new Address("742 Evergreen Terrace", "Apartment 221B", "Albuquerque", "NM", "87101");
+ Pizza pizza1 = new Pizza("Large, with mushrooms and onions", 1500);
+ Pizza pizza2 = new Pizza("Small, with pepperoni", 1200);
+ Pizza pizza3 = new Pizza("Medium, with extra cheese", 1300);
+
+ List orderList = Arrays.asList(pizza1, pizza2, pizza3);
+
+ PizzaOrder order = new PizzaOrder("XD001", customer, orderList, true, address);
+
+ return order;
+ }
+}
diff --git a/exercises/sending-signals-client/practice/src/main/java/sendingsignalsclient/exceptions/InvalidChargeAmountException.java b/exercises/sending-signals-client/practice/src/main/java/sendingsignalsclient/exceptions/InvalidChargeAmountException.java
new file mode 100644
index 0000000..09e8ec0
--- /dev/null
+++ b/exercises/sending-signals-client/practice/src/main/java/sendingsignalsclient/exceptions/InvalidChargeAmountException.java
@@ -0,0 +1,20 @@
+package sendingsignalsclient.exceptions;
+
+public class InvalidChargeAmountException extends Exception {
+
+ public InvalidChargeAmountException() {
+ super();
+ }
+
+ public InvalidChargeAmountException(String message) {
+ super(message);
+ }
+
+ public InvalidChargeAmountException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public InvalidChargeAmountException(Throwable cause) {
+ super(cause);
+ }
+}
diff --git a/exercises/sending-signals-client/practice/src/main/java/sendingsignalsclient/exceptions/OutOfServiceAreaException.java b/exercises/sending-signals-client/practice/src/main/java/sendingsignalsclient/exceptions/OutOfServiceAreaException.java
new file mode 100644
index 0000000..9bb4dfa
--- /dev/null
+++ b/exercises/sending-signals-client/practice/src/main/java/sendingsignalsclient/exceptions/OutOfServiceAreaException.java
@@ -0,0 +1,20 @@
+package sendingsignalsclient.exceptions;
+
+public class OutOfServiceAreaException extends Exception {
+
+ public OutOfServiceAreaException() {
+ super();
+ }
+
+ public OutOfServiceAreaException(String message) {
+ super(message);
+ }
+
+ public OutOfServiceAreaException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public OutOfServiceAreaException(Throwable cause) {
+ super(cause);
+ }
+}
diff --git a/exercises/sending-signals-client/practice/src/main/java/sendingsignalsclient/model/Address.java b/exercises/sending-signals-client/practice/src/main/java/sendingsignalsclient/model/Address.java
new file mode 100644
index 0000000..c028ab3
--- /dev/null
+++ b/exercises/sending-signals-client/practice/src/main/java/sendingsignalsclient/model/Address.java
@@ -0,0 +1,65 @@
+package sendingsignalsclient.model;
+
+public class Address {
+
+ private String line1;
+ private String line2;
+ private String city;
+ private String state;
+ private String postalCode;
+
+ public Address() {
+ }
+
+ public Address(String line1, String line2, String city, String state, String postalCode) {
+ this.line1 = line1;
+ this.line2 = line2;
+ this.city = city;
+ this.state = state;
+ this.postalCode = postalCode;
+ }
+
+ public String getLine1() {
+ return line1;
+ }
+
+ public void setLine1(String line1) {
+ this.line1 = line1;
+ }
+
+ public String getLine2() {
+ return line2;
+ }
+
+ public void setLine2(String line2) {
+ this.line2 = line2;
+ }
+
+ public String getCity() {
+ return city;
+ }
+
+ public void setCity(String city) {
+ this.city = city;
+ }
+
+ public String getState() {
+ return state;
+ }
+
+ public void setState(String state) {
+ this.state = state;
+ }
+
+ public String getPostalCode() {
+ return postalCode;
+ }
+
+ public void setPostalCode(String postalCode) {
+ this.postalCode = postalCode;
+ }
+
+ public String toString() {
+ return this.line1 + " " + this.line2 + " " + this.city + " " + this.state + " " + this.postalCode;
+ }
+}
diff --git a/exercises/sending-signals-client/practice/src/main/java/sendingsignalsclient/model/Bill.java b/exercises/sending-signals-client/practice/src/main/java/sendingsignalsclient/model/Bill.java
new file mode 100644
index 0000000..31ff32e
--- /dev/null
+++ b/exercises/sending-signals-client/practice/src/main/java/sendingsignalsclient/model/Bill.java
@@ -0,0 +1,55 @@
+package sendingsignalsclient.model;
+
+public class Bill {
+
+ private int customerID;
+ private String orderNumber;
+ private String description;
+ private int amount;
+
+ public Bill() {
+ }
+
+ public Bill(int customerID, String orderNumber, String description, int amount) {
+ this.customerID = customerID;
+ this.orderNumber = orderNumber;
+ this.description = description;
+ this.amount = amount;
+ }
+
+ public int getCustomerID() {
+ return customerID;
+ }
+
+ public void setCustomerID(int customerID) {
+ this.customerID = customerID;
+ }
+
+ public String getOrderNumber() {
+ return orderNumber;
+ }
+
+ public void setOrderNumber(String orderNumber) {
+ this.orderNumber = orderNumber;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ public int getAmount() {
+ return amount;
+ }
+
+ public void setAmount(int amount) {
+ this.amount = amount;
+ }
+
+ public String toString() {
+ return this.customerID + ", " + this.orderNumber + ", " + this.amount;
+ }
+}
diff --git a/exercises/sending-signals-client/practice/src/main/java/sendingsignalsclient/model/Customer.java b/exercises/sending-signals-client/practice/src/main/java/sendingsignalsclient/model/Customer.java
new file mode 100644
index 0000000..df6428b
--- /dev/null
+++ b/exercises/sending-signals-client/practice/src/main/java/sendingsignalsclient/model/Customer.java
@@ -0,0 +1,51 @@
+package sendingsignalsclient.model;
+
+public class Customer {
+
+ private int customerID;
+ private String name;
+ private String email;
+ private String phone;
+
+ public Customer() {
+ }
+
+ public Customer(int customerID, String name, String email, String phone) {
+ this.customerID = customerID;
+ this.name = name;
+ this.email = email;
+ this.phone = phone;
+ }
+
+ public int getCustomerID() {
+ return customerID;
+ }
+
+ public void setCustomerID(int customerID) {
+ this.customerID = customerID;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getEmail() {
+ return email;
+ }
+
+ public void setEmail(String email) {
+ this.email = email;
+ }
+
+ public String getPhone() {
+ return phone;
+ }
+
+ public void setPhone(String phone) {
+ this.phone = phone;
+ }
+}
diff --git a/exercises/sending-signals-client/practice/src/main/java/sendingsignalsclient/model/Distance.java b/exercises/sending-signals-client/practice/src/main/java/sendingsignalsclient/model/Distance.java
new file mode 100644
index 0000000..1f0d772
--- /dev/null
+++ b/exercises/sending-signals-client/practice/src/main/java/sendingsignalsclient/model/Distance.java
@@ -0,0 +1,21 @@
+package sendingsignalsclient.model;
+
+public class Distance {
+
+ private int kilometers;
+
+ public Distance() {
+ }
+
+ public Distance(int kilometers) {
+ this.kilometers = kilometers;
+ }
+
+ public int getKilometers() {
+ return kilometers;
+ }
+
+ public void setKilometers(int kilometers) {
+ this.kilometers = kilometers;
+ }
+}
diff --git a/exercises/sending-signals-client/practice/src/main/java/sendingsignalsclient/model/OrderConfirmation.java b/exercises/sending-signals-client/practice/src/main/java/sendingsignalsclient/model/OrderConfirmation.java
new file mode 100644
index 0000000..724b6b0
--- /dev/null
+++ b/exercises/sending-signals-client/practice/src/main/java/sendingsignalsclient/model/OrderConfirmation.java
@@ -0,0 +1,70 @@
+package sendingsignalsclient.model;
+
+public class OrderConfirmation {
+
+ private String orderNumber;
+ private String status;
+ private String confirmationNumber;
+ private long billingTimestamp;
+ private int amount;
+
+ public OrderConfirmation() {
+ }
+
+ public OrderConfirmation(String orderNumber, String status, String confirmationNumber,
+ long billingTimestamp, int amount) {
+
+ this.orderNumber = orderNumber;
+ this.status = status;
+ this.confirmationNumber = confirmationNumber;
+ this.billingTimestamp = billingTimestamp;
+ this.amount = amount;
+ }
+
+ public String getOrderNumber() {
+ return orderNumber;
+ }
+
+ public void setOrderNumber(String orderNumber) {
+ this.orderNumber = orderNumber;
+ }
+
+ public String getStatus() {
+ return status;
+ }
+
+ public void setStatus(String status) {
+ this.status = status;
+ }
+
+ public String getConfirmationNumber() {
+ return confirmationNumber;
+ }
+
+ public void setConfirmationNumber(String confirmationNumber) {
+ this.confirmationNumber = confirmationNumber;
+ }
+
+ public long getBillingTimestamp() {
+ return billingTimestamp;
+ }
+
+ public void setBillingTimestamp(long billingTimestamp) {
+ this.billingTimestamp = billingTimestamp;
+ }
+
+ public int getAmount() {
+ return amount;
+ }
+
+ public void setAmount(int amount) {
+ this.amount = amount;
+ }
+
+ @Override
+ public String toString() {
+ return "OrderConfirmation{" + "orderNumber='" + orderNumber + '\'' + ", status='" + status
+ + '\'' + ", confirmationNumber='" + confirmationNumber + '\'' + ", billingTimestamp="
+ + billingTimestamp + ", amount=" + amount + '}';
+ }
+}
diff --git a/exercises/sending-signals-client/practice/src/main/java/sendingsignalsclient/model/Pizza.java b/exercises/sending-signals-client/practice/src/main/java/sendingsignalsclient/model/Pizza.java
new file mode 100644
index 0000000..a4f88f7
--- /dev/null
+++ b/exercises/sending-signals-client/practice/src/main/java/sendingsignalsclient/model/Pizza.java
@@ -0,0 +1,30 @@
+package sendingsignalsclient.model;
+
+public class Pizza {
+ private String description;
+ private int price;
+
+ public Pizza() {
+ }
+
+ public Pizza(String description, int price) {
+ this.description = description;
+ this.price = price;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ public int getPrice() {
+ return price;
+ }
+
+ public void setPrice(int price) {
+ this.price = price;
+ }
+}
diff --git a/exercises/sending-signals-client/practice/src/main/java/sendingsignalsclient/model/PizzaOrder.java b/exercises/sending-signals-client/practice/src/main/java/sendingsignalsclient/model/PizzaOrder.java
new file mode 100644
index 0000000..ec17722
--- /dev/null
+++ b/exercises/sending-signals-client/practice/src/main/java/sendingsignalsclient/model/PizzaOrder.java
@@ -0,0 +1,65 @@
+package sendingsignalsclient.model;
+
+import java.util.List;
+
+public class PizzaOrder {
+
+ private String orderNumber;
+ private Customer customer;
+ private List items;
+ private boolean isDelivery;
+ private Address address;
+
+ public PizzaOrder() {
+ }
+
+ public PizzaOrder(String orderNumber, Customer customer, List items, boolean isDelivery,
+ Address address) {
+
+ this.orderNumber = orderNumber;
+ this.customer = customer;
+ this.items = items;
+ this.isDelivery = isDelivery;
+ this.address = address;
+ }
+
+ public String getOrderNumber() {
+ return orderNumber;
+ }
+
+ public void setOrderNumber(String orderNumber) {
+ this.orderNumber = orderNumber;
+ }
+
+ public Customer getCustomer() {
+ return customer;
+ }
+
+ public void setCustomer(Customer customer) {
+ this.customer = customer;
+ }
+
+ public List getItems() {
+ return items;
+ }
+
+ public void setItems(List items) {
+ this.items = items;
+ }
+
+ public boolean isDelivery() {
+ return isDelivery;
+ }
+
+ public void setDelivery(boolean delivery) {
+ isDelivery = delivery;
+ }
+
+ public Address getAddress() {
+ return address;
+ }
+
+ public void setAddress(Address address) {
+ this.address = address;
+ }
+}
diff --git a/exercises/sending-signals-client/practice/src/main/java/sendingsignalsclient/orderpizza/PizzaActivities.java b/exercises/sending-signals-client/practice/src/main/java/sendingsignalsclient/orderpizza/PizzaActivities.java
new file mode 100644
index 0000000..5574763
--- /dev/null
+++ b/exercises/sending-signals-client/practice/src/main/java/sendingsignalsclient/orderpizza/PizzaActivities.java
@@ -0,0 +1,16 @@
+package sendingsignalsclient.orderpizza;
+
+import io.temporal.activity.ActivityInterface;
+import sendingsignalsclient.model.Distance;
+import sendingsignalsclient.exceptions.InvalidChargeAmountException;
+import sendingsignalsclient.model.Address;
+import sendingsignalsclient.model.OrderConfirmation;
+import sendingsignalsclient.model.Bill;
+
+@ActivityInterface
+public interface PizzaActivities {
+
+ Distance getDistance(Address address);
+
+ OrderConfirmation sendBill(Bill bill) throws InvalidChargeAmountException;
+}
diff --git a/exercises/sending-signals-client/practice/src/main/java/sendingsignalsclient/orderpizza/PizzaActivitiesImpl.java b/exercises/sending-signals-client/practice/src/main/java/sendingsignalsclient/orderpizza/PizzaActivitiesImpl.java
new file mode 100644
index 0000000..54968b9
--- /dev/null
+++ b/exercises/sending-signals-client/practice/src/main/java/sendingsignalsclient/orderpizza/PizzaActivitiesImpl.java
@@ -0,0 +1,73 @@
+package sendingsignalsclient.orderpizza;
+
+import sendingsignalsclient.model.OrderConfirmation;
+import sendingsignalsclient.model.Address;
+import sendingsignalsclient.model.Distance;
+import sendingsignalsclient.model.Bill;
+
+import sendingsignalsclient.exceptions.InvalidChargeAmountException;
+
+import java.time.Instant;
+
+import io.temporal.activity.Activity;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class PizzaActivitiesImpl implements PizzaActivities {
+
+ private static final Logger logger = LoggerFactory.getLogger(PizzaActivitiesImpl.class);
+
+ @Override
+ public Distance getDistance(Address address) {
+
+ logger.info("getDistance invoked; determining distance to customer address");
+
+ // this is a simulation, which calculates a fake (but consistent)
+ // distance for a customer address based on its length. The value
+ // will therefore be different when called with different addresses,
+ // but will be the same across all invocations with the same address.
+
+ int kilometers = address.getLine1().length() + address.getLine2().length() - 10;
+ if (kilometers < 1) {
+ kilometers = 5;
+ }
+
+ Distance distance = new Distance(kilometers);
+
+ logger.info("getDistance complete: {}", distance.getKilometers());
+ return distance;
+ }
+
+ @Override
+ public OrderConfirmation sendBill(Bill bill) {
+ int amount = bill.getAmount();
+
+ logger.info("sendBill invoked: customer: {} amount: {}", bill.getCustomerID(), amount);
+
+ int chargeAmount = amount;
+
+ // This month's special offer: Get $5 off all orders over $30
+ if (amount > 3000) {
+ logger.info("Applying discount");
+
+ chargeAmount -= 500; // reduce amount charged by 500 cents
+ }
+
+ // reject invalid amounts before calling the payment processor
+ if (chargeAmount < 0) {
+ logger.error("invalid charge amount: {%d} (must be above zero)", chargeAmount);
+ String errorMessage = "invalid charge amount: " + chargeAmount;
+ throw Activity.wrap(new InvalidChargeAmountException(errorMessage));
+ }
+
+ // pretend we called a payment processing service here
+ OrderConfirmation confirmation = new OrderConfirmation(bill.getOrderNumber(), "SUCCESS",
+ "P24601", Instant.now().getEpochSecond(), chargeAmount);
+
+ logger.debug("Sendbill complete: Confirmation Number: {}",
+ confirmation.getConfirmationNumber());
+
+ return confirmation;
+ }
+}
diff --git a/exercises/sending-signals-client/practice/src/main/java/sendingsignalsclient/orderpizza/PizzaWorkflow.java b/exercises/sending-signals-client/practice/src/main/java/sendingsignalsclient/orderpizza/PizzaWorkflow.java
new file mode 100644
index 0000000..b881ed2
--- /dev/null
+++ b/exercises/sending-signals-client/practice/src/main/java/sendingsignalsclient/orderpizza/PizzaWorkflow.java
@@ -0,0 +1,18 @@
+package sendingsignalsclient.orderpizza;
+
+import io.temporal.workflow.WorkflowInterface;
+import io.temporal.workflow.WorkflowMethod;
+import io.temporal.workflow.SignalMethod;
+import sendingsignalsclient.model.PizzaOrder;
+import sendingsignalsclient.model.OrderConfirmation;
+
+@WorkflowInterface
+public interface PizzaWorkflow {
+
+ @WorkflowMethod
+ OrderConfirmation orderPizza(PizzaOrder order);
+
+ @SignalMethod
+ void fulfillOrderSignal(boolean bool);
+
+}
diff --git a/exercises/sending-signals-client/practice/src/main/java/sendingsignalsclient/orderpizza/PizzaWorkflowImpl.java b/exercises/sending-signals-client/practice/src/main/java/sendingsignalsclient/orderpizza/PizzaWorkflowImpl.java
new file mode 100644
index 0000000..d318a33
--- /dev/null
+++ b/exercises/sending-signals-client/practice/src/main/java/sendingsignalsclient/orderpizza/PizzaWorkflowImpl.java
@@ -0,0 +1,91 @@
+package sendingsignalsclient.orderpizza;
+
+import io.temporal.activity.ActivityOptions;
+import io.temporal.workflow.Workflow;
+import io.temporal.failure.ApplicationFailure;
+
+import sendingsignalsclient.model.Address;
+import sendingsignalsclient.model.Bill;
+import sendingsignalsclient.model.Customer;
+import sendingsignalsclient.model.Distance;
+import sendingsignalsclient.model.OrderConfirmation;
+import sendingsignalsclient.model.Pizza;
+import sendingsignalsclient.model.PizzaOrder;
+import sendingsignalsclient.exceptions.InvalidChargeAmountException;
+import sendingsignalsclient.exceptions.OutOfServiceAreaException;
+
+import java.time.Duration;
+import java.util.List;
+
+import org.slf4j.Logger;
+
+public class PizzaWorkflowImpl implements PizzaWorkflow {
+
+ public static final Logger logger = Workflow.getLogger(PizzaWorkflowImpl.class);
+
+ private boolean fulfilled;
+
+ ActivityOptions options = ActivityOptions.newBuilder().setStartToCloseTimeout(Duration.ofSeconds(5)).build();
+
+ private final PizzaActivities activities = Workflow.newActivityStub(PizzaActivities.class, options);
+
+ @Override
+ public OrderConfirmation orderPizza(PizzaOrder order) {
+
+ String orderNumber = order.getOrderNumber();
+ Customer customer = order.getCustomer();
+ List items = order.getItems();
+ boolean isDelivery = order.isDelivery();
+ Address address = order.getAddress();
+
+ logger.info("orderPizza Workflow Invoked");
+
+ int totalPrice = 0;
+ for (Pizza pizza : items) {
+ totalPrice += pizza.getPrice();
+ }
+
+ Distance distance;
+ try {
+ distance = activities.getDistance(address);
+ } catch (NullPointerException e) {
+ logger.error("Unable to get distance");
+ throw new NullPointerException("Unable to get distance");
+ }
+
+ if (isDelivery && (distance.getKilometers() > 25)) {
+ logger.error("Customer lives outside the service area");
+ throw ApplicationFailure.newFailure("Customer lives outside the service area",
+ OutOfServiceAreaException.class.getName());
+ }
+
+ logger.info("distance is {}", distance.getKilometers());
+
+ Workflow.await(() -> this.fulfilled);
+
+ OrderConfirmation confirmation;
+
+ if (this.fulfilled) {
+ Bill bill = new Bill(customer.getCustomerID(), orderNumber, "Pizza", totalPrice);
+
+ try {
+ confirmation = activities.sendBill(bill);
+ logger.info("Bill sent to customer {}", customer.getCustomerID());
+ } catch (InvalidChargeAmountException e) {
+ logger.error("Unable to bill customer");
+ throw Workflow.wrap(new InvalidChargeAmountException("Unable to bill customer"));
+ }
+ } else {
+ confirmation = null;
+ logger.info("Order was not fulfilled. Not billing the customer.");
+ }
+
+ return confirmation;
+
+ }
+
+ @Override
+ public void fulfillOrderSignal(boolean bool) {
+ this.fulfilled = bool;
+ }
+}
diff --git a/exercises/sending-signals-client/practice/src/main/resources/logback.xml b/exercises/sending-signals-client/practice/src/main/resources/logback.xml
new file mode 100644
index 0000000..8e488b0
--- /dev/null
+++ b/exercises/sending-signals-client/practice/src/main/resources/logback.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+ %d{HH:mm:ss.SSS} %-5level - %msg %n
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/exercises/sending-signals-client/solution/pom.xml b/exercises/sending-signals-client/solution/pom.xml
new file mode 100644
index 0000000..eb7ff16
--- /dev/null
+++ b/exercises/sending-signals-client/solution/pom.xml
@@ -0,0 +1,109 @@
+
+
+
+ 4.0.0
+
+ io.temporal.learn
+ sending-signals-client-solution
+ 1.0.0-SNAPSHOT
+
+ sending signals client (solution)
+ https://learn.temporal.io/
+
+
+ UTF-8
+ 1.8
+ 1.8
+
+
+
+
+
+ io.temporal
+ temporal-sdk
+ 1.20.1
+
+
+
+ ch.qos.logback
+ logback-classic
+ 1.4.8
+
+
+
+ org.apache.commons
+ commons-lang3
+ 3.11
+
+
+
+ io.temporal
+ temporal-testing
+ 1.20.1
+ test
+
+
+
+
+ org.junit.jupiter
+ junit-jupiter
+ 5.5.2
+ test
+
+
+
+ org.mockito
+ mockito-junit-jupiter
+ 5.3.1
+ test
+
+
+
+
+
+
+
+
+
+ maven-clean-plugin
+ 3.1.0
+
+
+
+ maven-resources-plugin
+ 3.0.2
+
+
+ maven-compiler-plugin
+ 3.8.0
+
+
+ maven-surefire-plugin
+ 2.22.1
+
+
+ maven-jar-plugin
+ 3.0.2
+
+
+ maven-install-plugin
+ 2.5.2
+
+
+ maven-deploy-plugin
+ 2.8.2
+
+
+
+ maven-site-plugin
+ 3.7.1
+
+
+ maven-project-info-reports-plugin
+ 3.0.0
+
+
+
+
+
diff --git a/exercises/sending-signals-client/solution/src/main/java/sendingsignalsclient/Constants.java b/exercises/sending-signals-client/solution/src/main/java/sendingsignalsclient/Constants.java
new file mode 100644
index 0000000..9ef9bd1
--- /dev/null
+++ b/exercises/sending-signals-client/solution/src/main/java/sendingsignalsclient/Constants.java
@@ -0,0 +1,7 @@
+package sendingsignalsclient;
+
+public class Constants {
+
+ public static final String TASK_QUEUE_NAME = "pizza-tasks";
+
+}
diff --git a/exercises/sending-signals-client/solution/src/main/java/sendingsignalsclient/SendSignalClientWorker.java b/exercises/sending-signals-client/solution/src/main/java/sendingsignalsclient/SendSignalClientWorker.java
new file mode 100644
index 0000000..ff66ba4
--- /dev/null
+++ b/exercises/sending-signals-client/solution/src/main/java/sendingsignalsclient/SendSignalClientWorker.java
@@ -0,0 +1,26 @@
+package sendingsignalsclient;
+
+import io.temporal.client.WorkflowClient;
+import io.temporal.serviceclient.WorkflowServiceStubs;
+import io.temporal.worker.Worker;
+import io.temporal.worker.WorkerFactory;
+
+import sendingsignalsclient.orderpizza.PizzaActivitiesImpl;
+import sendingsignalsclient.orderpizza.PizzaWorkflowImpl;
+
+public class SendSignalClientWorker {
+ public static void main(String[] args) {
+
+ WorkflowServiceStubs service = WorkflowServiceStubs.newLocalServiceStubs();
+ WorkflowClient client = WorkflowClient.newInstance(service);
+ WorkerFactory factory = WorkerFactory.newInstance(client);
+
+ Worker worker = factory.newWorker(Constants.TASK_QUEUE_NAME);
+
+ worker.registerWorkflowImplementationTypes(PizzaWorkflowImpl.class);
+
+ worker.registerActivitiesImplementations(new PizzaActivitiesImpl());
+
+ factory.start();
+ }
+}
diff --git a/exercises/sending-signals-client/solution/src/main/java/sendingsignalsclient/SignalClient.java b/exercises/sending-signals-client/solution/src/main/java/sendingsignalsclient/SignalClient.java
new file mode 100644
index 0000000..e9ad234
--- /dev/null
+++ b/exercises/sending-signals-client/solution/src/main/java/sendingsignalsclient/SignalClient.java
@@ -0,0 +1,21 @@
+package sendingsignalsclient;
+
+import io.temporal.client.WorkflowClient;
+import io.temporal.serviceclient.WorkflowServiceStubs;
+import sendingsignalsclient.orderpizza.PizzaWorkflow;
+
+
+public class SignalClient {
+ public static void main(String[] args) throws Exception {
+
+ WorkflowServiceStubs service = WorkflowServiceStubs.newLocalServiceStubs();
+
+ WorkflowClient client = WorkflowClient.newInstance(service);
+
+ PizzaWorkflow workflow = client.newWorkflowStub(PizzaWorkflow.class, "pizza-workflow-order-XD001");
+
+ workflow.fulfillOrderSignal(true);
+
+ System.exit(0);
+ }
+}
\ No newline at end of file
diff --git a/exercises/sending-signals-client/solution/src/main/java/sendingsignalsclient/Starter.java b/exercises/sending-signals-client/solution/src/main/java/sendingsignalsclient/Starter.java
new file mode 100644
index 0000000..6b9836a
--- /dev/null
+++ b/exercises/sending-signals-client/solution/src/main/java/sendingsignalsclient/Starter.java
@@ -0,0 +1,58 @@
+package sendingsignalsclient;
+
+import io.temporal.client.WorkflowClient;
+import io.temporal.client.WorkflowOptions;
+import java.util.concurrent.CompletableFuture;
+import io.temporal.serviceclient.WorkflowServiceStubs;
+
+import sendingsignalsclient.model.PizzaOrder;
+import sendingsignalsclient.model.Pizza;
+import sendingsignalsclient.model.Customer;
+import sendingsignalsclient.model.OrderConfirmation;
+import sendingsignalsclient.model.Address;
+import sendingsignalsclient.orderpizza.PizzaWorkflow;
+
+import java.util.Arrays;
+import java.util.List;
+
+public class Starter {
+ public static void main(String[] args) throws Exception {
+
+ WorkflowServiceStubs service = WorkflowServiceStubs.newLocalServiceStubs();
+
+ WorkflowClient client = WorkflowClient.newInstance(service);
+
+ PizzaOrder order = createPizzaOrder();
+
+ // Setup the Pizza Order Workflow Client
+ String pizzaWorkflowID = String.format("pizza-workflow-order-%s", order.getOrderNumber());
+
+ WorkflowOptions pizzaWorkflowOptions = WorkflowOptions.newBuilder()
+ .setWorkflowId(pizzaWorkflowID)
+ .setTaskQueue(Constants.TASK_QUEUE_NAME)
+ .build();
+
+ PizzaWorkflow pizzaWorkflow = client.newWorkflowStub(PizzaWorkflow.class, pizzaWorkflowOptions);
+
+ OrderConfirmation orderConfirmation = pizzaWorkflow.orderPizza(order);
+ //CompletableFuture orderConfirmation = WorkflowClient.execute(pizzaWorkflow::orderPizza, order);
+
+
+ System.out.printf("Workflow result: %s\n", orderConfirmation);
+ System.exit(0);
+ }
+
+ private static PizzaOrder createPizzaOrder() {
+ Customer customer = new Customer(8675309, "Lisa Anderson", "lisa@example.com", "555-555-0000");
+ Address address = new Address("742 Evergreen Terrace", "Apartment 221B", "Albuquerque", "NM", "87101");
+ Pizza pizza1 = new Pizza("Large, with mushrooms and onions", 1500);
+ Pizza pizza2 = new Pizza("Small, with pepperoni", 1200);
+ Pizza pizza3 = new Pizza("Medium, with extra cheese", 1300);
+
+ List orderList = Arrays.asList(pizza1, pizza2, pizza3);
+
+ PizzaOrder order = new PizzaOrder("XD001", customer, orderList, true, address);
+
+ return order;
+ }
+}
diff --git a/exercises/sending-signals-client/solution/src/main/java/sendingsignalsclient/exceptions/InvalidChargeAmountException.java b/exercises/sending-signals-client/solution/src/main/java/sendingsignalsclient/exceptions/InvalidChargeAmountException.java
new file mode 100644
index 0000000..09e8ec0
--- /dev/null
+++ b/exercises/sending-signals-client/solution/src/main/java/sendingsignalsclient/exceptions/InvalidChargeAmountException.java
@@ -0,0 +1,20 @@
+package sendingsignalsclient.exceptions;
+
+public class InvalidChargeAmountException extends Exception {
+
+ public InvalidChargeAmountException() {
+ super();
+ }
+
+ public InvalidChargeAmountException(String message) {
+ super(message);
+ }
+
+ public InvalidChargeAmountException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public InvalidChargeAmountException(Throwable cause) {
+ super(cause);
+ }
+}
diff --git a/exercises/sending-signals-client/solution/src/main/java/sendingsignalsclient/exceptions/OutOfServiceAreaException.java b/exercises/sending-signals-client/solution/src/main/java/sendingsignalsclient/exceptions/OutOfServiceAreaException.java
new file mode 100644
index 0000000..9bb4dfa
--- /dev/null
+++ b/exercises/sending-signals-client/solution/src/main/java/sendingsignalsclient/exceptions/OutOfServiceAreaException.java
@@ -0,0 +1,20 @@
+package sendingsignalsclient.exceptions;
+
+public class OutOfServiceAreaException extends Exception {
+
+ public OutOfServiceAreaException() {
+ super();
+ }
+
+ public OutOfServiceAreaException(String message) {
+ super(message);
+ }
+
+ public OutOfServiceAreaException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public OutOfServiceAreaException(Throwable cause) {
+ super(cause);
+ }
+}
diff --git a/exercises/sending-signals-client/solution/src/main/java/sendingsignalsclient/model/Address.java b/exercises/sending-signals-client/solution/src/main/java/sendingsignalsclient/model/Address.java
new file mode 100644
index 0000000..c028ab3
--- /dev/null
+++ b/exercises/sending-signals-client/solution/src/main/java/sendingsignalsclient/model/Address.java
@@ -0,0 +1,65 @@
+package sendingsignalsclient.model;
+
+public class Address {
+
+ private String line1;
+ private String line2;
+ private String city;
+ private String state;
+ private String postalCode;
+
+ public Address() {
+ }
+
+ public Address(String line1, String line2, String city, String state, String postalCode) {
+ this.line1 = line1;
+ this.line2 = line2;
+ this.city = city;
+ this.state = state;
+ this.postalCode = postalCode;
+ }
+
+ public String getLine1() {
+ return line1;
+ }
+
+ public void setLine1(String line1) {
+ this.line1 = line1;
+ }
+
+ public String getLine2() {
+ return line2;
+ }
+
+ public void setLine2(String line2) {
+ this.line2 = line2;
+ }
+
+ public String getCity() {
+ return city;
+ }
+
+ public void setCity(String city) {
+ this.city = city;
+ }
+
+ public String getState() {
+ return state;
+ }
+
+ public void setState(String state) {
+ this.state = state;
+ }
+
+ public String getPostalCode() {
+ return postalCode;
+ }
+
+ public void setPostalCode(String postalCode) {
+ this.postalCode = postalCode;
+ }
+
+ public String toString() {
+ return this.line1 + " " + this.line2 + " " + this.city + " " + this.state + " " + this.postalCode;
+ }
+}
diff --git a/exercises/sending-signals-client/solution/src/main/java/sendingsignalsclient/model/Bill.java b/exercises/sending-signals-client/solution/src/main/java/sendingsignalsclient/model/Bill.java
new file mode 100644
index 0000000..31ff32e
--- /dev/null
+++ b/exercises/sending-signals-client/solution/src/main/java/sendingsignalsclient/model/Bill.java
@@ -0,0 +1,55 @@
+package sendingsignalsclient.model;
+
+public class Bill {
+
+ private int customerID;
+ private String orderNumber;
+ private String description;
+ private int amount;
+
+ public Bill() {
+ }
+
+ public Bill(int customerID, String orderNumber, String description, int amount) {
+ this.customerID = customerID;
+ this.orderNumber = orderNumber;
+ this.description = description;
+ this.amount = amount;
+ }
+
+ public int getCustomerID() {
+ return customerID;
+ }
+
+ public void setCustomerID(int customerID) {
+ this.customerID = customerID;
+ }
+
+ public String getOrderNumber() {
+ return orderNumber;
+ }
+
+ public void setOrderNumber(String orderNumber) {
+ this.orderNumber = orderNumber;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ public int getAmount() {
+ return amount;
+ }
+
+ public void setAmount(int amount) {
+ this.amount = amount;
+ }
+
+ public String toString() {
+ return this.customerID + ", " + this.orderNumber + ", " + this.amount;
+ }
+}
diff --git a/exercises/sending-signals-client/solution/src/main/java/sendingsignalsclient/model/Customer.java b/exercises/sending-signals-client/solution/src/main/java/sendingsignalsclient/model/Customer.java
new file mode 100644
index 0000000..df6428b
--- /dev/null
+++ b/exercises/sending-signals-client/solution/src/main/java/sendingsignalsclient/model/Customer.java
@@ -0,0 +1,51 @@
+package sendingsignalsclient.model;
+
+public class Customer {
+
+ private int customerID;
+ private String name;
+ private String email;
+ private String phone;
+
+ public Customer() {
+ }
+
+ public Customer(int customerID, String name, String email, String phone) {
+ this.customerID = customerID;
+ this.name = name;
+ this.email = email;
+ this.phone = phone;
+ }
+
+ public int getCustomerID() {
+ return customerID;
+ }
+
+ public void setCustomerID(int customerID) {
+ this.customerID = customerID;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getEmail() {
+ return email;
+ }
+
+ public void setEmail(String email) {
+ this.email = email;
+ }
+
+ public String getPhone() {
+ return phone;
+ }
+
+ public void setPhone(String phone) {
+ this.phone = phone;
+ }
+}
diff --git a/exercises/sending-signals-client/solution/src/main/java/sendingsignalsclient/model/Distance.java b/exercises/sending-signals-client/solution/src/main/java/sendingsignalsclient/model/Distance.java
new file mode 100644
index 0000000..1f0d772
--- /dev/null
+++ b/exercises/sending-signals-client/solution/src/main/java/sendingsignalsclient/model/Distance.java
@@ -0,0 +1,21 @@
+package sendingsignalsclient.model;
+
+public class Distance {
+
+ private int kilometers;
+
+ public Distance() {
+ }
+
+ public Distance(int kilometers) {
+ this.kilometers = kilometers;
+ }
+
+ public int getKilometers() {
+ return kilometers;
+ }
+
+ public void setKilometers(int kilometers) {
+ this.kilometers = kilometers;
+ }
+}
diff --git a/exercises/sending-signals-client/solution/src/main/java/sendingsignalsclient/model/OrderConfirmation.java b/exercises/sending-signals-client/solution/src/main/java/sendingsignalsclient/model/OrderConfirmation.java
new file mode 100644
index 0000000..724b6b0
--- /dev/null
+++ b/exercises/sending-signals-client/solution/src/main/java/sendingsignalsclient/model/OrderConfirmation.java
@@ -0,0 +1,70 @@
+package sendingsignalsclient.model;
+
+public class OrderConfirmation {
+
+ private String orderNumber;
+ private String status;
+ private String confirmationNumber;
+ private long billingTimestamp;
+ private int amount;
+
+ public OrderConfirmation() {
+ }
+
+ public OrderConfirmation(String orderNumber, String status, String confirmationNumber,
+ long billingTimestamp, int amount) {
+
+ this.orderNumber = orderNumber;
+ this.status = status;
+ this.confirmationNumber = confirmationNumber;
+ this.billingTimestamp = billingTimestamp;
+ this.amount = amount;
+ }
+
+ public String getOrderNumber() {
+ return orderNumber;
+ }
+
+ public void setOrderNumber(String orderNumber) {
+ this.orderNumber = orderNumber;
+ }
+
+ public String getStatus() {
+ return status;
+ }
+
+ public void setStatus(String status) {
+ this.status = status;
+ }
+
+ public String getConfirmationNumber() {
+ return confirmationNumber;
+ }
+
+ public void setConfirmationNumber(String confirmationNumber) {
+ this.confirmationNumber = confirmationNumber;
+ }
+
+ public long getBillingTimestamp() {
+ return billingTimestamp;
+ }
+
+ public void setBillingTimestamp(long billingTimestamp) {
+ this.billingTimestamp = billingTimestamp;
+ }
+
+ public int getAmount() {
+ return amount;
+ }
+
+ public void setAmount(int amount) {
+ this.amount = amount;
+ }
+
+ @Override
+ public String toString() {
+ return "OrderConfirmation{" + "orderNumber='" + orderNumber + '\'' + ", status='" + status
+ + '\'' + ", confirmationNumber='" + confirmationNumber + '\'' + ", billingTimestamp="
+ + billingTimestamp + ", amount=" + amount + '}';
+ }
+}
diff --git a/exercises/sending-signals-client/solution/src/main/java/sendingsignalsclient/model/Pizza.java b/exercises/sending-signals-client/solution/src/main/java/sendingsignalsclient/model/Pizza.java
new file mode 100644
index 0000000..a4f88f7
--- /dev/null
+++ b/exercises/sending-signals-client/solution/src/main/java/sendingsignalsclient/model/Pizza.java
@@ -0,0 +1,30 @@
+package sendingsignalsclient.model;
+
+public class Pizza {
+ private String description;
+ private int price;
+
+ public Pizza() {
+ }
+
+ public Pizza(String description, int price) {
+ this.description = description;
+ this.price = price;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ public int getPrice() {
+ return price;
+ }
+
+ public void setPrice(int price) {
+ this.price = price;
+ }
+}
diff --git a/exercises/sending-signals-client/solution/src/main/java/sendingsignalsclient/model/PizzaOrder.java b/exercises/sending-signals-client/solution/src/main/java/sendingsignalsclient/model/PizzaOrder.java
new file mode 100644
index 0000000..ec17722
--- /dev/null
+++ b/exercises/sending-signals-client/solution/src/main/java/sendingsignalsclient/model/PizzaOrder.java
@@ -0,0 +1,65 @@
+package sendingsignalsclient.model;
+
+import java.util.List;
+
+public class PizzaOrder {
+
+ private String orderNumber;
+ private Customer customer;
+ private List items;
+ private boolean isDelivery;
+ private Address address;
+
+ public PizzaOrder() {
+ }
+
+ public PizzaOrder(String orderNumber, Customer customer, List items, boolean isDelivery,
+ Address address) {
+
+ this.orderNumber = orderNumber;
+ this.customer = customer;
+ this.items = items;
+ this.isDelivery = isDelivery;
+ this.address = address;
+ }
+
+ public String getOrderNumber() {
+ return orderNumber;
+ }
+
+ public void setOrderNumber(String orderNumber) {
+ this.orderNumber = orderNumber;
+ }
+
+ public Customer getCustomer() {
+ return customer;
+ }
+
+ public void setCustomer(Customer customer) {
+ this.customer = customer;
+ }
+
+ public List getItems() {
+ return items;
+ }
+
+ public void setItems(List items) {
+ this.items = items;
+ }
+
+ public boolean isDelivery() {
+ return isDelivery;
+ }
+
+ public void setDelivery(boolean delivery) {
+ isDelivery = delivery;
+ }
+
+ public Address getAddress() {
+ return address;
+ }
+
+ public void setAddress(Address address) {
+ this.address = address;
+ }
+}
diff --git a/exercises/sending-signals-client/solution/src/main/java/sendingsignalsclient/orderpizza/PizzaActivities.java b/exercises/sending-signals-client/solution/src/main/java/sendingsignalsclient/orderpizza/PizzaActivities.java
new file mode 100644
index 0000000..5574763
--- /dev/null
+++ b/exercises/sending-signals-client/solution/src/main/java/sendingsignalsclient/orderpizza/PizzaActivities.java
@@ -0,0 +1,16 @@
+package sendingsignalsclient.orderpizza;
+
+import io.temporal.activity.ActivityInterface;
+import sendingsignalsclient.model.Distance;
+import sendingsignalsclient.exceptions.InvalidChargeAmountException;
+import sendingsignalsclient.model.Address;
+import sendingsignalsclient.model.OrderConfirmation;
+import sendingsignalsclient.model.Bill;
+
+@ActivityInterface
+public interface PizzaActivities {
+
+ Distance getDistance(Address address);
+
+ OrderConfirmation sendBill(Bill bill) throws InvalidChargeAmountException;
+}
diff --git a/exercises/sending-signals-client/solution/src/main/java/sendingsignalsclient/orderpizza/PizzaActivitiesImpl.java b/exercises/sending-signals-client/solution/src/main/java/sendingsignalsclient/orderpizza/PizzaActivitiesImpl.java
new file mode 100644
index 0000000..54968b9
--- /dev/null
+++ b/exercises/sending-signals-client/solution/src/main/java/sendingsignalsclient/orderpizza/PizzaActivitiesImpl.java
@@ -0,0 +1,73 @@
+package sendingsignalsclient.orderpizza;
+
+import sendingsignalsclient.model.OrderConfirmation;
+import sendingsignalsclient.model.Address;
+import sendingsignalsclient.model.Distance;
+import sendingsignalsclient.model.Bill;
+
+import sendingsignalsclient.exceptions.InvalidChargeAmountException;
+
+import java.time.Instant;
+
+import io.temporal.activity.Activity;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class PizzaActivitiesImpl implements PizzaActivities {
+
+ private static final Logger logger = LoggerFactory.getLogger(PizzaActivitiesImpl.class);
+
+ @Override
+ public Distance getDistance(Address address) {
+
+ logger.info("getDistance invoked; determining distance to customer address");
+
+ // this is a simulation, which calculates a fake (but consistent)
+ // distance for a customer address based on its length. The value
+ // will therefore be different when called with different addresses,
+ // but will be the same across all invocations with the same address.
+
+ int kilometers = address.getLine1().length() + address.getLine2().length() - 10;
+ if (kilometers < 1) {
+ kilometers = 5;
+ }
+
+ Distance distance = new Distance(kilometers);
+
+ logger.info("getDistance complete: {}", distance.getKilometers());
+ return distance;
+ }
+
+ @Override
+ public OrderConfirmation sendBill(Bill bill) {
+ int amount = bill.getAmount();
+
+ logger.info("sendBill invoked: customer: {} amount: {}", bill.getCustomerID(), amount);
+
+ int chargeAmount = amount;
+
+ // This month's special offer: Get $5 off all orders over $30
+ if (amount > 3000) {
+ logger.info("Applying discount");
+
+ chargeAmount -= 500; // reduce amount charged by 500 cents
+ }
+
+ // reject invalid amounts before calling the payment processor
+ if (chargeAmount < 0) {
+ logger.error("invalid charge amount: {%d} (must be above zero)", chargeAmount);
+ String errorMessage = "invalid charge amount: " + chargeAmount;
+ throw Activity.wrap(new InvalidChargeAmountException(errorMessage));
+ }
+
+ // pretend we called a payment processing service here
+ OrderConfirmation confirmation = new OrderConfirmation(bill.getOrderNumber(), "SUCCESS",
+ "P24601", Instant.now().getEpochSecond(), chargeAmount);
+
+ logger.debug("Sendbill complete: Confirmation Number: {}",
+ confirmation.getConfirmationNumber());
+
+ return confirmation;
+ }
+}
diff --git a/exercises/sending-signals-client/solution/src/main/java/sendingsignalsclient/orderpizza/PizzaWorkflow.java b/exercises/sending-signals-client/solution/src/main/java/sendingsignalsclient/orderpizza/PizzaWorkflow.java
new file mode 100644
index 0000000..b881ed2
--- /dev/null
+++ b/exercises/sending-signals-client/solution/src/main/java/sendingsignalsclient/orderpizza/PizzaWorkflow.java
@@ -0,0 +1,18 @@
+package sendingsignalsclient.orderpizza;
+
+import io.temporal.workflow.WorkflowInterface;
+import io.temporal.workflow.WorkflowMethod;
+import io.temporal.workflow.SignalMethod;
+import sendingsignalsclient.model.PizzaOrder;
+import sendingsignalsclient.model.OrderConfirmation;
+
+@WorkflowInterface
+public interface PizzaWorkflow {
+
+ @WorkflowMethod
+ OrderConfirmation orderPizza(PizzaOrder order);
+
+ @SignalMethod
+ void fulfillOrderSignal(boolean bool);
+
+}
diff --git a/exercises/sending-signals-client/solution/src/main/java/sendingsignalsclient/orderpizza/PizzaWorkflowImpl.java b/exercises/sending-signals-client/solution/src/main/java/sendingsignalsclient/orderpizza/PizzaWorkflowImpl.java
new file mode 100644
index 0000000..d318a33
--- /dev/null
+++ b/exercises/sending-signals-client/solution/src/main/java/sendingsignalsclient/orderpizza/PizzaWorkflowImpl.java
@@ -0,0 +1,91 @@
+package sendingsignalsclient.orderpizza;
+
+import io.temporal.activity.ActivityOptions;
+import io.temporal.workflow.Workflow;
+import io.temporal.failure.ApplicationFailure;
+
+import sendingsignalsclient.model.Address;
+import sendingsignalsclient.model.Bill;
+import sendingsignalsclient.model.Customer;
+import sendingsignalsclient.model.Distance;
+import sendingsignalsclient.model.OrderConfirmation;
+import sendingsignalsclient.model.Pizza;
+import sendingsignalsclient.model.PizzaOrder;
+import sendingsignalsclient.exceptions.InvalidChargeAmountException;
+import sendingsignalsclient.exceptions.OutOfServiceAreaException;
+
+import java.time.Duration;
+import java.util.List;
+
+import org.slf4j.Logger;
+
+public class PizzaWorkflowImpl implements PizzaWorkflow {
+
+ public static final Logger logger = Workflow.getLogger(PizzaWorkflowImpl.class);
+
+ private boolean fulfilled;
+
+ ActivityOptions options = ActivityOptions.newBuilder().setStartToCloseTimeout(Duration.ofSeconds(5)).build();
+
+ private final PizzaActivities activities = Workflow.newActivityStub(PizzaActivities.class, options);
+
+ @Override
+ public OrderConfirmation orderPizza(PizzaOrder order) {
+
+ String orderNumber = order.getOrderNumber();
+ Customer customer = order.getCustomer();
+ List items = order.getItems();
+ boolean isDelivery = order.isDelivery();
+ Address address = order.getAddress();
+
+ logger.info("orderPizza Workflow Invoked");
+
+ int totalPrice = 0;
+ for (Pizza pizza : items) {
+ totalPrice += pizza.getPrice();
+ }
+
+ Distance distance;
+ try {
+ distance = activities.getDistance(address);
+ } catch (NullPointerException e) {
+ logger.error("Unable to get distance");
+ throw new NullPointerException("Unable to get distance");
+ }
+
+ if (isDelivery && (distance.getKilometers() > 25)) {
+ logger.error("Customer lives outside the service area");
+ throw ApplicationFailure.newFailure("Customer lives outside the service area",
+ OutOfServiceAreaException.class.getName());
+ }
+
+ logger.info("distance is {}", distance.getKilometers());
+
+ Workflow.await(() -> this.fulfilled);
+
+ OrderConfirmation confirmation;
+
+ if (this.fulfilled) {
+ Bill bill = new Bill(customer.getCustomerID(), orderNumber, "Pizza", totalPrice);
+
+ try {
+ confirmation = activities.sendBill(bill);
+ logger.info("Bill sent to customer {}", customer.getCustomerID());
+ } catch (InvalidChargeAmountException e) {
+ logger.error("Unable to bill customer");
+ throw Workflow.wrap(new InvalidChargeAmountException("Unable to bill customer"));
+ }
+ } else {
+ confirmation = null;
+ logger.info("Order was not fulfilled. Not billing the customer.");
+ }
+
+ return confirmation;
+
+ }
+
+ @Override
+ public void fulfillOrderSignal(boolean bool) {
+ this.fulfilled = bool;
+ }
+}
diff --git a/exercises/sending-signals-client/solution/src/main/resources/logback.xml b/exercises/sending-signals-client/solution/src/main/resources/logback.xml
new file mode 100644
index 0000000..8e488b0
--- /dev/null
+++ b/exercises/sending-signals-client/solution/src/main/resources/logback.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+ %d{HH:mm:ss.SSS} %-5level - %msg %n
+
+
+
+
+
+
+
\ No newline at end of file