luigidemasi commented on code in PR #26813:
URL: https://github.com/apache/camel/pull/26813#discussion_r4091903158


##########
components/camel-ai/camel-semantic/src/main/java/org/apache/camel/language/semantic/SemanticLanguageConfigurer.java:
##########
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file 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 org.apache.camel.language.semantic;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.spi.PropertyConfigurer;
+
+/** Preserves adapter references so the language can validate their type and 
manage only instances it creates. */
+public class SemanticLanguageConfigurer implements PropertyConfigurer {
+    @Override
+    public boolean configureRaw(CamelContext context, Object target, String 
name, Object value, boolean ignoreCase) {
+        if (ignoreCase ? "adapter".equalsIgnoreCase(name) : 
"adapter".equals(name)) {
+            if (!(value instanceof String)) {
+                throw new IllegalArgumentException("Semantic adapter selection 
must be a bean reference or class name");
+            }
+            ((SemanticLanguage) target).setAdapter((String) value);
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public boolean configure(CamelContext context, Object target, String name, 
Object value, boolean ignoreCase) {

Review Comment:
   Removed in 7c10c75e1bf5 together with both service registrations. The 
language uses standard property binding for `adapter` and `defaultState`; Camel 
Main tests cover bean names, class names and property placeholders.
   
   _AI-generated by Codex on behalf of 
[luigidemasi](https://github.com/luigidemasi)._



##########
components/camel-ai/camel-semantic/src/main/java/org/apache/camel/semantic/yaml/SemanticDefinitionDeserializer.java:
##########
@@ -0,0 +1,188 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file 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 org.apache.camel.dsl.yaml.deserializers;
+
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.dsl.yaml.common.YamlDeserializationContext;
+import org.apache.camel.dsl.yaml.common.YamlDeserializerSupport;
+import org.apache.camel.dsl.yaml.common.exception.YamlDeserializationException;
+import org.apache.camel.semantic.SemanticQuestion;
+import org.apache.camel.semantic.SemanticQuestions;
+import org.apache.camel.spi.CamelContextCustomizer;
+import org.apache.camel.spi.annotations.YamlIn;
+import org.apache.camel.spi.annotations.YamlProperty;
+import org.apache.camel.spi.annotations.YamlType;
+import org.snakeyaml.engine.v2.api.ConstructNode;
+import org.snakeyaml.engine.v2.nodes.Node;
+import org.snakeyaml.engine.v2.nodes.NodeTuple;
+import org.snakeyaml.engine.v2.nodes.SequenceNode;
+
+/** Named semantic declarations are installed in a resource-wide pass before 
route references are resolved. */
+@YamlIn
+@YamlType(nodes = "semantic", properties = {
+        @YamlProperty(name = "question",
+                      type = 
"map:org.apache.camel.dsl.yaml.deserializers.SemanticDefinitionDeserializer$QuestionSchema",
+                      required = true)
+})
+public class SemanticDefinitionDeserializer extends YamlDeserializerSupport 
implements ConstructNode {
+    private static final Set<String> FIELDS
+            = Set.of("type", "instructions", "state", "criteria", "threshold", 
"uncertainty", "uncertaintyPolicy");
+
+    @Override
+    public Object construct(Node node) {
+        read(node);
+        // Registration happens once for the entire resource, including 
declarations after routes.
+        return (CamelContextCustomizer) context -> {
+        };
+    }
+
+    public static void configure(CamelContext context, 
YamlDeserializationContext dc, Node root) {
+        if (!(root instanceof SequenceNode sequence)) {
+            return;
+        }
+        Map<String, SemanticQuestion> definitions = new LinkedHashMap<>();
+        for (Node node : sequence.getValue()) {
+            for (NodeTuple tuple : asMappingNode(node).getValue()) {
+                if ("semantic".equals(asText(tuple.getKeyNode()))) {
+                    read(tuple.getValueNode()).forEach((name, question) -> {
+                        if (definitions.putIfAbsent(name, question) != null) {
+                            throw new IllegalArgumentException("Duplicate 
semantic question: " + name);
+                        }
+                    });
+                }
+            }
+        }
+        SemanticQuestions.get(context).replace(dc.getResource(), definitions);
+    }
+
+    private static Map<String, SemanticQuestion> read(Node node) {
+        Map<String, Node> semantic = fields(node);
+        if (!semantic.keySet().equals(Set.of("question"))) {
+            throw new IllegalArgumentException("Semantic declaration requires 
only question");
+        }
+        Map<String, SemanticQuestion> result = new LinkedHashMap<>();
+        fields(semantic.get("question")).forEach((name, definition) -> {
+            Map<String, Node> values = fields(definition);
+            if (!FIELDS.containsAll(values.keySet())) {
+                throw new IllegalArgumentException("Unknown property in 
semantic question: " + name);
+            }
+            String typeName = asText(values.get("type"));
+            if (typeName == null) {
+                throw new IllegalArgumentException("Semantic question type is 
required: " + name);
+            }
+            SemanticQuestion.Type type = 
SemanticQuestion.Type.valueOf(typeName.toUpperCase(Locale.ROOT));

Review Comment:
   The diagnostic gap is valid and remains open in this update; moving the 
parser into `camel-semantic` does not fix it. I reproduced unlocated enum 
failures. One detail differs from the report: an empty policy scalar produces 
an `IllegalArgumentException` from enum parsing, rather than an NPE, because 
`asText` returns an empty string. Located diagnostics for enum and structural 
errors still need follow-up.
   
   _AI-generated by Codex on behalf of 
[luigidemasi](https://github.com/luigidemasi)._



##########
components/camel-ai/camel-semantic/src/main/java/org/apache/camel/semantic/SemanticQuestions.java:
##########
@@ -0,0 +1,88 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file 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 org.apache.camel.semantic;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.spi.Resource;
+
+/** Context-local named questions, replaced atomically per source when a route 
resource is reloaded. */
+public final class SemanticQuestions {
+    private final Map<String, Map<String, SemanticQuestion>> sources = new 
HashMap<>();
+    private final Map<String, Resource> resources = new HashMap<>();
+    private volatile Map<String, SemanticQuestion> questions = Map.of();
+
+    public static SemanticQuestions get(CamelContext context) {
+        synchronized (context) {

Review Comment:
   Agreed that private coordination is preferable. This remains open in this 
update. A replacement must still coordinate separate language instances sharing 
one context/registry; simply changing both monitors to per-instance locks would 
lose that property. No actual deadlock was established during this assessment.
   
   _AI-generated by Codex on behalf of 
[luigidemasi](https://github.com/luigidemasi)._



##########
components/camel-ai/camel-semantic/src/main/java/org/apache/camel/language/semantic/SemanticLanguage.java:
##########
@@ -0,0 +1,307 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file 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 org.apache.camel.language.semantic;
+
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.CamelContextAware;
+import org.apache.camel.Exchange;
+import org.apache.camel.Expression;
+import org.apache.camel.Predicate;
+import org.apache.camel.RuntimeCamelException;
+import org.apache.camel.semantic.SemanticAdapter;
+import org.apache.camel.semantic.SemanticQuestion;
+import org.apache.camel.semantic.SemanticQuestions;
+import org.apache.camel.semantic.SemanticResult;
+import org.apache.camel.spi.Metadata;
+import org.apache.camel.spi.annotations.Language;
+import org.apache.camel.support.ExpressionAdapter;
+import org.apache.camel.support.LanguageSupport;
+import org.apache.camel.support.service.ServiceHelper;
+import org.apache.camel.support.service.ServiceSupport;
+
+/** Evaluates a named, provider-independent question against selected message 
state. */
+@Language(value = "semantic", modelName = "language")
+@Metadata(title = "Semantic", description = "Evaluate named semantic questions 
through a provider adapter",
+          label = "language,ai", firstVersion = "4.23.0")
+public class SemanticLanguage extends LanguageSupport {
+    public static final String RESULT = "CamelSemanticResult";
+    public static final String ADAPTER_NAME = "camelSemanticAdapter";
+    public static final String ADAPTER_RESOURCE = 
"META-INF/services/org.apache.camel.semantic.SemanticAdapter";
+
+    private String adapter;
+    private String defaultState = "${body}";
+    private SemanticAdapter selectedAdapter;
+
+    public String getAdapter() {
+        return adapter;
+    }
+
+    /**
+     * Adapter registry reference (#bean:name) or implementation class (plain 
FQCN or #class:FQCN). Absent selects the
+     * sole advertised adapter.
+     */
+    public void setAdapter(String adapter) {
+        this.adapter = adapter;
+    }
+
+    public String getDefaultState() {
+        return defaultState;
+    }
+
+    /** Default Simple state selector for questions without their own 
selector. Defaults to the body. */
+    public void setDefaultState(String defaultState) {
+        this.defaultState = defaultState;
+    }
+
+    @Override
+    public Expression createExpression(String expression) {
+        return createEvaluation(expression, false);
+    }
+
+    @Override
+    public Predicate createPredicate(String expression) {
+        return createEvaluation(expression, true);
+    }
+
+    public boolean validateExpression(String expression) {
+        if (expression == null || !expression.startsWith("ref:") || 
expression.substring(4).isBlank()) {
+            throw new IllegalArgumentException("Semantic expression must 
reference a named question using ref:name");
+        }
+        return true;
+    }
+
+    public boolean validatePredicate(String expression) {
+        return validateExpression(expression);
+    }
+
+    private Evaluation createEvaluation(String expression, boolean predicate) {
+        validateExpression(expression);
+        Evaluation evaluation = new Evaluation(expression.substring(4), 
predicate);
+        if (getCamelContext() != null) {
+            evaluation.init(getCamelContext());
+        }
+        return evaluation;
+    }
+
+    private synchronized SemanticAdapter adapter() {
+        if (selectedAdapter != null) {
+            return selectedAdapter;
+        }
+        CamelContext context = getCamelContext();
+        String configured = adapter == null ? null : 
context.resolvePropertyPlaceholders(adapter);
+        if (configured != null && configured.startsWith("#") && 
!configured.startsWith("#class:")) {
+            String name = configured.startsWith("#bean:") ? 
configured.substring(6) : configured.substring(1);
+            if (name.contains(":")) {
+                throw new IllegalArgumentException("Semantic adapter reference 
must use #bean:name, #name or #class:FQCN");
+            }
+            Object bean = context.getRegistry().lookupByName(name);
+            if (!(bean instanceof SemanticAdapter found)) {
+                throw new IllegalArgumentException("Semantic adapter bean is 
missing or does not implement SemanticAdapter");
+            }
+            selectedAdapter = found;
+            return found;
+        }
+        ManagedAdapter owned = null;
+        try {
+            String className = configured;
+            if (className == null) {
+                Set<String> candidates = new TreeSet<>();
+                Enumeration<URL> resources = 
context.getClassResolver().loadAllResourcesAsURL(ADAPTER_RESOURCE);

Review Comment:
   The discovery implementation is unchanged in this update. 
`FactoryFinder.findClass` reads a single Camel `class=...` resource; it does 
not directly preserve enumeration, deduplication and ambiguity rejection across 
the current provider descriptors. A replacement needs to retain those 
semantics. Native-image discovery has not been validated, so that compatibility 
concern remains open.
   
   _AI-generated by Codex on behalf of 
[luigidemasi](https://github.com/luigidemasi)._



##########
core/camel-core-reifier/src/main/java/org/apache/camel/reifier/ChoiceReifier.java:
##########
@@ -49,6 +52,25 @@ public ChoiceReifier(Route route, ProcessorDefinition<?> 
definition) {
     @Override
     public Processor createProcessor() throws Exception {
         final boolean isPrecondition = Boolean.TRUE == 
parseBoolean(definition.getPrecondition());
+        boolean selecting = definition.getSelector() != null;
+        if (selecting && isPrecondition) {
+            throw new IllegalArgumentException("Choice selector cannot be 
combined with precondition mode");
+        }
+        Set<String> uniqueValues = new HashSet<>();
+        for (WhenDefinition when : definition.getWhenClauses()) {
+            if (selecting) {
+                if (when.getValue() == null || when.getExpression() != null) {
+                    throw new IllegalArgumentException("Choice selector 
requires literal when values without predicates");
+                }
+                if (!uniqueValues.add(parseString(when.getValue()))) {
+                    throw new IllegalArgumentException("Choice selector has 
duplicate when values");
+                }
+            } else if (when.getValue() != null || when.getExpression() == 
null) {
+                throw new IllegalArgumentException("Choice without a selector 
requires when predicates without values");
+            }
+        }
+        Expression selector = selecting ? 
createExpression(definition.getSelector().getExpressionType()) : null;

Review Comment:
   Removed in 7c10c75e1bf5. `ChoiceReifier` matches the original base and no 
longer accepts or dereferences a selector. Semantic category routing uses 
existing Set Property and Choice expressions.
   
   _AI-generated by Codex on behalf of 
[luigidemasi](https://github.com/luigidemasi)._



##########
core/camel-management/src/main/java/org/apache/camel/management/mbean/ManagedChoice.java:
##########
@@ -76,8 +76,9 @@ public TabularData extendedInformation() {
                 FilterProcessor filter = filters.get(i);
 
                 CompositeType ct = CamelOpenMBeanTypes.choiceCompositeType();
-                String predicate = when.getExpression().getExpression();
-                String language = when.getExpression().getLanguage();
+                String predicate = when.getValue() != null ? when.getValue() : 
when.getExpression().getExpression();
+                String language = getDefinition().getSelector() != null
+                        ? 
getDefinition().getSelector().getExpressionType().getLanguage() : 
when.getExpression().getLanguage();

Review Comment:
   Removed in 7c10c75e1bf5. `ManagedChoice` matches the original base; its JMX 
path no longer contains selector handling.
   
   _AI-generated by Codex on behalf of 
[luigidemasi](https://github.com/luigidemasi)._



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to