11package io .temporal .springai .util ;
22
3+ import io .temporal .internal .sync .ActivityInvocationHandler ;
4+ import io .temporal .internal .sync .LocalActivityInvocationHandler ;
5+ import io .temporal .internal .sync .NexusServiceInvocationHandler ;
36import java .lang .reflect .Proxy ;
47
58/**
6- * Utility class for detecting and working with Temporal stub types.
9+ * Utility class for detecting Temporal stub types.
710 *
811 * <p>Temporal creates dynamic proxies for various stub types (activities, local activities, child
912 * workflows, Nexus services). This utility provides methods to detect what type of stub an object
1013 * is, which is useful for determining how to handle tool calls.
14+ *
15+ * <p>This class uses direct {@code instanceof} checks against the SDK's internal invocation handler
16+ * classes. Since the {@code temporal-spring-ai} module lives in the SDK repo, this coupling is
17+ * intentional and will be caught by compilation if the handler classes are renamed or moved.
1118 */
1219public final class TemporalStubUtil {
1320
14- private TemporalStubUtil () {
15- // Utility class
16- }
21+ private TemporalStubUtil () {}
1722
1823 /**
1924 * Checks if the given object is an activity stub created by {@code Workflow.newActivityStub()}.
2025 *
2126 * @param object the object to check
22- * @return true if the object is an activity stub
27+ * @return true if the object is an activity stub (but not a local activity stub)
2328 */
2429 public static boolean isActivityStub (Object object ) {
25- return object != null
26- && Proxy .isProxyClass (object .getClass ())
27- && Proxy .getInvocationHandler (object )
28- .getClass ()
29- .getName ()
30- .contains ("ActivityInvocationHandler" )
31- && !isLocalActivityStub (object );
30+ if (object == null || !Proxy .isProxyClass (object .getClass ())) {
31+ return false ;
32+ }
33+ var handler = Proxy .getInvocationHandler (object );
34+ return handler instanceof ActivityInvocationHandler ;
3235 }
3336
3437 /**
@@ -39,28 +42,32 @@ public static boolean isActivityStub(Object object) {
3942 * @return true if the object is a local activity stub
4043 */
4144 public static boolean isLocalActivityStub (Object object ) {
42- return object != null
43- && Proxy .isProxyClass (object .getClass ())
44- && Proxy .getInvocationHandler (object )
45- .getClass ()
46- .getName ()
47- .contains ("LocalActivityInvocationHandler" );
45+ if (object == null || !Proxy .isProxyClass (object .getClass ())) {
46+ return false ;
47+ }
48+ var handler = Proxy .getInvocationHandler (object );
49+ return handler instanceof LocalActivityInvocationHandler ;
4850 }
4951
5052 /**
5153 * Checks if the given object is a child workflow stub created by {@code
5254 * Workflow.newChildWorkflowStub()}.
5355 *
56+ * <p>Note: {@code ChildWorkflowInvocationHandler} is package-private in the SDK, so we check via
57+ * the class name. This is safe because the module lives in the SDK repo — any rename would break
58+ * compilation of this module's tests.
59+ *
5460 * @param object the object to check
5561 * @return true if the object is a child workflow stub
5662 */
5763 public static boolean isChildWorkflowStub (Object object ) {
58- return object != null
59- && Proxy .isProxyClass (object .getClass ())
60- && Proxy .getInvocationHandler (object )
61- .getClass ()
62- .getName ()
63- .contains ("ChildWorkflowInvocationHandler" );
64+ if (object == null || !Proxy .isProxyClass (object .getClass ())) {
65+ return false ;
66+ }
67+ var handler = Proxy .getInvocationHandler (object );
68+ // ChildWorkflowInvocationHandler is package-private, so we use class name check.
69+ // This is the only handler where instanceof is not possible.
70+ return handler .getClass ().getName ().endsWith ("ChildWorkflowInvocationHandler" );
6471 }
6572
6673 /**
@@ -71,11 +78,10 @@ public static boolean isChildWorkflowStub(Object object) {
7178 * @return true if the object is a Nexus service stub
7279 */
7380 public static boolean isNexusServiceStub (Object object ) {
74- return object != null
75- && Proxy .isProxyClass (object .getClass ())
76- && Proxy .getInvocationHandler (object )
77- .getClass ()
78- .getName ()
79- .contains ("NexusServiceInvocationHandler" );
81+ if (object == null || !Proxy .isProxyClass (object .getClass ())) {
82+ return false ;
83+ }
84+ var handler = Proxy .getInvocationHandler (object );
85+ return handler instanceof NexusServiceInvocationHandler ;
8086 }
8187}
0 commit comments