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


##########
components/camel-kamelet/src/main/java/org/apache/camel/component/kamelet/Kamelet.java:
##########
@@ -184,6 +206,207 @@ public static void 
extractKameletEnvironmentVariables(Map<String, Object> proper
         }
     }
 
+    /**
+     * Reads the original advanced options map stored under a single internal 
key.
+     */
+    private static Map<String, Object> extractAdvancedOptions(Map<String, 
Object> parameters, String key) {
+        Map<String, Object> options = new LinkedHashMap<>();
+        if (parameters == null) {
+            return options;
+        }
+
+        Object value = parameters.get(key);
+        if (value instanceof Map<?, ?> map) {
+            for (Map.Entry<?, ?> entry : map.entrySet()) {
+                if (entry.getKey() != null) {
+                    options.put(entry.getKey().toString(), entry.getValue());
+                }
+            }
+        }
+
+        return options;
+    }
+
+    /**
+     * Preserves the user-supplied option name. When both camelCase and 
kebab-case forms are present, the kebab-case
+     * form is kept so the same option is not appended twice.
+     */
+    private static Map<String, Object> normalizeParameterNames(Map<String, 
Object> parameters) {
+        Map<String, Object> normalized = new LinkedHashMap<>();
+        Map<String, String> seenOriginal = new HashMap<>();
+        Map<String, Boolean> seenIsCanonical = new HashMap<>();
+
+        if (parameters == null) {
+            return normalized;
+        }
+
+        for (Map.Entry<String, Object> entry : parameters.entrySet()) {
+            String originalName = entry.getKey();
+            String name = StringHelper.camelCaseToDash(originalName);
+            boolean isCanonical = originalName.equals(name);
+
+            String existingOriginal = seenOriginal.get(name);
+            if (existingOriginal == null) {
+                seenOriginal.put(name, originalName);
+                seenIsCanonical.put(name, isCanonical);
+                normalized.put(originalName, entry.getValue());
+            } else {
+                boolean existingIsCanonical = seenIsCanonical.get(name);
+                if (isCanonical && !existingIsCanonical) {
+                    normalized.remove(existingOriginal);
+                    seenOriginal.put(name, originalName);
+                    seenIsCanonical.put(name, isCanonical);
+                    normalized.put(originalName, entry.getValue());
+                }
+            }
+        }
+
+        return normalized;
+    }
+
+    /**
+     * Appends query parameters without parsing or re-encoding the endpoint 
URI. This preserves property placeholders
+     * and RAW expressions that may appear in the URI.
+     */
+    private static String appendParametersToURI(String uri, Map<String, 
Object> parameters) {
+        if (parameters == null || parameters.isEmpty()) {
+            return uri;
+        }
+
+        String result = uri;
+
+        // First remove all parameters that will be overridden.
+        for (String name : parameters.keySet()) {
+            result = removeQueryParameter(result, name);
+        }
+
+        // Then append the options to the preserved URI.
+        StringBuilder sb = new StringBuilder(result);
+        char sep = result.indexOf('?') < 0 ? '?' : '&';
+
+        for (Map.Entry<String, Object> entry : parameters.entrySet()) {
+            sb.append(sep)
+                    .append(entry.getKey())
+                    .append('=')
+                    .append(wrapRawIfNeeded(String.valueOf(entry.getValue())));
+            sep = '&';
+        }

Review Comment:
   This works now: overridden names are removed first, then the separator comes 
from what is left. Please add the two tests that would have caught the earlier 
versions:
   
   1. Two options, where the second one overrides the only existing parameter: 
`to("log:foo?level=WARN")` with `?sink.showAll=true&sink.level=INFO` must give 
`log:foo?showAll=true&level=INFO`.
   2. A value with `+` and `%`, for example `sink.password=RAW(se+cret%41)`, so 
the endpoint gets `se+cret%41` and not `se cret` / `secretA`. 
`sinkOptionsPreserveRawValuesWithUriSensitiveCharacters` only covers `&`.



##########
components/camel-kamelet/src/main/java/org/apache/camel/component/kamelet/Kamelet.java:
##########
@@ -214,11 +437,12 @@ public static RouteDefinition 
templateToRoute(RouteTemplateDefinition in, Map<St
             // (if ppid is null then it is a source kamelet)
             if (ppid != null) {
                 ProcessorDefinition<?> pro = mcc.getProcessorDefinition(ppid);
-                wrap = pro == null || 
ProcessorDefinitionHelper.shouldWrapInErrorHandler(mcc, pro, null,
-                        CamelContextHelper.parseBoolean(mcc, 
pro.getInheritErrorHandler()));
+                wrap = pro == null || 
ProcessorDefinitionHelper.shouldWrapInErrorHandler(
+                        mcc, pro, null, CamelContextHelper.parseBoolean(mcc, 
pro.getInheritErrorHandler()));
             }
+
             if (wrap && parent != null && parent.isKamelet() == null) {
-                // do not wrap if the parent is also a kamelet
+                // do not wrap if the parent is also a Kamelet

Review Comment:
   These lines are still unrelated to the feature (re-wrapped call, blank line, 
comment capitalisation). Please revert them to the original:
   
   ```suggestion
                   wrap = pro == null || 
ProcessorDefinitionHelper.shouldWrapInErrorHandler(mcc, pro, null,
                           CamelContextHelper.parseBoolean(mcc, 
pro.getInheritErrorHandler()));
               }
               if (wrap && parent != null && parent.isKamelet() == null) {
                   // do not wrap if the parent is also a kamelet
   ```



##########
components/camel-kamelet/src/main/java/org/apache/camel/component/kamelet/Kamelet.java:
##########
@@ -252,15 +539,17 @@ public static RouteDefinition 
templateToRoute(RouteTemplateDefinition in, Map<St
         int line = -1;
         boolean sink = false;
         Collection<ToDefinition> col = filterTypeInOutputs(def.getOutputs(), 
ToDefinition.class);
+
         for (ToDefinition to : col) {
-            if (to.getEndpointUri().startsWith("kamelet:sink") || 
to.getEndpointUri().startsWith("kamelet://sink")) {
+            if (isKameletEndpoint(to.getEndpointUri(), SINK_ID)) {
                 to.setUri("kamelet://sink?" + PARAM_ROUTE_ID + "=" + rid);
                 sink = true;
             }
             line = to.getLineNumber();
         }
+
         if (!sink) {
-            // this is appended and is used to go back to the kamelet that 
called me
+            // This is appended and is used to go back to the Kamelet that 
called me.

Review Comment:
   Same here: the reworded comment and the blank lines added before the `for` 
loop (line 542) and before `if (!sink)` (line 550) are unrelated. Please revert 
them; the comment was:
   
   ```suggestion
               // this is appended and is used to go back to the kamelet that 
called me
   ```



##########
docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc:
##########
@@ -202,3 +202,9 @@ throttling): the failure of the first object is then the 
failure of the poll, an
 In both cases the objects are no longer kept in the `inProgressRepository`; 
before, the object (or a new object with
 the same key) was skipped by all later polls until the endpoint was restarted. 
With the `fileName` option, a failed
 get still fails the poll.
+
+=== camel-kamelet - support advanced component and endpoint options
+
+Kamelets can now support advanced component and endpoint options when creating 
the underlying component and endpoint. This allows Kamelets to expose and use 
options beyond the standard options previously handled by the Kamelet 
component, including options that require special handling of endpoint URI 
parameters.
+
+Existing Kamelets that use these advanced options can now pass them through to 
the underlying component or endpoint. Review custom Kamelets that define or 
forward endpoint options to ensure that the options are intended for the 
underlying component or endpoint and that they do not conflict with 
Kamelet-specific options.

Review Comment:
   The upgrade guide is for migration, so this should say what changes for an 
existing user, and the `source.` / `sink.` prefixes are not mentioned at all. 
Something along these lines (adjust it if the syntax changes after the open 
design questions):
   
   ```suggestion
   === camel-kamelet - `source.*` and `sink.*` parameters are set on the 
endpoint of the Kamelet
   
   A `kamelet:` endpoint parameter whose name starts with `source.` or `sink.` 
is no longer passed to the Kamelet
   template as a parameter. Camel removes the prefix and sets the option on the 
endpoint of the Kamelet:
   `source.*` on the input endpoint of a source Kamelet, and `sink.*` on the 
single top-level static `to()` of a sink or
   action Kamelet. The option replaces a parameter of the same name that is 
already in that endpoint URI.
   
   A Kamelet template parameter whose name starts with `source.` or `sink.` can 
therefore no longer be set in the
   endpoint URI, and must be renamed. A route now fails at startup when it uses 
`sink.*` on a source Kamelet or
   `source.*` on a sink Kamelet, or when it uses `sink.*` on a Kamelet without 
exactly one top-level static `to()`, or
   with a `toD`, `enrich` or `pollEnrich`.
   ```



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