diff --git a/extras/queue-manager-replicated/core/src/main/java/io/a2a/extras/queuemanager/replicated/core/ReplicatedQueueManager.java b/extras/queue-manager-replicated/core/src/main/java/io/a2a/extras/queuemanager/replicated/core/ReplicatedQueueManager.java index 88c0c75ab..586ab11a7 100644 --- a/extras/queue-manager-replicated/core/src/main/java/io/a2a/extras/queuemanager/replicated/core/ReplicatedQueueManager.java +++ b/extras/queue-manager-replicated/core/src/main/java/io/a2a/extras/queuemanager/replicated/core/ReplicatedQueueManager.java @@ -24,12 +24,26 @@ public class ReplicatedQueueManager implements QueueManager { private static final Logger LOGGER = LoggerFactory.getLogger(ReplicatedQueueManager.class); - private final InMemoryQueueManager delegate; - + // Fields set by constructor injection cannot be final. We need a noargs constructor for + // Jakarta compatibility, and it seems that making fields set by constructor injection + // final, is not proxyable in all runtimes + private InMemoryQueueManager delegate; private ReplicationStrategy replicationStrategy; - private TaskStateProvider taskStateProvider; + /** + * No-args constructor for CDI proxy creation. + * CDI requires a non-private constructor to create proxies for @ApplicationScoped beans. + * All fields are initialized by the @Inject constructor during actual bean creation. + */ + @SuppressWarnings("NullAway") + protected ReplicatedQueueManager() { + // For CDI proxy creation + this.delegate = null; + this.replicationStrategy = null; + this.taskStateProvider = null; + } + @Inject public ReplicatedQueueManager(ReplicationStrategy replicationStrategy, TaskStateProvider taskStateProvider) { this.replicationStrategy = replicationStrategy; diff --git a/server-common/src/main/java/io/a2a/server/events/InMemoryQueueManager.java b/server-common/src/main/java/io/a2a/server/events/InMemoryQueueManager.java index 19179f50d..e5a17e0e7 100644 --- a/server-common/src/main/java/io/a2a/server/events/InMemoryQueueManager.java +++ b/server-common/src/main/java/io/a2a/server/events/InMemoryQueueManager.java @@ -16,8 +16,23 @@ public class InMemoryQueueManager implements QueueManager { private static final Logger LOGGER = LoggerFactory.getLogger(InMemoryQueueManager.class); private final ConcurrentMap queues = new ConcurrentHashMap<>(); - private final EventQueueFactory factory; - private final TaskStateProvider taskStateProvider; + // Fields set by constructor injection cannot be final. We need a noargs constructor for + // Jakarta compatibility, and it seems that making fields set by constructor injection + // final, is not proxyable in all runtimes + private EventQueueFactory factory; + private TaskStateProvider taskStateProvider; + + /** + * No-args constructor for CDI proxy creation. + * CDI requires a non-private constructor to create proxies for @ApplicationScoped beans. + * All fields are initialized by the @Inject constructor during actual bean creation. + */ + @SuppressWarnings("NullAway") + protected InMemoryQueueManager() { + // For CDI proxy creation + this.factory = null; + this.taskStateProvider = null; + } @Inject public InMemoryQueueManager(TaskStateProvider taskStateProvider) { diff --git a/server-common/src/main/java/io/a2a/server/requesthandlers/DefaultRequestHandler.java b/server-common/src/main/java/io/a2a/server/requesthandlers/DefaultRequestHandler.java index 61af1d43e..e19f7920d 100644 --- a/server-common/src/main/java/io/a2a/server/requesthandlers/DefaultRequestHandler.java +++ b/server-common/src/main/java/io/a2a/server/requesthandlers/DefaultRequestHandler.java @@ -56,7 +56,6 @@ import io.a2a.spec.Message; import io.a2a.spec.MessageSendParams; import io.a2a.spec.PushNotificationConfig; -import io.a2a.spec.PushNotificationNotSupportedError; import io.a2a.spec.StreamingEventKind; import io.a2a.spec.Task; import io.a2a.spec.TaskIdParams; @@ -208,17 +207,37 @@ public class DefaultRequestHandler implements RequestHandler { */ int consumptionCompletionTimeoutSeconds; - private final AgentExecutor agentExecutor; - private final TaskStore taskStore; - private final QueueManager queueManager; - private final PushNotificationConfigStore pushConfigStore; - private final PushNotificationSender pushSender; - private final Supplier requestContextBuilder; + // Fields set by constructor injection cannot be final. We need a noargs constructor for + // Jakarta compatibility, and it seems that making fields set by constructor injection + // final, is not proxyable in all runtimes + private AgentExecutor agentExecutor; + private TaskStore taskStore; + private QueueManager queueManager; + private PushNotificationConfigStore pushConfigStore; + private PushNotificationSender pushSender; + private Supplier requestContextBuilder; private final ConcurrentMap> runningAgents = new ConcurrentHashMap<>(); private final Set> backgroundTasks = ConcurrentHashMap.newKeySet(); - private final Executor executor; + private Executor executor; + + /** + * No-args constructor for CDI proxy creation. + * CDI requires a non-private constructor to create proxies for @ApplicationScoped beans. + * All fields are initialized by the @Inject constructor during actual bean creation. + */ + @SuppressWarnings("NullAway") + protected DefaultRequestHandler() { + // For CDI proxy creation + this.agentExecutor = null; + this.taskStore = null; + this.queueManager = null; + this.pushConfigStore = null; + this.pushSender = null; + this.requestContextBuilder = null; + this.executor = null; + } @Inject public DefaultRequestHandler(AgentExecutor agentExecutor, TaskStore taskStore, diff --git a/server-common/src/main/java/io/a2a/server/tasks/BasePushNotificationSender.java b/server-common/src/main/java/io/a2a/server/tasks/BasePushNotificationSender.java index 35a248373..a59767d8f 100644 --- a/server-common/src/main/java/io/a2a/server/tasks/BasePushNotificationSender.java +++ b/server-common/src/main/java/io/a2a/server/tasks/BasePushNotificationSender.java @@ -27,8 +27,24 @@ public class BasePushNotificationSender implements PushNotificationSender { private static final Logger LOGGER = LoggerFactory.getLogger(BasePushNotificationSender.class); - private final A2AHttpClient httpClient; - private final PushNotificationConfigStore configStore; + // Fields set by constructor injection cannot be final. We need a noargs constructor for + // Jakarta compatibility, and it seems that making fields set by constructor injection + // final, is not proxyable in all runtimes + private A2AHttpClient httpClient; + private PushNotificationConfigStore configStore; + + + /** + * No-args constructor for CDI proxy creation. + * CDI requires a non-private constructor to create proxies for @ApplicationScoped beans. + * All fields are initialized by the @Inject constructor during actual bean creation. + */ + @SuppressWarnings("NullAway") + protected BasePushNotificationSender() { + // For CDI proxy creation + this.httpClient = null; + this.configStore = null; + } @Inject public BasePushNotificationSender(PushNotificationConfigStore configStore) { diff --git a/transport/grpc/src/main/java/io/a2a/transport/grpc/handler/GrpcHandler.java b/transport/grpc/src/main/java/io/a2a/transport/grpc/handler/GrpcHandler.java index 7a1e0a912..2998965ea 100644 --- a/transport/grpc/src/main/java/io/a2a/transport/grpc/handler/GrpcHandler.java +++ b/transport/grpc/src/main/java/io/a2a/transport/grpc/handler/GrpcHandler.java @@ -67,7 +67,7 @@ public abstract class GrpcHandler extends A2AServiceGrpc.A2AServiceImplBase { // Without this we get intermittent failures private static volatile Runnable streamingSubscribedRunnable; - private AtomicBoolean initialised = new AtomicBoolean(false); + private final AtomicBoolean initialised = new AtomicBoolean(false); private static final Logger LOGGER = Logger.getLogger(GrpcHandler.class.getName()); diff --git a/transport/jsonrpc/src/main/java/io/a2a/transport/jsonrpc/handler/JSONRPCHandler.java b/transport/jsonrpc/src/main/java/io/a2a/transport/jsonrpc/handler/JSONRPCHandler.java index 06de11dcd..ca8149099 100644 --- a/transport/jsonrpc/src/main/java/io/a2a/transport/jsonrpc/handler/JSONRPCHandler.java +++ b/transport/jsonrpc/src/main/java/io/a2a/transport/jsonrpc/handler/JSONRPCHandler.java @@ -60,10 +60,27 @@ @ApplicationScoped public class JSONRPCHandler { + // Fields set by constructor injection cannot be final. We need a noargs constructor for + // Jakarta compatibility, and it seems that making fields set by constructor injection + // final, is not proxyable in all runtimes private AgentCard agentCard; private @Nullable Instance extendedAgentCard; private RequestHandler requestHandler; - private final Executor executor; + private Executor executor; + + /** + * No-args constructor for CDI proxy creation. + * CDI requires a non-private constructor to create proxies for @ApplicationScoped beans. + * All fields are initialized by the @Inject constructor during actual bean creation. + */ + @SuppressWarnings("NullAway") + protected JSONRPCHandler() { + // For CDI proxy creation + this.agentCard = null; + this.extendedAgentCard = null; + this.requestHandler = null; + this.executor = null; + } @Inject public JSONRPCHandler(@PublicAgentCard AgentCard agentCard, @Nullable @ExtendedAgentCard Instance extendedAgentCard, diff --git a/transport/rest/src/main/java/io/a2a/transport/rest/handler/RestHandler.java b/transport/rest/src/main/java/io/a2a/transport/rest/handler/RestHandler.java index 8c4050fd9..489bf909b 100644 --- a/transport/rest/src/main/java/io/a2a/transport/rest/handler/RestHandler.java +++ b/transport/rest/src/main/java/io/a2a/transport/rest/handler/RestHandler.java @@ -67,11 +67,20 @@ public class RestHandler { private static final Logger log = Logger.getLogger(RestHandler.class.getName()); + + // Fields set by constructor injection cannot be final. We need a noargs constructor for + // Jakarta compatibility, and it seems that making fields set by constructor injection + // final, is not proxyable in all runtimes private AgentCard agentCard; private @Nullable Instance extendedAgentCard; private RequestHandler requestHandler; - private final Executor executor; + private Executor executor; + /** + * No-args constructor for CDI proxy creation. + * CDI requires a non-private constructor to create proxies for @ApplicationScoped beans. + * All fields are initialized by the @Inject constructor during actual bean creation. + */ @SuppressWarnings("NullAway") protected RestHandler() { // For CDI