ramu11 commented on code in PR #27686:
URL: https://github.com/apache/camel/pull/27686#discussion_r4267313235
##########
components/camel-kamelet/src/main/java/org/apache/camel/component/kamelet/Kamelet.java:
##########
@@ -184,6 +206,215 @@ 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());
+ if (entry.getValue() != null) {
+
sb.append('=').append(wrapRawIfNeeded(String.valueOf(entry.getValue())));
+ }
+ sep = '&';
+ }
+
+ return sb.toString();
+ }
+
+ /**
+ * 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.
+ * <p>
+ * RAW(...) and RAW{...} have limitations when their closing delimiter is
followed by an ampersand. Avoid RAW{...}
+ * for values starting with '{' or ending with '}', as they may be
interpreted as property placeholders.
+ */
+ private static String wrapRawIfNeeded(String value) {
+ if (value == null || value.isEmpty() || isRawToken(value)) {
+ return value;
+ }
+ if (value.contains("&") || value.contains("=") || value.contains("?")
+ || value.contains("+") || value.contains("%")) {
+ if (value.contains(")&") && !value.startsWith("{") &&
!value.endsWith("}")
+ && !value.contains("}&")) {
+ return "RAW{" + value + "}";
+ }
+ return "RAW(" + value + ")";
Review Comment:
Thanks for the suggestion! I tried implementing this check in
`wrapRawIfNeeded`, but ran into an issue with how Camel parses URIs:
When passing a string containing an unescaped ampersand (like
`sink.filter=RAW({"filter":"(x)&y"})`), Camel's upstream URI parser splits the
query string on `&` *before* the value ever reaches `wrapRawIfNeeded`. As a
result, the value gets prematurely truncated into separate parameters (`(x` and
`y}`), meaning the `value.contains(")&")` check never sees the full combined
string to trigger the exception.
Because of this upstream URI parsing order, the edge-case test has been kept
`@Disabled` for now. Let me know if you'd like to handle it differently or if
you have a preferred way to intercept it before the URI parser splits it!
`[INFO] Running org.apache.camel.component.kamelet.KameletAdvancedOptionsTest
[ERROR] Tests run: 1, Failures: 1, Errors: 0, Skipped: 0, Time elapsed:
0.020 s <<< FAILURE! -- in
org.apache.camel.component.kamelet.KameletAdvancedOptionsTest
[ERROR]
org.apache.camel.component.kamelet.KameletAdvancedOptionsTest.sinkOptionsPreserveJsonRawValueWithClosingParenthesisAndAmpersand
-- Time elapsed: 0.019 s <<< FAILURE!
org.opentest4j.AssertionFailedError: Expected java.lang.Exception to be
thrown, but nothing was thrown.
at org.junit.jupiter.api.Assertions.assertThrows(Assertions.java:3234)
at
org.apache.camel.component.kamelet.KameletAdvancedOptionsTest.sinkOptionsPreserveJsonRawValueWithClosingParenthesisAndAmpersand(KameletAdvancedOptionsTest.java:852)
[INFO] Running org.apache.camel.component.kamelet.KameletAdvancedOptionsTest
[ERROR] Tests run: 1, Failures: 1, Errors: 0, Skipped: 0, Time elapsed:
0.016 s <<< FAILURE! -- in
org.apache.camel.component.kamelet.KameletAdvancedOptionsTest
[ERROR]
org.apache.camel.component.kamelet.KameletAdvancedOptionsTest.sinkOptionsPreserveJsonRawValueWithClosingParenthesisAndAmpersand
-- Time elapsed: 0.016 s <<< FAILURE!
org.opentest4j.AssertionFailedError: Expected java.lang.Exception to be
thrown, but nothing was thrown.
at org.junit.jupiter.api.Assertions.assertThrows(Assertions.java:3234)
at
org.apache.camel.component.kamelet.KameletAdvancedOptionsTest.sinkOptionsPreserveJsonRawValueWithClosingParenthesisAndAmpersand(KameletAdvancedOptionsTest.java:852)
[INFO]
[INFO] Results:
[INFO]
[ERROR] Failures:
[ERROR]
org.apache.camel.component.kamelet.KameletAdvancedOptionsTest.sinkOptionsPreserveJsonRawValueWithClosingParenthesisAndAmpersand
[ERROR] Run 1:
KameletAdvancedOptionsTest.sinkOptionsPreserveJsonRawValueWithClosingParenthesisAndAmpersand:852
Expected java.lang.Exception to be thrown, but nothing was thrown.
[ERROR] Run 2:
KameletAdvancedOptionsTest.sinkOptionsPreserveJsonRawValueWithClosingParenthesisAndAmpersand:852
Expected java.lang.Exception to be thrown, but nothing was thrown.
[ERROR] Run 3:
KameletAdvancedOptionsTest.sinkOptionsPreserveJsonRawValueWithClosingParenthesisAndAmpersand:852
Expected java.lang.Exception to be thrown, but nothing was thrown.
[INFO]
[INFO]
[ERROR] Tests run: 127, Failures: 1, Errors: 0, Skipped: 2
[INFO]
`
--
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]