diff --git a/README.md b/README.md index a8af530f4..f6351bbce 100644 --- a/README.md +++ b/README.md @@ -105,6 +105,8 @@ See the README.md file in each main sample directory for cut/paste Gradle comman - [**Safe Message Passing**](/core/src/main/java/io/temporal/samples/safemessagepassing): Safely handling concurrent updates and signals messages. +- [**Custom Annotation**](/core/src/main/java/io/temporal/samples/customannotation): Demonstrates how to create a custom annotation using an interceptor. + #### API demonstrations - [**Async Untyped Child Workflow**](/core/src/main/java/io/temporal/samples/asyncuntypedchild): Demonstrates how to invoke an untyped child workflow async, that can complete after parent workflow is already completed. diff --git a/build.gradle b/build.gradle index 6c5da619d..a7ece2dce 100644 --- a/build.gradle +++ b/build.gradle @@ -26,7 +26,7 @@ subprojects { ext { otelVersion = '1.30.1' otelVersionAlpha = "${otelVersion}-alpha" - javaSDKVersion = '1.30.0' + javaSDKVersion = '1.30.1' camelVersion = '3.22.1' jarVersion = '1.0.0' } diff --git a/core/src/main/java/io/temporal/samples/customannotation/BenignExceptionTypes.java b/core/src/main/java/io/temporal/samples/customannotation/BenignExceptionTypes.java new file mode 100644 index 000000000..a97ebd26c --- /dev/null +++ b/core/src/main/java/io/temporal/samples/customannotation/BenignExceptionTypes.java @@ -0,0 +1,18 @@ +package io.temporal.samples.customannotation; + +import java.lang.annotation.*; + +/** + * BenignExceptionTypes is an annotation that can be used to specify an exception type is benign and + * not an issue worth logging. + * + *
For this annotation to work, {@link BenignExceptionTypesAnnotationInterceptor} must be passed
+ * as a worker interceptor to the worker factory.
+ */
+@Documented
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface BenignExceptionTypes {
+ /** Type of exceptions that should be considered benign and not logged as errors. */
+ Class extends Exception>[] value();
+}
diff --git a/core/src/main/java/io/temporal/samples/customannotation/BenignExceptionTypesAnnotationInterceptor.java b/core/src/main/java/io/temporal/samples/customannotation/BenignExceptionTypesAnnotationInterceptor.java
new file mode 100644
index 000000000..71ee51a9d
--- /dev/null
+++ b/core/src/main/java/io/temporal/samples/customannotation/BenignExceptionTypesAnnotationInterceptor.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (c) 2020 Temporal Technologies, Inc. All Rights Reserved
+ *
+ * Copyright 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Modifications copyright (C) 2017 Uber Technologies, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may not
+ * use this file except in compliance with the License. A copy of the License is
+ * located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package io.temporal.samples.customannotation;
+
+import io.temporal.activity.ActivityExecutionContext;
+import io.temporal.common.interceptors.ActivityInboundCallsInterceptor;
+import io.temporal.common.interceptors.WorkerInterceptorBase;
+import io.temporal.common.metadata.POJOActivityImplMetadata;
+import io.temporal.common.metadata.POJOActivityMethodMetadata;
+import io.temporal.failure.ApplicationErrorCategory;
+import io.temporal.failure.ApplicationFailure;
+import io.temporal.failure.TemporalFailure;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Checks if the activity method has the {@link BenignExceptionTypes} annotation. If it does, it
+ * will throw an ApplicationFailure with {@link ApplicationErrorCategory#BENIGN}.
+ */
+public class BenignExceptionTypesAnnotationInterceptor extends WorkerInterceptorBase {
+
+ @Override
+ public ActivityInboundCallsInterceptor interceptActivity(ActivityInboundCallsInterceptor next) {
+ return new ActivityInboundCallsInterceptorAnnotation(next);
+ }
+
+ public static class ActivityInboundCallsInterceptorAnnotation
+ extends io.temporal.common.interceptors.ActivityInboundCallsInterceptorBase {
+ private final ActivityInboundCallsInterceptor next;
+ private Set Workflow Definitions should not contain any heavyweight computations, non-deterministic
+ * code, network calls, database operations, etc. Those things should be handled by the
+ * Activities.
+ *
+ * @see WorkflowInterface
+ * @see WorkflowMethod
+ */
+ @WorkflowInterface
+ public interface GreetingWorkflow {
+
+ /**
+ * This is the method that is executed when the Workflow Execution is started. The Workflow
+ * Execution completes when this method finishes execution.
+ */
+ @WorkflowMethod
+ String getGreeting(String name);
+ }
+
+ /**
+ * This is the Activity Definition's Interface. Activities are building blocks of any Temporal
+ * Workflow and contain any business logic that could perform long running computation, network
+ * calls, etc.
+ *
+ * Annotating Activity Definition methods with @ActivityMethod is optional.
+ *
+ * @see ActivityInterface
+ * @see ActivityMethod
+ */
+ @ActivityInterface
+ public interface GreetingActivities {
+
+ /** Define your activity method which can be called during workflow execution */
+ String composeGreeting(String greeting, String name);
+ }
+
+ // Define the workflow implementation which implements our getGreeting workflow method.
+ public static class GreetingWorkflowImpl implements GreetingWorkflow {
+
+ /**
+ * Define the GreetingActivities stub. Activity stubs are proxies for activity invocations that
+ * are executed outside of the workflow thread on the activity worker, that can be on a
+ * different host. Temporal is going to dispatch the activity results back to the workflow and
+ * unblock the stub as soon as activity is completed on the activity worker.
+ */
+ private final GreetingActivities activities =
+ Workflow.newActivityStub(
+ GreetingActivities.class,
+ ActivityOptions.newBuilder().setStartToCloseTimeout(Duration.ofSeconds(10)).build());
+
+ @Override
+ public String getGreeting(String name) {
+ // This is a blocking call that returns only after activity is completed.
+ return activities.composeGreeting("Hello", name);
+ }
+ }
+
+ /**
+ * Implementation of our workflow activity interface. It overwrites our defined composeGreeting
+ * activity method.
+ */
+ static class GreetingActivitiesImpl implements GreetingActivities {
+ private int callCount;
+
+ /**
+ * Our activity implementation simulates a failure 3 times. Given our previously set
+ * RetryOptions, our workflow is going to retry our activity execution.
+ */
+ @Override
+ @BenignExceptionTypes({IllegalStateException.class})
+ public synchronized String composeGreeting(String greeting, String name) {
+ if (++callCount < 4) {
+ System.out.println("composeGreeting activity is going to fail");
+ throw new IllegalStateException("not yet");
+ }
+
+ // after 3 unsuccessful retries we finally can complete our activity execution
+ System.out.println("composeGreeting activity is going to complete");
+ return greeting + " " + name + "!";
+ }
+ }
+
+ /**
+ * With our Workflow and Activities defined, we can now start execution. The main method starts
+ * the worker and then the workflow.
+ */
+ public static void main(String[] args) {
+
+ // Get a Workflow service stub.
+ WorkflowServiceStubs service = WorkflowServiceStubs.newLocalServiceStubs();
+
+ /*
+ * Get a Workflow service client which can be used to start, Signal, and Query Workflow Executions.
+ */
+ WorkflowClient client = WorkflowClient.newInstance(service);
+
+ /*
+ * Define the workflow factory. It is used to create workflow workers for a specific task queue.
+ */
+ WorkerFactory factory =
+ WorkerFactory.newInstance(
+ client,
+ WorkerFactoryOptions.newBuilder()
+ .setWorkerInterceptors(new BenignExceptionTypesAnnotationInterceptor())
+ .build());
+
+ /*
+ * Define the workflow worker. Workflow workers listen to a defined task queue and process
+ * workflows and activities.
+ */
+ Worker worker = factory.newWorker(TASK_QUEUE);
+
+ /*
+ * Register our workflow implementation with the worker.
+ * Workflow implementations must be known to the worker at runtime in
+ * order to dispatch workflow tasks.
+ */
+ worker.registerWorkflowImplementationTypes(GreetingWorkflowImpl.class);
+
+ /*
+ * Register our Activity Types with the Worker. Since Activities are stateless and thread-safe,
+ * the Activity Type is a shared instance.
+ */
+ worker.registerActivitiesImplementations(new GreetingActivitiesImpl());
+
+ /*
+ * Start all the workers registered for a specific task queue.
+ * The started workers then start polling for workflows and activities.
+ */
+ factory.start();
+
+ // Set our workflow options
+ WorkflowOptions workflowOptions =
+ WorkflowOptions.newBuilder().setWorkflowId(WORKFLOW_ID).setTaskQueue(TASK_QUEUE).build();
+
+ // Create the workflow client stub. It is used to start our workflow execution.
+ GreetingWorkflow workflow = client.newWorkflowStub(GreetingWorkflow.class, workflowOptions);
+
+ /*
+ * Execute our workflow and wait for it to complete. The call to our getGreeting method is
+ * synchronous.
+ *
+ * See {@link io.temporal.samples.hello.HelloSignal} for an example of starting workflow
+ * without waiting synchronously for its result.
+ */
+ String greeting = workflow.getGreeting("World");
+
+ // Display workflow execution results
+ System.out.println(greeting);
+ System.exit(0);
+ }
+}
diff --git a/core/src/main/java/io/temporal/samples/customannotation/README.md b/core/src/main/java/io/temporal/samples/customannotation/README.md
new file mode 100644
index 000000000..f04b25915
--- /dev/null
+++ b/core/src/main/java/io/temporal/samples/customannotation/README.md
@@ -0,0 +1,9 @@
+# Custom annotation
+
+The sample demonstrates how to create a custom annotation using an interceptor. In this case the annotation allows specifying an exception of a certain type is benign.
+
+This samples shows a custom annotation on an activity method, but the same approach can be used for workflow methods or Nexus operations.
+
+```bash
+./gradlew -q execute -PmainClass=io.temporal.samples.customannotation.CustomAnnotation
+```