|
1 | 1 | /*
|
| 2 | + * Modifications Copyright (c) 2017-2020 Uber Technologies Inc. |
| 3 | + * Portions of the Software are attributed to Copyright (c) 2020 Temporal Technologies Inc. |
2 | 4 | * Copyright 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
3 | 5 | *
|
4 |
| - * Modifications copyright (C) 2017 Uber Technologies, Inc. |
5 |
| - * |
6 | 6 | * Licensed under the Apache License, Version 2.0 (the "License"). You may not
|
7 | 7 | * use this file except in compliance with the License. A copy of the License is
|
8 | 8 | * located at
|
|
18 | 18 | package com.uber.cadence.internal.sync;
|
19 | 19 |
|
20 | 20 | import com.google.common.base.Joiner;
|
21 |
| -import com.google.common.reflect.TypeToken; |
| 21 | +import com.google.common.base.Objects; |
22 | 22 | import com.uber.cadence.PollForActivityTaskResponse;
|
23 | 23 | import com.uber.cadence.RespondActivityTaskCompletedRequest;
|
24 | 24 | import com.uber.cadence.RespondActivityTaskFailedRequest;
|
| 25 | +import com.uber.cadence.activity.ActivityInterface; |
25 | 26 | import com.uber.cadence.activity.ActivityMethod;
|
26 | 27 | import com.uber.cadence.client.ActivityCancelledException;
|
27 | 28 | import com.uber.cadence.common.MethodRetry;
|
|
33 | 34 | import com.uber.cadence.serviceclient.IWorkflowService;
|
34 | 35 | import com.uber.cadence.testing.SimulatedTimeoutException;
|
35 | 36 | import com.uber.m3.tally.Scope;
|
| 37 | +import java.lang.annotation.Annotation; |
36 | 38 | import java.lang.reflect.InvocationTargetException;
|
37 | 39 | import java.lang.reflect.Method;
|
38 | 40 | import java.util.Collections;
|
39 | 41 | import java.util.HashMap;
|
| 42 | +import java.util.HashSet; |
40 | 43 | import java.util.Map;
|
| 44 | +import java.util.Set; |
41 | 45 | import java.util.concurrent.CancellationException;
|
42 | 46 | import java.util.concurrent.ScheduledExecutorService;
|
43 | 47 | import java.util.function.BiFunction;
|
@@ -82,30 +86,29 @@ private void addActivityImplementation(
|
82 | 86 | + "\" This annotation can be used only on the interface method it implements.");
|
83 | 87 | }
|
84 | 88 | }
|
85 |
| - TypeToken<?>.TypeSet interfaces = TypeToken.of(cls).getTypes().interfaces(); |
86 |
| - if (interfaces.isEmpty()) { |
87 |
| - throw new IllegalArgumentException("Activity must implement at least one interface"); |
| 89 | + Set<MethodInterfacePair> activityMethods = |
| 90 | + getAnnotatedInterfaceMethods(cls, ActivityInterface.class); |
| 91 | + if (activityMethods.isEmpty()) { |
| 92 | + throw new IllegalArgumentException( |
| 93 | + "Class doesn't implement any non empty interface annotated with @ActivityInterface: " |
| 94 | + + cls.getName()); |
88 | 95 | }
|
89 |
| - for (TypeToken<?> i : interfaces) { |
90 |
| - if (i.getType().getTypeName().startsWith("org.mockito")) { |
91 |
| - continue; |
| 96 | + for (MethodInterfacePair pair : activityMethods) { |
| 97 | + Method method = pair.getMethod(); |
| 98 | + ActivityMethod annotation = method.getAnnotation(ActivityMethod.class); |
| 99 | + String activityType; |
| 100 | + if (annotation != null && !annotation.name().isEmpty()) { |
| 101 | + activityType = annotation.name(); |
| 102 | + } else { |
| 103 | + activityType = InternalUtils.getSimpleName(pair.getType(), method); |
92 | 104 | }
|
93 |
| - for (Method method : i.getRawType().getMethods()) { |
94 |
| - ActivityMethod annotation = method.getAnnotation(ActivityMethod.class); |
95 |
| - String activityType; |
96 |
| - if (annotation != null && !annotation.name().isEmpty()) { |
97 |
| - activityType = annotation.name(); |
98 |
| - } else { |
99 |
| - activityType = InternalUtils.getSimpleName(method); |
100 |
| - } |
101 |
| - if (activities.containsKey(activityType)) { |
102 |
| - throw new IllegalStateException( |
103 |
| - activityType + " activity type is already registered with the worker"); |
104 |
| - } |
105 |
| - |
106 |
| - ActivityTaskExecutor implementation = newTaskExecutor.apply(method, activity); |
107 |
| - activities.put(activityType, implementation); |
| 105 | + if (activities.containsKey(activityType)) { |
| 106 | + throw new IllegalStateException( |
| 107 | + activityType + " activity type is already registered with the worker"); |
108 | 108 | }
|
| 109 | + |
| 110 | + ActivityTaskExecutor implementation = newTaskExecutor.apply(method, activity); |
| 111 | + activities.put(activityType, implementation); |
109 | 112 | }
|
110 | 113 | }
|
111 | 114 |
|
@@ -266,4 +269,108 @@ public ActivityTaskHandler.Result execute(ActivityTaskImpl task, Scope metricsSc
|
266 | 269 | void setWorkflowService(IWorkflowService service) {
|
267 | 270 | this.service = service;
|
268 | 271 | }
|
| 272 | + |
| 273 | + static class MethodInterfacePair { |
| 274 | + private final Method method; |
| 275 | + private final Class<?> type; |
| 276 | + |
| 277 | + MethodInterfacePair(Method method, Class<?> type) { |
| 278 | + this.method = method; |
| 279 | + this.type = type; |
| 280 | + } |
| 281 | + |
| 282 | + public Method getMethod() { |
| 283 | + return method; |
| 284 | + } |
| 285 | + |
| 286 | + public Class<?> getType() { |
| 287 | + return type; |
| 288 | + } |
| 289 | + |
| 290 | + @Override |
| 291 | + public boolean equals(Object o) { |
| 292 | + if (this == o) return true; |
| 293 | + if (o == null || getClass() != o.getClass()) return false; |
| 294 | + MethodInterfacePair that = (MethodInterfacePair) o; |
| 295 | + return Objects.equal(method, that.method) && Objects.equal(type, that.type); |
| 296 | + } |
| 297 | + |
| 298 | + @Override |
| 299 | + public int hashCode() { |
| 300 | + return Objects.hashCode(method, type); |
| 301 | + } |
| 302 | + |
| 303 | + @Override |
| 304 | + public String toString() { |
| 305 | + return "MethodInterfacePair{" + "method=" + method + ", type=" + type + '}'; |
| 306 | + } |
| 307 | + } |
| 308 | + |
| 309 | + /** Used to override equals and hashCode of Method to ensure deduping by method name in a set. */ |
| 310 | + static class MethodWrapper { |
| 311 | + private final Method method; |
| 312 | + |
| 313 | + MethodWrapper(Method method) { |
| 314 | + this.method = method; |
| 315 | + } |
| 316 | + |
| 317 | + public Method getMethod() { |
| 318 | + return method; |
| 319 | + } |
| 320 | + |
| 321 | + @Override |
| 322 | + public boolean equals(Object o) { |
| 323 | + if (this == o) return true; |
| 324 | + if (o == null || getClass() != o.getClass()) return false; |
| 325 | + MethodWrapper that = (MethodWrapper) o; |
| 326 | + return Objects.equal(method.getName(), that.method.getName()); |
| 327 | + } |
| 328 | + |
| 329 | + @Override |
| 330 | + public int hashCode() { |
| 331 | + return Objects.hashCode(method.getName()); |
| 332 | + } |
| 333 | + } |
| 334 | + |
| 335 | + Set<MethodInterfacePair> getAnnotatedInterfaceMethods( |
| 336 | + Class<?> implementationClass, Class<? extends Annotation> annotationClass) { |
| 337 | + if (implementationClass.isInterface()) { |
| 338 | + throw new IllegalArgumentException( |
| 339 | + "Concrete class expected. Found interface: " + implementationClass.getSimpleName()); |
| 340 | + } |
| 341 | + Set<MethodInterfacePair> pairs = new HashSet<>(); |
| 342 | + // Methods inherited from interfaces that are not annotated with @ActivityInterface |
| 343 | + Set<MethodWrapper> ignored = new HashSet<>(); |
| 344 | + getAnnotatedInterfaceMethods(implementationClass, annotationClass, ignored, pairs); |
| 345 | + return pairs; |
| 346 | + } |
| 347 | + |
| 348 | + private void getAnnotatedInterfaceMethods( |
| 349 | + Class<?> current, |
| 350 | + Class<? extends Annotation> annotationClass, |
| 351 | + Set<MethodWrapper> methods, |
| 352 | + Set<MethodInterfacePair> result) { |
| 353 | + // Using set to dedupe methods which are defined in both non activity parent and current |
| 354 | + Set<MethodWrapper> ourMethods = new HashSet<>(); |
| 355 | + if (current.isInterface()) { |
| 356 | + Method[] declaredMethods = current.getDeclaredMethods(); |
| 357 | + for (int i = 0; i < declaredMethods.length; i++) { |
| 358 | + Method declaredMethod = declaredMethods[i]; |
| 359 | + ourMethods.add(new MethodWrapper(declaredMethod)); |
| 360 | + } |
| 361 | + } |
| 362 | + Class<?>[] interfaces = current.getInterfaces(); |
| 363 | + for (int i = 0; i < interfaces.length; i++) { |
| 364 | + Class<?> anInterface = interfaces[i]; |
| 365 | + getAnnotatedInterfaceMethods(anInterface, annotationClass, ourMethods, result); |
| 366 | + } |
| 367 | + Annotation annotation = current.getAnnotation(annotationClass); |
| 368 | + if (annotation == null) { |
| 369 | + methods.addAll(ourMethods); |
| 370 | + return; |
| 371 | + } |
| 372 | + for (MethodWrapper method : ourMethods) { |
| 373 | + result.add(new MethodInterfacePair(method.getMethod(), current)); |
| 374 | + } |
| 375 | + } |
269 | 376 | }
|
0 commit comments