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


##########
components/camel-kamelet/src/main/java/org/apache/camel/component/kamelet/Kamelet.java:
##########
@@ -184,6 +207,196 @@ 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;
+        for (Map.Entry<String, Object> entry : parameters.entrySet()) {
+            String name = entry.getKey();
+            String value = String.valueOf(entry.getValue());
+
+            result = removeQueryParameter(result, name);
+            result += result.contains("?") ? "&" : "?";
+            result += name + "=" + wrapRawIfNeeded(value);
+        }
+
+        return result;
+    }
+
+    /**
+     * Re-wraps values that contain URI-sensitive characters. RAW wrappers may 
already have been stripped by
+     * {@link URISupport#resolveRawParameterValues(Map)} before options reach 
this method.
+     */
+    private static String wrapRawIfNeeded(String value) {
+        if (isRawToken(value)) {
+            return value;
+        }
+        if (value.contains("&") || value.contains("=") || value.contains("?")) 
{

Review Comment:
   `+` and `%` are also decoded when the endpoint parses its query 
(`URIScanner.addParameter` calls `URLDecoder.decode` on values that are not 
RAW). The value has already lost its `RAW(...)` in 
`KameletComponent.createEndpoint`, so `sink.password=RAW(se+cret)` arrives as 
`se cret`. Please also wrap those two:
   
   ```suggestion
           if (value.contains("&") || value.contains("=") || value.contains("?")
                   || value.contains("+") || value.contains("%")) {
   ```
   
   A test like `sinkOptionsPreserveRawValuesWithUriSensitiveCharacters` with a 
`+` in the value would cover it.



##########
components/camel-kamelet/src/main/docs/kamelet-component.adoc:
##########
@@ -163,6 +163,52 @@ The usage of this Kamelet into a Camel route is going to 
be the same as any othe
 
 WARNING: beware of any potential circular reference you may introduce when 
using chain of Kamelets, in which case, the runtime will likely be idle 
consuming a high amount of resources.
 
+=== Advanced component endpoint options
+
+Kamelets expose a defined set of parameters, but the underlying Camel 
component may support additional endpoint options. You can pass these options 
when using a Kamelet by prefixing them with `source.` or `sink.`, depending on 
the type of Kamelet.
+
+The prefix is removed when the options are applied to the underlying endpoint 
URI. The options must be supported by the component used by that endpoint.
+
+==== Source Kamelets
+
+For source Kamelets, use the `source.` prefix to pass additional options to 
the underlying source component endpoint.
+
+For example:
+
+[source,java]
+----
+from("kamelet:my-source?source.bridgeErrorHandler=true")
+    .to("log:info");
+----
+
+In this example, `source.bridgeErrorHandler=true` passes 
`bridgeErrorHandler=true` to the source component endpoint. If the endpoint URI 
already contains the same option, the advanced option overrides its existing 
value.
+
+Property placeholders and RAW expressions in endpoint URIs are preserved when 
advanced options are applied. For example, `kafka:{{topic}}` or 
`aws2-s3:{{bucket}}?secretKey=RAW({{secret}})` remain intact.

Review Comment:
   This line breaks the "Validate documentation" CI job (`skipping reference to 
missing attribute: topic / bucket / secret`). Asciidoctor reads `{topic}`, 
`{bucket}` and `{secret}` inside the `{{...}}` as attribute references. A 
literal passthrough fixes it:
   
   ```suggestion
   Property placeholders and RAW expressions in endpoint URIs are preserved 
when advanced options are applied. For example, `+kafka:{{topic}}+` or 
`+aws2-s3:{{bucket}}?secretKey=RAW({{secret}})+` remain intact.
   ```
   
   Please regenerate the catalog copy 
(`catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/kamelet-component.adoc`)
 afterwards.



##########
components/camel-kamelet/src/main/java/org/apache/camel/component/kamelet/Kamelet.java:
##########
@@ -55,18 +63,33 @@ public final class Kamelet {
     public static final String PARENT_PROCESSOR_ID = "parentProcessorId";
     public static final String NO_ERROR_HANDLER = "noErrorHandler";
     public static final String BRIDGE_ERROR_HANDLER = "bridgeErrorHandler";
+    static final String ADVANCED_SOURCE_OPTIONS = "internal.advanced.source";
+    static final String ADVANCED_SINK_OPTIONS = "internal.advanced.sink";
 
     // use a running counter as uuid
     private static final UuidGenerator UUID = new SimpleUuidGenerator();
 
     private Kamelet() {
     }
 
+    private static boolean isKameletEndpoint(String uri, String endpoint) {
+        if (uri == null) {
+            return false;
+        }
+        return uri.equals("kamelet:" + endpoint)
+                || uri.startsWith("kamelet:" + endpoint + "?")
+                || uri.startsWith("kamelet:" + endpoint + "/")
+                || uri.equals("kamelet://" + endpoint)
+                || uri.startsWith("kamelet://" + endpoint + "?")
+                || uri.startsWith("kamelet://" + endpoint + "/");
+    }
+
     public static Predicate<String> startsWith(String prefix) {
         return item -> item.startsWith(prefix);
     }
 
-    public static String extractTemplateId(CamelContext context, String 
remaining, Map<String, Object> parameters) {
+    public static String extractTemplateId(
+            CamelContext context, String remaining, Map<String, Object> 
parameters) {

Review Comment:
   These changes are still unrelated to the feature: this re-wrapped signature, 
the same for `extractRouteId` and `extractKameletProperties`, the removed blank 
lines in the two env-var loops, the changed env-var javadoc example, and the 
reworded error-handler and "at least one sink" comments in `templateToRoute`. 
Please revert them so the diff only contains the feature. (The 
`isKameletEndpoint` changes in `templateToRoute` are fine to keep.)



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