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


##########
core/camel-core-model/src/main/java/org/apache/camel/model/BasicExpressionNode.java:
##########
@@ -36,7 +36,7 @@ public abstract class BasicExpressionNode<T extends 
BasicExpressionNode<T>> exte
 
     @Metadata(required = true,
               description = "Expression used as the predicate to evaluate 
whether this when should trigger and route the message or not.")
-    @XmlElementRef
+    @XmlElementRef(required = false)

Review Comment:
   This is the change I would most like to see reverted, and the scope split 
removes it.
   
   Relaxing `@XmlElementRef` to `required = false` turns `<xs:choice>` into 
`<xs:choice minOccurs="0"/>` in both `camel-xml-io.xsd` and `camel-spring.xsd`. 
`WhenDefinition` is the only subclass, so the blast radius is contained — but 
the effect is that a `<when>` with no predicate stops being a schema error and 
becomes a route-build error instead, and every XML author and IDE loses that 
validation at edit time. That is a real regression in the editing experience 
for a twenty-year-old element.
   
   A separate EIP with its own branch element carrying a `value` attribute 
leaves `WhenDefinition.expression` required and this line untouched.



##########
core/camel-core-model/src/main/java/org/apache/camel/model/ChoiceDefinition.java:
##########
@@ -45,10 +47,14 @@
           description = "Routes messages to different steps based on a series 
of conditions (predicates),"
                         + " similar to if-elseif-else in Java. Each condition 
is evaluated in order until one matches.")
 @XmlRootElement(name = "choice")
-@XmlType(propOrder = { "whenClauses", "otherwise" })
+@XmlType(propOrder = { "selector", "whenClauses", "otherwise" })
 @XmlAccessorType(XmlAccessType.FIELD)
 public class ChoiceDefinition extends NoOutputDefinition<ChoiceDefinition> {
 
+    @XmlElement
+    @Metadata(description = "Expression evaluated once per entry into this 
choice. Its String result is matched against literal when values. Cannot be 
combined with precondition mode or predicate branches.")
+    @DslArg
+    private ExpressionSubElementDefinition selector;

Review Comment:
   **Blocking — please take this out of the PR.** This field, the 
`when(String)` method, the `@XmlType(propOrder)` reordering and the 
`preCreateProcessor()` null-skip together change the model of a foundational 
EIP that has been stable for twenty years, along with its generated 
`choice.json`, its XSD block and its YAML schema.
   
   `camel-semantic` does not need it — `choice().when(semantic("ref:isSpam"))` 
already works against unmodified Choice, and a category question routes with 
`setHeader` plus `simple`.
   
   When the value-dispatch EIP is proposed on its own ticket, a standalone 
definition is entirely achievable: `OtherwiseDefinition` is already a 
standalone `@XmlRootElement` that `ChoiceDefinition` references through a plain 
`@XmlElement` field, so a new parent can reuse it unchanged, and the 
registration points in `ProcessorReifier` and `DefaultManagementObjectStrategy` 
are additive branches. Please do not reach for a shared base class — inserting 
an abstract parent between `ChoiceDefinition` and `NoOutputDefinition` changes 
choice's `<xs:extension base=...>` in the generated XSD, which is itself a 
change to Choice.



##########
core/camel-core-processor/src/main/java/org/apache/camel/processor/ChoiceProcessor.java:
##########
@@ -71,7 +97,7 @@ public boolean process(final Exchange exchange, final 
AsyncCallback callback) {
             // as we should only pick one processor
             boolean matches = false;
             try {
-                matches = filter.matches(exchange);
+                matches = (selector == null || values.get(i).equals(selected)) 
&& filter.matches(exchange);

Review Comment:
   Two things here, both resolved by the split.
   
   **Choice's hot path.** `ChoiceProcessor.process()` is one of the 
most-executed methods in Camel. Adding a selector branch and a per-iteration 
`selector == null` guard to it, for a feature most routes never use, is exactly 
the kind of change a foundational EIP should not have to absorb.
   
   **It is also the slower design.** To reach branch N this builds a 
`FilterProcessor` per branch wrapping an `exchange -> true` predicate, so it 
costs N calls to `filter.matches()` plus N calls to 
`MessageHelper.resetStreamCache()` on the way — and those resets are pure 
waste, since a predicate that ignores the exchange never reads the body. A 
dedicated processor for the follow-up EIP can be a `Map<String, Processor>` 
lookup: O(1), no dummy predicates, no resets.
   
   One detail worth carrying forward when you build that processor: reusing 
`FilterProcessor` here is what keeps `getFilteredCount()` populated for the JMX 
`extendedInformation()` table, so a standalone processor will need to track 
per-branch counts itself.



##########
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:
   This file comes out with the scope split, but the point is worth carrying to 
the follow-up EIP: `definition.getSelector().getExpressionType()` is 
dereferenced without a null check, so a `<selector/>` element with no nested 
expression NPEs during route build instead of producing one of the clear 
`IllegalArgumentException`s this method raises for every other malformed 
combination just above.
   
   Given how carefully the surrounding validation reports the 
selector/predicate/precondition conflicts, a matching message would be 
consistent — something along the lines of "selector requires an expression".



##########
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:
   Locking on the `CamelContext` instance itself. It is a shared, publicly 
reachable object, so any other code that ever locks on the same monitor can 
deadlock against this, and the lock is held far wider than the map access it is 
protecting.
   
   I grepped `core/` and `components/` excluding tests: there is **no other 
non-test code in the codebase that synchronizes on a `CamelContext`**, so this 
is a new pattern rather than an existing convention. SonarCloud flags it as 
java:S2445 ("blocks should be synchronized on private final fields").
   
   A private static lock, or a compute-if-absent style access on the context 
plugin map, would scope this correctly. The same applies to `synchronized 
(context.getRegistry())` in `SemanticLanguage.adapter()` — likewise with no 
precedent elsewhere in the tree.



##########
dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/pom.xml:
##########
@@ -38,6 +38,10 @@
     </properties>
 
     <dependencies>
+        <dependency>
+            <groupId>org.apache.camel</groupId>
+            <artifactId>camel-semantic</artifactId>

Review Comment:
   **Blocking (the dependency-direction question you raised).** A compile-scope 
dependency here makes `camel-semantic` — and its `camel-core-languages` 
dependency — transitive for *every* YAML DSL user: JBang, Spring Boot, Quarkus, 
Kamelets. Combined with the unconditional 
`SemanticDefinitionDeserializer.configure(...)` call in 
`YamlRoutesBuilderLoader.preConfigureNode`, every YAML resource pays a semantic 
preparse pass whether or not it declares `semantic:`.
   
   The downstream consequence you asked about is real: `camel-quarkus-yaml-dsl` 
would pull in an artifact with no corresponding Quarkus extension, and the 
camel-spring-boot starter dependency graph changes for all YAML users.
   
   Suggested direction: resolve the `semantic` node lazily through 
`FactoryFinder`/SPI so this module keeps no compile-scope dependency on an AI 
component, and express the resource-wide preparse hook as an interface in 
`camel-yaml-dsl-common` that `camel-semantic` implements. That keeps the 
layering pointing the usual way — components depend on the DSL, not the reverse.



##########
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:
   `configure()` returns `false` unconditionally, so `defaultState` — and 
`adapter` on any path that does not go through `configureRaw` — always falls 
back to reflection binding in `PropertyBindingSupport`. Generated configurers 
exist precisely to keep configuration reflection-free for GraalVM native and 
`camel-main` fast startup; this hand-written one gives that up for the whole 
language.
   
   Two related points:
   
   - No other language in the repo ships a `PropertyConfigurer` at all — I 
could not find a single `*-language` entry under any 
`META-INF/services/org/apache/camel/configurer/` directory. This is a new 
pattern for languages, not an existing one being followed.
   - The two service files are hand-maintained under `src/main/resources`, 
whereas every other configurer service file in the tree lives under 
`src/generated/resources` and is owned by the package plugin.
   
   If the `configureRaw` hook is dropped per the `PropertyConfigurer` comment, 
this class and both service files disappear with it. If it is kept, 
`configure()` should at least bind `defaultState` properly rather than forcing 
reflection.



##########
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:
   This hand-rolls service-file discovery: `loadAllResourcesAsURL(...)`, then 
reading each URL and splitting lines on `#` to strip comments. The resource 
path chosen (`META-INF/services/org.apache.camel.semantic.SemanticAdapter`) is 
exactly the JDK `ServiceLoader` convention, and Camel already has 
`FactoryFinder` for the same job with class-resolver and injector integration.
   
   Two concrete consequences of doing it by hand:
   
   - GraalVM native will not register the resource automatically the way it 
does for `ServiceLoader`/`FactoryFinder` providers, so adapter discovery is 
likely to fail in native mode without an explicit resource registration.
   - The manual comment-stripping and trimming reimplements parsing rules 
`ServiceLoader` already specifies.
   
   If the reason for reading names rather than loading instances is to 
type-check before construction, `FactoryFinder.findClass(...)` gives you the 
`Class` without instantiating it.



##########
core/camel-api/src/main/java/org/apache/camel/spi/PropertyConfigurer.java:
##########
@@ -37,6 +37,24 @@
  */
 public interface PropertyConfigurer {
 
+    /**
+     * Optionally binds a value before resolving bean or class references. 
Property placeholders are resolved before
+     * this call when placeholder resolution was requested by the caller. This 
allows an option that owns reference
+     * resolution and lifecycle to retain the reference text. Returning false 
leaves the normal binding behavior
+     * unchanged.
+     *
+     * @param  camelContext the Camel context
+     * @param  target       the target instance
+     * @param  name         the property name
+     * @param  value        the value before reference resolution
+     * @param  ignoreCase   whether to ignore case for matching the property 
name
+     * @return              true if the property was configured
+     * @since               4.23
+     */
+    default boolean configureRaw(CamelContext camelContext, Object target, 
String name, Object value, boolean ignoreCase) {

Review Comment:
   **Blocking (the SPI question you raised).** I think this addition can be 
avoided entirely, which would be the better outcome.
   
   `PropertyBindingSupport.resolveValue` delegates string values to 
`resolveBean`, and `resolveBean` only transforms a string **that starts with 
`#`** — a plain FQCN or a plain registry bean name passes through binding 
completely untouched. So `setAdapter("com.foo.MyAdapter")` and 
`setAdapter("myAdapterBean")` already reach the language as raw text, with no 
instantiation and no SPI hook at all.
   
   That means this hook is needed *only* to preserve the `#bean:` and `#class:` 
prefixed spellings documented in `semantic-language.adoc`. If the language 
accepts just the unprefixed spellings, you get the same 
type-check-before-instantiation contract and can drop:
   
   - this `camel-api` SPI method and its `PropertyBindingSupport` call site,
   - the hand-written `SemanticLanguageConfigurer`,
   - the two hand-maintained configurer service files under 
`src/main/resources`.
   
   Weighed against that, the cost of keeping it is a permanent public SPI 
method on `camel-api` invoked on every `doSetPropertyValue` for every 
configurer in the framework, in order to serve one option of one preview 
language. I do not think that trade holds up — but flagging it for the PMC 
rather than deciding unilaterally, since you asked for agreement.



##########
dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/main/java/org/apache/camel/dsl/yaml/deserializers/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 located-diagnostic fix from the previous round reached `number(...)` but 
not the enum parsing in the same file. `type: foo` throws a bare 
`IllegalArgumentException: No enum constant 
org.apache.camel.semantic.SemanticQuestion.Type.FOO` with no question name, 
resource or line/column — exactly the failure mode that was just fixed for 
`threshold`.
   
   Same class of gap at lines 109-110: 
`UncertaintyPolicy.valueOf(asText(values.get("uncertaintyPolicy")).replace(...))`
 **NPEs** when the key is present but the value is empty, since `asText` 
returns null before `.replace` is called.
   
   Also unlocated in this file: `"Duplicate semantic question: "`, `"Unknown 
property in semantic question: "`, `"Semantic question type is required: "` and 
`"Semantic declaration requires only question"`. Schema validation catches most 
of these when enabled, but the parameterised tests added in this PR 
deliberately load without schema validation, so the path is reachable in 
practice.
   
   Wrapping these the same way `number(...)` now does — 
`YamlDeserializationException` with the offending `Node` — would make the whole 
file consistent.



##########
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:
   Same null dereference as in the reifier: 
`getDefinition().getSelector().getExpressionType().getLanguage()` assumes the 
selector always carries an expression type. Here it is worse than a build-time 
failure, because it surfaces while a JMX client is reading 
`extendedInformation()` — a monitoring call failing with an NPE is hard to 
diagnose.
   
   This file reverts entirely with the scope split; the null-safety point 
applies to the new MBean that the follow-up EIP will need.



-- 
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