1313import org .jspecify .annotations .Nullable ;
1414
1515/**
16- * The rule every framework applies to the application's {@code @AcpAgent} classes: none means a
17- * client-only application, one is served, more than one is an error naming them. The container
18- * finds the candidates its own way (bean metadata, a build-time index); this class decides.
16+ * Decides which {@code @AcpAgent} an application serves, by the rule every framework applies:
17+ * none means a client-only application (serve no agent), one is served, and more than one is an
18+ * error that names them all. The framework finds the candidates its own way, from bean metadata
19+ * at run time ({@link AgentCandidate}) or from a build-time index by class name, and passes them
20+ * to {@code requireSingle}; this class only decides. The chosen candidate goes to
21+ * {@link AcpAgents#builder}.
22+ *
23+ * <p>Pass the framework's property that turns the agent off, such as
24+ * {@code spring.acp.agent.enabled}, so the error tells the user how to serve none. A framework
25+ * that requires an agent (because it found one by annotation already) turns the empty result
26+ * into its own error.
1927 */
2028public final class AcpAgentDiscovery {
2129
2230 private AcpAgentDiscovery () {
2331 }
2432
2533 /**
26- * An {@code @AcpAgent} bean the container found.
27- * @param name the container's name for it, used in messages
28- * @param userClass the class the application wrote, from the container's metadata: never a
29- * proxy or generated subclass (CGLIB, ArC, Micronaut AOP), whose methods carry no handler
34+ * One {@code @AcpAgent} bean the container found: its name, the class the application wrote,
35+ * and a way to get the bean. The framework builds one per bean from its own metadata and
36+ * passes them to {@link AcpAgentDiscovery#requireSingle(Collection, String)}.
37+ *
38+ * <p>The user class must be the class the application declared, taken from the container's
39+ * metadata (Spring's {@code ClassUtils.getUserClass}, Micronaut's bean definition type), never
40+ * the class of a proxy or generated subclass (CGLIB, ArC, Micronaut AOP): the handler
41+ * annotations are on the user class, and a proxy's overriding methods carry none. The instance
42+ * may be such a proxy, which keeps its advice (transactions, security) on every call.
43+ * {@link AcpAgents#builder} calls the supplier once, while it assembles the agent.
44+ * @param name the container's name for the bean, used in messages
45+ * @param userClass the class the application wrote, whose methods carry the handler
3046 * annotations
31- * @param instance supplies the bean, possibly a proxy, that handles every request
47+ * @param instance supplies the bean that handles every request, possibly a proxy
3248 * @param <T> the agent type
3349 */
3450 public record AgentCandidate <T >(String name , Class <T > userClass , Supplier <? extends T > instance ) {
3551
3652 /**
37- * A candidate, each part checked .
53+ * Creates a candidate, checking each part.
3854 * @throws NullPointerException if a part is null
3955 */
4056 public AgentCandidate {
@@ -46,22 +62,26 @@ public record AgentCandidate<T>(String name, Class<T> userClass, Supplier<? exte
4662 }
4763
4864 /**
49- * The one candidate.
65+ * Returns the one candidate, as {@link #requireSingle(Collection, String)} does, with an
66+ * error that names no property.
5067 * @param candidates the {@code @AcpAgent} beans the container found
5168 * @return the candidate, or empty when there is none
52- * @throws IllegalStateException listing them when there is more than one
69+ * @throws IllegalStateException if there is more than one; the message lists their user
70+ * classes
5371 */
5472 public static Optional <AgentCandidate <?>> requireSingle (Collection <? extends AgentCandidate <?>> candidates ) {
5573 return requireSingle (candidates , null );
5674 }
5775
5876 /**
59- * The one candidate.
77+ * Returns the one candidate the application serves, or empty for a client-only application .
6078 * @param candidates the {@code @AcpAgent} beans the container found
61- * @param enabledProperty the framework's property that turns the agent off, named in the error
62- * as the way to serve none; null to name none
79+ * @param enabledProperty the framework's property that turns the agent off, such as
80+ * {@code spring.acp.agent.enabled}, which the error names as the way to serve none; null to
81+ * name none
6382 * @return the candidate, or empty when there is none
64- * @throws IllegalStateException listing them when there is more than one
83+ * @throws IllegalStateException if there is more than one; the message lists their user
84+ * classes, sorted, and says to remove {@code @AcpAgent} from all but one
6585 */
6686 public static Optional <AgentCandidate <?>> requireSingle (Collection <? extends AgentCandidate <?>> candidates ,
6787 @ Nullable String enabledProperty ) {
@@ -73,13 +93,13 @@ public static Optional<AgentCandidate<?>> requireSingle(Collection<? extends Age
7393 }
7494
7595 /**
76- * The same rule over class names, for build-time discovery (a Jandex index), with no class
77- * loaded.
96+ * Applies the same rule to class names, for discovery at build time (Quarkus reads a Jandex
97+ * index), so no class is loaded.
7898 * @param classNames the names of the {@code @AcpAgent} classes found
79- * @param enabledProperty the framework's property that turns the agent off, named in the error;
80- * null to name none
99+ * @param enabledProperty the framework's property that turns the agent off, which the error
100+ * names; null to name none
81101 * @return the class name, or empty when there is none
82- * @throws IllegalStateException listing them when there is more than one
102+ * @throws IllegalStateException if there is more than one; the message lists them, sorted
83103 */
84104 public static Optional <String > requireSingle (List <String > classNames , @ Nullable String enabledProperty ) {
85105 if (classNames .size () > 1 ) {
0 commit comments