diff --git a/temporal-sdk/src/main/java/io/temporal/activity/ActivityOptions.java b/temporal-sdk/src/main/java/io/temporal/activity/ActivityOptions.java index fb498e28b9..95856a8dea 100644 --- a/temporal-sdk/src/main/java/io/temporal/activity/ActivityOptions.java +++ b/temporal-sdk/src/main/java/io/temporal/activity/ActivityOptions.java @@ -243,6 +243,7 @@ public Builder setVersioningIntent(VersioningIntent versioningIntent) { return this; } + /** Merge options with override parameter having higher precedence. */ public Builder mergeActivityOptions(ActivityOptions override) { if (override == null) { return this; diff --git a/temporal-sdk/src/main/java/io/temporal/internal/common/ActivityOptionUtils.java b/temporal-sdk/src/main/java/io/temporal/internal/common/ActivityOptionUtils.java deleted file mode 100644 index d62854563a..0000000000 --- a/temporal-sdk/src/main/java/io/temporal/internal/common/ActivityOptionUtils.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (C) 2022 Temporal Technologies, Inc. All Rights Reserved. - * - * Copyright (C) 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 material except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License 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.internal.common; - -import io.temporal.activity.ActivityOptions; -import io.temporal.activity.LocalActivityOptions; -import java.util.Map; -import javax.annotation.Nonnull; - -public class ActivityOptionUtils { - public static void mergePredefinedActivityOptions( - @Nonnull Map mergeTo, - @Nonnull Map override) { - override.forEach( - (key, value) -> - mergeTo.merge(key, value, (o1, o2) -> o1.toBuilder().mergeActivityOptions(o2).build())); - } - - public static void mergePredefinedLocalActivityOptions( - @Nonnull Map mergeTo, - @Nonnull Map override) { - override.forEach( - (key, value) -> - mergeTo.merge(key, value, (o1, o2) -> o1.toBuilder().mergeActivityOptions(o2).build())); - } -} diff --git a/temporal-sdk/src/main/java/io/temporal/internal/common/MergedActivityOptions.java b/temporal-sdk/src/main/java/io/temporal/internal/common/MergedActivityOptions.java new file mode 100644 index 0000000000..bb1df403a5 --- /dev/null +++ b/temporal-sdk/src/main/java/io/temporal/internal/common/MergedActivityOptions.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2022 Temporal Technologies, Inc. All Rights Reserved. + * + * Copyright (C) 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 material except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License 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.internal.common; + +import io.temporal.activity.ActivityOptions; +import java.util.HashMap; +import java.util.Map; + +/** + * The chain of ActivityOptions and per type options maps. Used to merge options specified at the + * following layers: + * + *
+ *     * WorkflowImplementationOptions
+ *     * Workflow
+ *     * ActivityStub
+ * 
+ * + * Each next layer overrides specific options specified at the previous layer. + */ +public final class MergedActivityOptions { + + /** Common options across all activity types. */ + private ActivityOptions defaultOptions; + + /** Per activity type options. These override defaultOptions. */ + private final Map optionsMap = new HashMap<>(); + + /** The options specified at the previous layer. They are overriden by this object. */ + private final MergedActivityOptions overridden; + + public MergedActivityOptions( + MergedActivityOptions overridden, + ActivityOptions defaultOptions, + Map optionsMap) { + this.overridden = overridden; + this.defaultOptions = defaultOptions; + if (optionsMap != null) { + this.optionsMap.putAll(optionsMap); + } + } + + public MergedActivityOptions(MergedActivityOptions overridden) { + this.overridden = overridden; + defaultOptions = null; + } + + public void setDefaultOptions(ActivityOptions defaultOptions) { + this.defaultOptions = defaultOptions; + } + + public void applyOptionsMap(Map optionsMap) { + if (optionsMap != null) { + this.optionsMap.putAll(optionsMap); + } + } + + /** Get merged options for the given activityType. */ + public ActivityOptions getMergedOptions(String activityType) { + ActivityOptions overrideOptions = null; + if (overridden != null) { + overrideOptions = overridden.getMergedOptions(activityType); + } + return merge(overrideOptions, defaultOptions, optionsMap.get(activityType)); + } + + /** later options override the previous ones */ + private static ActivityOptions merge(ActivityOptions... options) { + if (options == null || options.length == 0) { + return null; + } + ActivityOptions result = options[0]; + for (int i = 1; i < options.length; i++) { + if (result == null) { + result = options[i]; + } else { + result = result.toBuilder().mergeActivityOptions(options[i]).build(); + } + } + return result; + } +} diff --git a/temporal-sdk/src/main/java/io/temporal/internal/common/MergedLocalActivityOptions.java b/temporal-sdk/src/main/java/io/temporal/internal/common/MergedLocalActivityOptions.java new file mode 100644 index 0000000000..09963114ca --- /dev/null +++ b/temporal-sdk/src/main/java/io/temporal/internal/common/MergedLocalActivityOptions.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2022 Temporal Technologies, Inc. All Rights Reserved. + * + * Copyright (C) 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 material except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License 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.internal.common; + +import io.temporal.activity.LocalActivityOptions; +import java.util.HashMap; +import java.util.Map; + +/** + * The chain of LocalActivityOptions and per type options maps. Used to merge options specified at + * the following layers: + * + *
+ *     * WorkflowImplementationOptions
+ *     * Workflow
+ *     * ActivityStub
+ * 
+ * + * Each next layer overrides specific options specified at the previous layer. + */ +public final class MergedLocalActivityOptions { + + /** Common options across all activity types. */ + private LocalActivityOptions defaultOptions; + + /** Per activity type options. These override defaultOptions. */ + private final Map optionsMap = new HashMap<>(); + + /** The options specified at the previous layer. They are overriden by this object. */ + private final MergedLocalActivityOptions overridden; + + public MergedLocalActivityOptions( + MergedLocalActivityOptions overridden, + LocalActivityOptions defaultOptions, + Map optionsMap) { + this.overridden = overridden; + this.defaultOptions = defaultOptions; + if (optionsMap != null) { + this.optionsMap.putAll(optionsMap); + } + } + + public MergedLocalActivityOptions(MergedLocalActivityOptions overridden) { + this.overridden = overridden; + defaultOptions = null; + } + + public void setDefaultOptions(LocalActivityOptions defaultOptions) { + this.defaultOptions = defaultOptions; + } + + public void applyOptionsMap(Map optionsMap) { + if (optionsMap != null) { + this.optionsMap.putAll(optionsMap); + } + } + + /** Get merged options for the given activityType. */ + public LocalActivityOptions getMergedOptions(String activityType) { + LocalActivityOptions overrideOptions = null; + if (overridden != null) { + overrideOptions = overridden.getMergedOptions(activityType); + } + return merge(overrideOptions, defaultOptions, optionsMap.get(activityType)); + } + + /** later options override the previous ones */ + private static LocalActivityOptions merge(LocalActivityOptions... options) { + if (options == null || options.length == 0) { + return null; + } + LocalActivityOptions result = options[0]; + for (int i = 1; i < options.length; i++) { + if (result == null) { + result = options[i]; + } else { + result = result.toBuilder().mergeActivityOptions(options[i]).build(); + } + } + return result; + } +} diff --git a/temporal-sdk/src/main/java/io/temporal/internal/sync/ActivityInvocationHandler.java b/temporal-sdk/src/main/java/io/temporal/internal/sync/ActivityInvocationHandler.java index 18ef07f8c2..931fa04429 100644 --- a/temporal-sdk/src/main/java/io/temporal/internal/sync/ActivityInvocationHandler.java +++ b/temporal-sdk/src/main/java/io/temporal/internal/sync/ActivityInvocationHandler.java @@ -24,41 +24,36 @@ import io.temporal.activity.ActivityOptions; import io.temporal.common.MethodRetry; import io.temporal.common.interceptors.WorkflowOutboundCallsInterceptor; +import io.temporal.internal.common.MergedActivityOptions; import io.temporal.workflow.ActivityStub; import io.temporal.workflow.Functions; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; -import java.util.HashMap; -import java.util.Map; import java.util.function.Function; @VisibleForTesting public class ActivityInvocationHandler extends ActivityInvocationHandlerBase { - private final ActivityOptions options; - private final Map activityMethodOptions; + private final MergedActivityOptions options; private final WorkflowOutboundCallsInterceptor activityExecutor; private final Functions.Proc assertReadOnly; @VisibleForTesting public static InvocationHandler newInstance( Class activityInterface, - ActivityOptions options, - Map methodOptions, + MergedActivityOptions options, WorkflowOutboundCallsInterceptor activityExecutor, Functions.Proc assertReadOnly) { return new ActivityInvocationHandler( - activityInterface, activityExecutor, options, methodOptions, assertReadOnly); + activityInterface, activityExecutor, options, assertReadOnly); } private ActivityInvocationHandler( Class activityInterface, WorkflowOutboundCallsInterceptor activityExecutor, - ActivityOptions options, - Map methodOptions, + MergedActivityOptions options, Functions.Proc assertReadOnly) { super(activityInterface); this.options = options; - this.activityMethodOptions = (methodOptions == null) ? new HashMap<>() : methodOptions; this.activityExecutor = activityExecutor; this.assertReadOnly = assertReadOnly; } @@ -67,11 +62,7 @@ private ActivityInvocationHandler( protected Function getActivityFunc( Method method, MethodRetry methodRetry, String activityName) { Function function; - ActivityOptions merged = - ActivityOptions.newBuilder(options) - .mergeActivityOptions(this.activityMethodOptions.get(activityName)) - .mergeMethodRetry(methodRetry) - .build(); + ActivityOptions merged = options.getMergedOptions(activityName); if (merged.getStartToCloseTimeout() == null && merged.getScheduleToCloseTimeout() == null) { throw new IllegalArgumentException( "Both StartToCloseTimeout and ScheduleToCloseTimeout aren't specified for " diff --git a/temporal-sdk/src/main/java/io/temporal/internal/sync/LocalActivityInvocationHandler.java b/temporal-sdk/src/main/java/io/temporal/internal/sync/LocalActivityInvocationHandler.java index 6d12850f8c..3942ebfe7c 100644 --- a/temporal-sdk/src/main/java/io/temporal/internal/sync/LocalActivityInvocationHandler.java +++ b/temporal-sdk/src/main/java/io/temporal/internal/sync/LocalActivityInvocationHandler.java @@ -24,41 +24,36 @@ import io.temporal.activity.LocalActivityOptions; import io.temporal.common.MethodRetry; import io.temporal.common.interceptors.WorkflowOutboundCallsInterceptor; +import io.temporal.internal.common.MergedLocalActivityOptions; import io.temporal.workflow.ActivityStub; import io.temporal.workflow.Functions; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; -import java.util.HashMap; -import java.util.Map; import java.util.function.Function; @VisibleForTesting public class LocalActivityInvocationHandler extends ActivityInvocationHandlerBase { - private final LocalActivityOptions options; - private final Map activityMethodOptions; + private final MergedLocalActivityOptions options; private final WorkflowOutboundCallsInterceptor activityExecutor; private final Functions.Proc assertReadOnly; @VisibleForTesting public static InvocationHandler newInstance( Class activityInterface, - LocalActivityOptions options, - Map methodOptions, + MergedLocalActivityOptions options, WorkflowOutboundCallsInterceptor activityExecutor, Functions.Proc assertReadOnly) { return new LocalActivityInvocationHandler( - activityInterface, activityExecutor, options, methodOptions, assertReadOnly); + activityInterface, activityExecutor, options, assertReadOnly); } private LocalActivityInvocationHandler( Class activityInterface, WorkflowOutboundCallsInterceptor activityExecutor, - LocalActivityOptions options, - Map methodOptions, + MergedLocalActivityOptions options, Functions.Proc assertReadOnly) { super(activityInterface); this.options = options; - this.activityMethodOptions = (methodOptions == null) ? new HashMap<>() : methodOptions; this.activityExecutor = activityExecutor; this.assertReadOnly = assertReadOnly; } @@ -68,11 +63,7 @@ private LocalActivityInvocationHandler( public Function getActivityFunc( Method method, MethodRetry methodRetry, String activityName) { Function function; - LocalActivityOptions mergedOptions = - LocalActivityOptions.newBuilder(options) - .mergeActivityOptions(activityMethodOptions.get(activityName)) - .setMethodRetry(methodRetry) - .build(); + LocalActivityOptions mergedOptions = options.getMergedOptions(activityName); ActivityStub stub = LocalActivityStubImpl.newInstance(mergedOptions, activityExecutor, assertReadOnly); function = diff --git a/temporal-sdk/src/main/java/io/temporal/internal/sync/SyncWorkflowContext.java b/temporal-sdk/src/main/java/io/temporal/internal/sync/SyncWorkflowContext.java index 78a3b714ba..83225b8ded 100644 --- a/temporal-sdk/src/main/java/io/temporal/internal/sync/SyncWorkflowContext.java +++ b/temporal-sdk/src/main/java/io/temporal/internal/sync/SyncWorkflowContext.java @@ -55,8 +55,9 @@ import io.temporal.common.interceptors.WorkflowInboundCallsInterceptor; import io.temporal.common.interceptors.WorkflowOutboundCallsInterceptor; import io.temporal.failure.*; -import io.temporal.internal.common.ActivityOptionUtils; import io.temporal.internal.common.HeaderUtils; +import io.temporal.internal.common.MergedActivityOptions; +import io.temporal.internal.common.MergedLocalActivityOptions; import io.temporal.internal.common.OptionsUtils; import io.temporal.internal.common.ProtobufTimeUtils; import io.temporal.internal.common.SdkFlag; @@ -119,10 +120,9 @@ final class SyncWorkflowContext implements WorkflowContext, WorkflowOutboundCall private WorkflowInboundCallsInterceptor headInboundInterceptor; private WorkflowOutboundCallsInterceptor headOutboundInterceptor; - private ActivityOptions defaultActivityOptions = null; - private Map activityOptionsMap; - private LocalActivityOptions defaultLocalActivityOptions = null; - private Map localActivityOptionsMap; + private final MergedActivityOptions activityOptions; + private final MergedLocalActivityOptions localActivityOptions; + private boolean readOnly = false; public SyncWorkflowContext( @@ -144,14 +144,22 @@ public SyncWorkflowContext( this.signalDispatcher = signalDispatcher; this.queryDispatcher = queryDispatcher; this.updateDispatcher = updateDispatcher; + MergedActivityOptions activityOptions = null; + MergedLocalActivityOptions localActivityOptions = null; if (workflowImplementationOptions != null) { - this.defaultActivityOptions = workflowImplementationOptions.getDefaultActivityOptions(); - this.activityOptionsMap = new HashMap<>(workflowImplementationOptions.getActivityOptions()); - this.defaultLocalActivityOptions = - workflowImplementationOptions.getDefaultLocalActivityOptions(); - this.localActivityOptionsMap = - new HashMap<>(workflowImplementationOptions.getLocalActivityOptions()); + activityOptions = + new MergedActivityOptions( + null, + workflowImplementationOptions.getDefaultActivityOptions(), + workflowImplementationOptions.getActivityOptions()); + localActivityOptions = + new MergedLocalActivityOptions( + null, + workflowImplementationOptions.getDefaultLocalActivityOptions(), + workflowImplementationOptions.getLocalActivityOptions()); } + this.activityOptions = new MergedActivityOptions(activityOptions); + this.localActivityOptions = new MergedLocalActivityOptions(localActivityOptions); this.workflowImplementationOptions = workflowImplementationOptions == null ? WorkflowImplementationOptions.getDefaultInstance() @@ -200,61 +208,28 @@ public void initHeadInboundCallsInterceptor(WorkflowInboundCallsInterceptor head updateDispatcher.setInboundCallsInterceptor(head); } - public ActivityOptions getDefaultActivityOptions() { - return defaultActivityOptions; - } - - public @Nonnull Map getActivityOptions() { - return activityOptionsMap != null - ? Collections.unmodifiableMap(activityOptionsMap) - : Collections.emptyMap(); + public MergedActivityOptions getActivityOptions() { + return activityOptions; } - public LocalActivityOptions getDefaultLocalActivityOptions() { - return defaultLocalActivityOptions; - } - - public @Nonnull Map getLocalActivityOptions() { - return localActivityOptionsMap != null - ? Collections.unmodifiableMap(localActivityOptionsMap) - : Collections.emptyMap(); + public MergedLocalActivityOptions getLocalActivityOptions() { + return localActivityOptions; } public void setDefaultActivityOptions(ActivityOptions defaultActivityOptions) { - this.defaultActivityOptions = - (this.defaultActivityOptions == null) - ? defaultActivityOptions - : this.defaultActivityOptions.toBuilder() - .mergeActivityOptions(defaultActivityOptions) - .build(); + activityOptions.setDefaultOptions(defaultActivityOptions); } public void applyActivityOptions(Map activityTypeToOption) { - Objects.requireNonNull(activityTypeToOption); - if (this.activityOptionsMap == null) { - this.activityOptionsMap = new HashMap<>(activityTypeToOption); - return; - } - ActivityOptionUtils.mergePredefinedActivityOptions(activityOptionsMap, activityTypeToOption); + activityOptions.applyOptionsMap(activityTypeToOption); } public void setDefaultLocalActivityOptions(LocalActivityOptions defaultLocalActivityOptions) { - this.defaultLocalActivityOptions = - (this.defaultLocalActivityOptions == null) - ? defaultLocalActivityOptions - : this.defaultLocalActivityOptions.toBuilder() - .mergeActivityOptions(defaultLocalActivityOptions) - .build(); + localActivityOptions.setDefaultOptions(defaultLocalActivityOptions); } public void applyLocalActivityOptions(Map activityTypeToOption) { - Objects.requireNonNull(activityTypeToOption); - if (this.localActivityOptionsMap == null) { - this.localActivityOptionsMap = new HashMap<>(activityTypeToOption); - return; - } - ActivityOptionUtils.mergePredefinedLocalActivityOptions( - localActivityOptionsMap, activityTypeToOption); + localActivityOptions.applyOptionsMap(activityTypeToOption); } @Override diff --git a/temporal-sdk/src/main/java/io/temporal/internal/sync/WorkflowInternal.java b/temporal-sdk/src/main/java/io/temporal/internal/sync/WorkflowInternal.java index 03c4da2b2f..0898d303aa 100644 --- a/temporal-sdk/src/main/java/io/temporal/internal/sync/WorkflowInternal.java +++ b/temporal-sdk/src/main/java/io/temporal/internal/sync/WorkflowInternal.java @@ -24,7 +24,6 @@ import static io.temporal.internal.sync.DeterministicRunnerImpl.currentThreadInternal; import com.google.common.base.Joiner; -import com.google.common.base.MoreObjects; import com.google.common.base.Preconditions; import com.uber.m3.tally.Scope; import io.temporal.activity.ActivityOptions; @@ -41,7 +40,8 @@ import io.temporal.common.metadata.POJOWorkflowInterfaceMetadata; import io.temporal.common.metadata.POJOWorkflowMethodMetadata; import io.temporal.internal.WorkflowThreadMarker; -import io.temporal.internal.common.ActivityOptionUtils; +import io.temporal.internal.common.MergedActivityOptions; +import io.temporal.internal.common.MergedLocalActivityOptions; import io.temporal.internal.common.NonIdempotentHandle; import io.temporal.internal.common.SearchAttributesUtil; import io.temporal.internal.logging.ReplayAwareLogger; @@ -300,29 +300,13 @@ public static T newActivityStub( // Merge the activity options we may have received from the workflow with the options we may // have received in WorkflowImplementationOptions. SyncWorkflowContext context = getRootWorkflowContext(); - options = (options == null) ? context.getDefaultActivityOptions() : options; - - Map mergedActivityOptionsMap; - @Nonnull Map predefinedActivityOptions = context.getActivityOptions(); - if (activityMethodOptions != null - && !activityMethodOptions.isEmpty() - && predefinedActivityOptions.isEmpty()) { - // we need to merge only in this case - mergedActivityOptionsMap = new HashMap<>(predefinedActivityOptions); - ActivityOptionUtils.mergePredefinedActivityOptions( - mergedActivityOptionsMap, activityMethodOptions); - } else { - mergedActivityOptionsMap = - MoreObjects.firstNonNull( - activityMethodOptions, - MoreObjects.firstNonNull(predefinedActivityOptions, Collections.emptyMap())); - } + MergedActivityOptions activityOptions = + new MergedActivityOptions(context.getActivityOptions(), options, activityMethodOptions); InvocationHandler invocationHandler = ActivityInvocationHandler.newInstance( activityInterface, - options, - mergedActivityOptionsMap, + activityOptions, context.getWorkflowOutboundInterceptor(), () -> assertNotReadOnly("schedule activity")); return ActivityInvocationHandlerBase.newProxy(activityInterface, invocationHandler); @@ -343,31 +327,14 @@ public static T newLocalActivityStub( // Merge the activity options we may have received from the workflow with the options we may // have received in WorkflowImplementationOptions. SyncWorkflowContext context = getRootWorkflowContext(); - options = (options == null) ? context.getDefaultLocalActivityOptions() : options; - - Map mergedLocalActivityOptionsMap; - @Nonnull - Map predefinedLocalActivityOptions = - context.getLocalActivityOptions(); - if (activityMethodOptions != null - && !activityMethodOptions.isEmpty() - && predefinedLocalActivityOptions.isEmpty()) { - // we need to merge only in this case - mergedLocalActivityOptionsMap = new HashMap<>(predefinedLocalActivityOptions); - ActivityOptionUtils.mergePredefinedLocalActivityOptions( - mergedLocalActivityOptionsMap, activityMethodOptions); - } else { - mergedLocalActivityOptionsMap = - MoreObjects.firstNonNull( - activityMethodOptions, - MoreObjects.firstNonNull(predefinedLocalActivityOptions, Collections.emptyMap())); - } + MergedLocalActivityOptions activityOptions = + new MergedLocalActivityOptions( + context.getLocalActivityOptions(), options, activityMethodOptions); InvocationHandler invocationHandler = LocalActivityInvocationHandler.newInstance( activityInterface, - options, - mergedLocalActivityOptionsMap, + activityOptions, WorkflowInternal.getWorkflowOutboundInterceptor(), () -> assertNotReadOnly("schedule local activity")); return ActivityInvocationHandlerBase.newProxy(activityInterface, invocationHandler); diff --git a/temporal-sdk/src/test/java/io/temporal/activity/ActivityOptionsPrecedenceTest.java b/temporal-sdk/src/test/java/io/temporal/activity/ActivityOptionsPrecedenceTest.java new file mode 100644 index 0000000000..8d575fa3c0 --- /dev/null +++ b/temporal-sdk/src/test/java/io/temporal/activity/ActivityOptionsPrecedenceTest.java @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2022 Temporal Technologies, Inc. All Rights Reserved. + * + * Copyright (C) 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 material except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License 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.activity; + +import static org.junit.Assert.assertEquals; + +import io.temporal.client.WorkflowOptions; +import io.temporal.testing.internal.SDKTestWorkflowRule; +import io.temporal.worker.WorkflowImplementationOptions; +import io.temporal.workflow.Workflow; +import io.temporal.workflow.shared.TestActivities.TestActivity; +import io.temporal.workflow.shared.TestActivities.TestActivityImpl; +import io.temporal.workflow.shared.TestWorkflows.TestWorkflowReturnMap; +import java.time.Duration; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import org.junit.Rule; +import org.junit.Test; + +public class ActivityOptionsPrecedenceTest { + + private static final ActivityOptions implOptions = + ActivityOptions.newBuilder() + .setHeartbeatTimeout(Duration.ofSeconds(10)) + .setStartToCloseTimeout(Duration.ofHours(10)) + .setScheduleToCloseTimeout(Duration.ofDays(10)) + .build(); + + private static final Map implOptionsMap = + Collections.singletonMap( + "Activity1", + ActivityOptions.newBuilder().setHeartbeatTimeout(Duration.ofSeconds(11)).build()); + + private static final ActivityOptions workflowOps = + ActivityOptions.newBuilder() + .setHeartbeatTimeout(Duration.ofSeconds(20)) + .setStartToCloseTimeout(Duration.ofHours(20)) + .build(); + + private static final Map workflowOptionsMap = + Collections.singletonMap( + "Activity1", + ActivityOptions.newBuilder().setHeartbeatTimeout(Duration.ofSeconds(22)).build()); + + private static final ActivityOptions stubOptions = + ActivityOptions.newBuilder().setHeartbeatTimeout(Duration.ofSeconds(30)).build(); + + private static final Map stubOptionsMap = + Collections.singletonMap( + "Activity2", + ActivityOptions.newBuilder().setHeartbeatTimeout(Duration.ofSeconds(33)).build()); + + @Rule + public SDKTestWorkflowRule testWorkflowRule = + SDKTestWorkflowRule.newBuilder() + .setWorkflowTypes( + WorkflowImplementationOptions.newBuilder() + .setDefaultActivityOptions(implOptions) + .setActivityOptions(implOptionsMap) + .build(), + TestSetDefaultActivityOptionsWorkflowImpl.class) + .setActivityImplementations(new TestActivityImpl()) + .build(); + + @Test + public void testSetWorkflowImplementationOptions() { + TestWorkflowReturnMap workflowStub = + testWorkflowRule + .getWorkflowClient() + .newWorkflowStub( + TestWorkflowReturnMap.class, + WorkflowOptions.newBuilder().setTaskQueue(testWorkflowRule.getTaskQueue()).build()); + Map> result = workflowStub.execute(); + + Map activity1Values = result.get("Activity1"); + Duration a1Heartbeat = activity1Values.get("HeartbeatTimeout"); + Duration a1StartToClose = activity1Values.get("StartToCloseTimeout"); + Duration a1ScheduleToClose = activity1Values.get("ScheduleToCloseTimeout"); + + assertEquals(stubOptions.getHeartbeatTimeout(), a1Heartbeat); + assertEquals(workflowOps.getStartToCloseTimeout(), a1StartToClose); + assertEquals(implOptions.getScheduleToCloseTimeout(), a1ScheduleToClose); + + Map activity2Values = result.get("Activity2"); + Duration a2Heartbeat = activity2Values.get("HeartbeatTimeout"); + Duration a2StartToClose = activity2Values.get("StartToCloseTimeout"); + Duration a2ScheduleToClose = activity2Values.get("ScheduleToCloseTimeout"); + + assertEquals(stubOptionsMap.get("Activity2").getHeartbeatTimeout(), a2Heartbeat); + assertEquals(workflowOps.getStartToCloseTimeout(), a2StartToClose); + assertEquals(implOptions.getScheduleToCloseTimeout(), a2ScheduleToClose); + } + + public static class TestSetDefaultActivityOptionsWorkflowImpl implements TestWorkflowReturnMap { + @Override + public Map> execute() { + Workflow.setDefaultActivityOptions(workflowOps); + Workflow.applyActivityOptions(workflowOptionsMap); + TestActivity activities = + Workflow.newActivityStub(TestActivity.class, stubOptions, stubOptionsMap); + + Map> result = new HashMap<>(); + result.put("Activity1", activities.activity1()); + result.put("Activity2", activities.activity2()); + return result; + } + } +} diff --git a/temporal-sdk/src/test/java/io/temporal/activity/ActivityOptionsTest.java b/temporal-sdk/src/test/java/io/temporal/activity/ActivityOptionsTest.java index 7bd9d87603..e0fc5d2e33 100644 --- a/temporal-sdk/src/test/java/io/temporal/activity/ActivityOptionsTest.java +++ b/temporal-sdk/src/test/java/io/temporal/activity/ActivityOptionsTest.java @@ -27,11 +27,13 @@ import io.temporal.common.MethodRetry; import io.temporal.common.RetryOptions; +import io.temporal.internal.common.MergedActivityOptions; import io.temporal.testing.TestActivityEnvironment; import io.temporal.workflow.shared.TestActivities.TestActivity; import io.temporal.workflow.shared.TestActivities.TestActivityImpl; import java.lang.reflect.Method; import java.time.Duration; +import java.util.HashMap; import java.util.Map; import org.junit.*; import org.junit.rules.Timeout; @@ -85,6 +87,76 @@ public void testActivityOptionsMerge() { Assert.assertEquals(methodOps1, merged); } + @Test + public void testMergedActivityOptions() { + MergedActivityOptions options0 = new MergedActivityOptions(null, null, null); + + ActivityOptions a1 = + ActivityOptions.newBuilder() + .setScheduleToStartTimeout(Duration.ofMillis(10)) + .setScheduleToCloseTimeout(Duration.ofDays(10)) + .setStartToCloseTimeout(Duration.ofSeconds(10)) + .setRetryOptions(RetryOptions.newBuilder().setMaximumAttempts(10).build()) + .setCancellationType(ActivityCancellationType.TRY_CANCEL) + .build(); + + Map map1 = new HashMap<>(); + map1.put( + "Activity1", + ActivityOptions.newBuilder() + .setScheduleToStartTimeout(Duration.ofMillis(11)) + .setScheduleToCloseTimeout(Duration.ofDays(11)) + .setStartToCloseTimeout(Duration.ofSeconds(11)) + .setRetryOptions(RetryOptions.newBuilder().setMaximumAttempts(11).build()) + .setCancellationType(ActivityCancellationType.WAIT_CANCELLATION_COMPLETED) + .build()); + map1.put( + "Activity2", + ActivityOptions.newBuilder() + .setScheduleToStartTimeout(Duration.ofMillis(12)) + .setStartToCloseTimeout(Duration.ofSeconds(12)) + .setRetryOptions(RetryOptions.newBuilder().setMaximumAttempts(12).build()) + .setCancellationType(ActivityCancellationType.ABANDON) + .build()); + + MergedActivityOptions options1 = new MergedActivityOptions(options0, a1, map1); + + ActivityOptions a2 = + ActivityOptions.newBuilder() + .setScheduleToStartTimeout(Duration.ofMillis(20)) + .setRetryOptions(RetryOptions.newBuilder().setMaximumAttempts(20).build()) + .setCancellationType(ActivityCancellationType.TRY_CANCEL) + .build(); + + MergedActivityOptions options2 = new MergedActivityOptions(options1, a2, null); + + Map map3 = new HashMap<>(); + map3.put( + "Activity1", + ActivityOptions.newBuilder() + .setScheduleToStartTimeout(Duration.ofMillis(31)) + .setScheduleToCloseTimeout(Duration.ofDays(31)) + .setStartToCloseTimeout(Duration.ofSeconds(31)) + .setRetryOptions(RetryOptions.newBuilder().setMaximumAttempts(31).build()) + .build()); + + MergedActivityOptions options3 = new MergedActivityOptions(options2, null, map3); + + // From a1 + Assert.assertEquals( + Duration.ofDays(10), options3.getMergedOptions("Activity2").getScheduleToCloseTimeout()); + // From map1 + Assert.assertEquals( + Duration.ofSeconds(12), options3.getMergedOptions("Activity2").getStartToCloseTimeout()); + // From a2 + assertEquals( + ActivityCancellationType.TRY_CANCEL, + options3.getMergedOptions("Activity2").getCancellationType()); + // From map3 + assertEquals( + Duration.ofMillis(31), options3.getMergedOptions("Activity1").getScheduleToStartTimeout()); + } + @Test public void testActivityOptionsDefaultInstance() { testEnv.registerActivitiesImplementations(new TestActivityImpl()); @@ -101,7 +173,6 @@ public void testActivityOptionsDefaultInstance() { @Test public void testOnlyAnnotationsPresent() throws NoSuchMethodException { Method method = ActivityOptionsTest.class.getMethod("activityAndRetryOptions"); - ActivityMethod a = method.getAnnotation(ActivityMethod.class); MethodRetry r = method.getAnnotation(MethodRetry.class); ActivityOptions o = ActivityOptions.newBuilder().build(); ActivityOptions merged = ActivityOptions.newBuilder(o).mergeMethodRetry(r).build(); diff --git a/temporal-sdk/src/test/java/io/temporal/activity/DefaultActivityOptionsSetOnWorkflowTest.java b/temporal-sdk/src/test/java/io/temporal/activity/DefaultActivityOptionsSetOnWorkflowTest.java deleted file mode 100644 index b8b07cd630..0000000000 --- a/temporal-sdk/src/test/java/io/temporal/activity/DefaultActivityOptionsSetOnWorkflowTest.java +++ /dev/null @@ -1,157 +0,0 @@ -/* - * Copyright (C) 2022 Temporal Technologies, Inc. All Rights Reserved. - * - * Copyright (C) 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 material except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License 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.activity; - -import static org.junit.Assert.assertEquals; - -import io.temporal.client.WorkflowOptions; -import io.temporal.testing.internal.SDKTestOptions; -import io.temporal.testing.internal.SDKTestWorkflowRule; -import io.temporal.worker.WorkflowImplementationOptions; -import io.temporal.workflow.Workflow; -import io.temporal.workflow.shared.TestActivities.TestActivity; -import io.temporal.workflow.shared.TestActivities.TestActivityImpl; -import io.temporal.workflow.shared.TestActivities.TestLocalActivity; -import io.temporal.workflow.shared.TestActivities.TestLocalActivityImpl; -import io.temporal.workflow.shared.TestWorkflows.TestWorkflowReturnMap; -import java.time.Duration; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import org.junit.Rule; -import org.junit.Test; - -public class DefaultActivityOptionsSetOnWorkflowTest { - - private static final ActivityOptions workflowOps = ActivityTestOptions.newActivityOptions1(); - private static final ActivityOptions workerOps = ActivityTestOptions.newActivityOptions2(); - private static final ActivityOptions activity2Ops = - SDKTestOptions.newActivityOptions20sScheduleToClose(); - private static final Map activity2options = - Collections.singletonMap("Activity2", activity2Ops); - private static final Map defaultActivity2options = - Collections.singletonMap( - "Activity2", - ActivityOptions.newBuilder().setHeartbeatTimeout(Duration.ofSeconds(2)).build()); - - // local activity options - private static final LocalActivityOptions localActivityWorkflowOps = - ActivityTestOptions.newLocalActivityOptions1(); - private static final LocalActivityOptions localActivityWorkerOps = - ActivityTestOptions.newLocalActivityOptions2(); - private static final LocalActivityOptions localActivity2Ops = - SDKTestOptions.newLocalActivityOptions20sScheduleToClose(); - private static final Map localActivity2options = - Collections.singletonMap("LocalActivity2", localActivity2Ops); - private static final Map defaultLocalActivity2options = - Collections.singletonMap( - "LocalActivity2", - LocalActivityOptions.newBuilder().setDoNotIncludeArgumentsIntoMarker(false).build()); - - @Rule - public SDKTestWorkflowRule testWorkflowRule = - SDKTestWorkflowRule.newBuilder() - .setWorkflowTypes( - WorkflowImplementationOptions.newBuilder() - .setDefaultActivityOptions(workerOps) - .setActivityOptions(activity2options) - .setDefaultLocalActivityOptions(localActivityWorkerOps) - .setLocalActivityOptions(localActivity2options) - .build(), - TestSetDefaultActivityOptionsWorkflowImpl.class) - .setActivityImplementations(new TestActivityImpl(), new TestLocalActivityImpl()) - .build(); - - @Test - public void testSetWorkflowImplementationOptions() { - TestWorkflowReturnMap workflowStub = - testWorkflowRule - .getWorkflowClient() - .newWorkflowStub( - TestWorkflowReturnMap.class, - WorkflowOptions.newBuilder().setTaskQueue(testWorkflowRule.getTaskQueue()).build()); - Map> result = workflowStub.execute(); - - // Check that activity1 has default workerOptions options that were partially overwritten with - // workflow. - Map activity1Values = result.get("Activity1"); - assertEquals(workerOps.getHeartbeatTimeout(), activity1Values.get("HeartbeatTimeout")); - assertEquals( - workflowOps.getScheduleToCloseTimeout(), activity1Values.get("ScheduleToCloseTimeout")); - assertEquals(workflowOps.getStartToCloseTimeout(), activity1Values.get("StartToCloseTimeout")); - - // Check that default options for activity2 were overwritten. - Map activity2Values = result.get("Activity2"); - assertEquals( - defaultActivity2options.get("Activity2").getHeartbeatTimeout(), - activity2Values.get("HeartbeatTimeout")); - assertEquals( - activity2Ops.getScheduleToCloseTimeout(), activity2Values.get("ScheduleToCloseTimeout")); - assertEquals(workflowOps.getStartToCloseTimeout(), activity2Values.get("StartToCloseTimeout")); - } - - @Test - public void testSetLocalActivityWorkflowImplementationOptions() { - TestWorkflowReturnMap workflowStub = - testWorkflowRule - .getWorkflowClient() - .newWorkflowStub( - TestWorkflowReturnMap.class, - WorkflowOptions.newBuilder().setTaskQueue(testWorkflowRule.getTaskQueue()).build()); - Map> result = workflowStub.execute(); - - // Check that local activity1 has default workerOptions options that were partially overwritten - // with workflow. - Map localActivity1Values = result.get("LocalActivity1"); - assertEquals( - localActivityWorkflowOps.getScheduleToCloseTimeout(), - localActivity1Values.get("ScheduleToCloseTimeout")); - assertEquals( - localActivityWorkflowOps.getStartToCloseTimeout(), - localActivity1Values.get("StartToCloseTimeout")); - // Check that default options for local activity2 were overwritten. - Map localActivity2Values = result.get("LocalActivity2"); - assertEquals( - localActivity2Ops.getScheduleToCloseTimeout(), - localActivity2Values.get("ScheduleToCloseTimeout")); - assertEquals( - localActivityWorkflowOps.getStartToCloseTimeout(), - localActivity2Values.get("StartToCloseTimeout")); - } - - public static class TestSetDefaultActivityOptionsWorkflowImpl implements TestWorkflowReturnMap { - @Override - public Map> execute() { - Workflow.setDefaultActivityOptions(workflowOps); - Workflow.applyActivityOptions(defaultActivity2options); - Workflow.setDefaultLocalActivityOptions(localActivityWorkflowOps); - Workflow.applyLocalActivityOptions(defaultLocalActivity2options); - Map> result = new HashMap<>(); - TestActivity activities = Workflow.newActivityStub(TestActivity.class); - TestLocalActivity localActivities = Workflow.newLocalActivityStub(TestLocalActivity.class); - result.put("Activity1", activities.activity1()); - result.put("Activity2", activities.activity2()); - result.put("LocalActivity1", localActivities.localActivity1()); - result.put("LocalActivity2", localActivities.localActivity2()); - return result; - } - } -} diff --git a/temporal-sdk/src/test/java/io/temporal/activity/LocalActivityOptionsPrecedenceTest.java b/temporal-sdk/src/test/java/io/temporal/activity/LocalActivityOptionsPrecedenceTest.java new file mode 100644 index 0000000000..57c6cedc8a --- /dev/null +++ b/temporal-sdk/src/test/java/io/temporal/activity/LocalActivityOptionsPrecedenceTest.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2022 Temporal Technologies, Inc. All Rights Reserved. + * + * Copyright (C) 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 material except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License 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.activity; + +import static org.junit.Assert.assertEquals; + +import io.temporal.client.WorkflowOptions; +import io.temporal.testing.internal.SDKTestWorkflowRule; +import io.temporal.worker.WorkflowImplementationOptions; +import io.temporal.workflow.Workflow; +import io.temporal.workflow.shared.TestActivities.TestActivity; +import io.temporal.workflow.shared.TestActivities.TestActivityImpl; +import io.temporal.workflow.shared.TestWorkflows.TestWorkflowReturnMap; +import java.time.Duration; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import org.junit.Rule; +import org.junit.Test; + +public class LocalActivityOptionsPrecedenceTest { + + private static final LocalActivityOptions implOptions = + LocalActivityOptions.newBuilder() + .setStartToCloseTimeout(Duration.ofHours(10)) + .setScheduleToCloseTimeout(Duration.ofDays(10)) + .build(); + + private static final Map workflowOptionsMap = + Collections.singletonMap( + "Activity1", + LocalActivityOptions.newBuilder().setStartToCloseTimeout(Duration.ofHours(20)).build()); + + private static final Map stubOptionsMap = + Collections.singletonMap( + "Activity2", + LocalActivityOptions.newBuilder().setScheduleToCloseTimeout(Duration.ofDays(30)).build()); + + @Rule + public SDKTestWorkflowRule testWorkflowRule = + SDKTestWorkflowRule.newBuilder() + .setWorkflowTypes( + WorkflowImplementationOptions.newBuilder() + .setDefaultLocalActivityOptions(implOptions) + .build(), + TestSetDefaultLocalActivityOptionsWorkflowImpl.class) + .setActivityImplementations(new TestActivityImpl()) + .build(); + + @Test + public void testSetWorkflowImplementationOptions() { + TestWorkflowReturnMap workflowStub = + testWorkflowRule + .getWorkflowClient() + .newWorkflowStub( + TestWorkflowReturnMap.class, + WorkflowOptions.newBuilder().setTaskQueue(testWorkflowRule.getTaskQueue()).build()); + Map> result = workflowStub.execute(); + + Map activity1Values = result.get("Activity1"); + Duration a1StartToClose = activity1Values.get("StartToCloseTimeout"); + Duration a1ScheduleToClose = activity1Values.get("ScheduleToCloseTimeout"); + + assertEquals(workflowOptionsMap.get("Activity1").getStartToCloseTimeout(), a1StartToClose); + assertEquals(implOptions.getScheduleToCloseTimeout(), a1ScheduleToClose); + + Map activity2Values = result.get("Activity2"); + Duration a2StartToClose = activity2Values.get("StartToCloseTimeout"); + Duration a2ScheduleToClose = activity2Values.get("ScheduleToCloseTimeout"); + + assertEquals(implOptions.getStartToCloseTimeout(), a2StartToClose); + assertEquals(stubOptionsMap.get("Activity2").getScheduleToCloseTimeout(), a2ScheduleToClose); + } + + public static class TestSetDefaultLocalActivityOptionsWorkflowImpl + implements TestWorkflowReturnMap { + @Override + public Map> execute() { + Workflow.applyLocalActivityOptions(workflowOptionsMap); + TestActivity activities = + Workflow.newLocalActivityStub(TestActivity.class, null, stubOptionsMap); + + Map> result = new HashMap<>(); + result.put("Activity1", activities.activity1()); + result.put("Activity2", activities.activity2()); + return result; + } + } +} diff --git a/temporal-testing/src/main/java/io/temporal/testing/TestActivityEnvironment.java b/temporal-testing/src/main/java/io/temporal/testing/TestActivityEnvironment.java index 96065966e2..55c0be3505 100644 --- a/temporal-testing/src/main/java/io/temporal/testing/TestActivityEnvironment.java +++ b/temporal-testing/src/main/java/io/temporal/testing/TestActivityEnvironment.java @@ -99,6 +99,20 @@ static TestActivityEnvironment newInstance(TestEnvironmentOptions options) { */ T newActivityStub(Class activityInterface, ActivityOptions options); + /** + * Creates a stub that can be used to invoke activities registered through {@link + * #registerActivitiesImplementations(Object...)}. + * + * @param activityInterface interface type implemented by activities + * @param options options that together with the properties of {@link + * io.temporal.activity.ActivityMethod} specify the activity invocation parameters + * @param activityMethodOptions activity method-specific invocation parameters + */ + T newActivityStub( + Class activityInterface, + ActivityOptions options, + Map activityMethodOptions); + /** * Creates a stub that can be used to invoke activities registered through {@link * #registerActivitiesImplementations(Object...)}. diff --git a/temporal-testing/src/main/java/io/temporal/testing/TestActivityEnvironmentInternal.java b/temporal-testing/src/main/java/io/temporal/testing/TestActivityEnvironmentInternal.java index 1760edf5c1..bd6e879261 100644 --- a/temporal-testing/src/main/java/io/temporal/testing/TestActivityEnvironmentInternal.java +++ b/temporal-testing/src/main/java/io/temporal/testing/TestActivityEnvironmentInternal.java @@ -48,6 +48,8 @@ import io.temporal.internal.activity.ActivityExecutionContextFactory; import io.temporal.internal.activity.ActivityExecutionContextFactoryImpl; import io.temporal.internal.activity.ActivityTaskHandlerImpl; +import io.temporal.internal.common.MergedActivityOptions; +import io.temporal.internal.common.MergedLocalActivityOptions; import io.temporal.internal.common.ProtobufTimeUtils; import io.temporal.internal.sync.*; import io.temporal.internal.testservice.InProcessGRPCServer; @@ -188,9 +190,10 @@ public T newActivityStub(Class activityInterface) { .setScheduleToCloseTimeout(Duration.ofDays(1)) .setHeartbeatTimeout(Duration.ofSeconds(1)) .build(); + MergedActivityOptions activityOptions = new MergedActivityOptions(null, options, null); InvocationHandler invocationHandler = ActivityInvocationHandler.newInstance( - activityInterface, options, null, new TestActivityExecutor(), () -> {}); + activityInterface, activityOptions, new TestActivityExecutor(), () -> {}); invocationHandler = new DeterministicRunnerWrapper(invocationHandler, deterministicRunnerExecutor::submit); return ActivityInvocationHandlerBase.newProxy(activityInterface, invocationHandler); @@ -204,9 +207,20 @@ public T newActivityStub(Class activityInterface) { */ @Override public T newActivityStub(Class activityInterface, ActivityOptions options) { + return newActivityStub(activityInterface, options, null); + } + + @Override + public T newActivityStub( + Class activityInterface, + ActivityOptions options, + Map activityMethodOptions) { + MergedActivityOptions activityOptions = + new MergedActivityOptions(null, options, activityMethodOptions); + InvocationHandler invocationHandler = ActivityInvocationHandler.newInstance( - activityInterface, options, null, new TestActivityExecutor(), () -> {}); + activityInterface, activityOptions, new TestActivityExecutor(), () -> {}); invocationHandler = new DeterministicRunnerWrapper(invocationHandler, deterministicRunnerExecutor::submit); return ActivityInvocationHandlerBase.newProxy(activityInterface, invocationHandler); @@ -224,13 +238,12 @@ public T newLocalActivityStub( Class activityInterface, LocalActivityOptions options, Map activityMethodOptions) { + MergedLocalActivityOptions activityOptions = + new MergedLocalActivityOptions(null, options, activityMethodOptions); + InvocationHandler invocationHandler = LocalActivityInvocationHandler.newInstance( - activityInterface, - options, - activityMethodOptions, - new TestActivityExecutor(), - () -> {}); + activityInterface, activityOptions, new TestActivityExecutor(), () -> {}); invocationHandler = new DeterministicRunnerWrapper(invocationHandler, deterministicRunnerExecutor::submit); return ActivityInvocationHandlerBase.newProxy(activityInterface, invocationHandler);