diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/atmosphere-websocket.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/atmosphere-websocket.json index 181d8af69752b..8470227392767 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/atmosphere-websocket.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/atmosphere-websocket.json @@ -40,10 +40,10 @@ "deserializationFilter": { "index": 12, "kind": "property", "displayName": "Deserialization Filter", "group": "security", "label": "advanced,security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Sets an ObjectInputFilter pattern (jdk.serialFilter syntax) applied when deserializing Java objects from requests or responses with Content-Type application\/x-java-serialized-object (only used when allowJavaSerializedObject or transferException is enabled). When not set, the JVM-wide jdk.serialFilter is used if present; otherwise a conservative default filter denying java.net. and otherwise allowing java., javax. and org.apache.camel. packages is applied." } }, "headers": { - "websocket.connectionKey": { "index": 0, "kind": "header", "displayName": "", "group": "common", "label": "", "required": false, "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The connection key.", "constantName": "org.apache.camel.component.atmosphere.websocket.WebsocketConstants#CONNECTION_KEY" }, - "websocket.connectionKey.list": { "index": 1, "kind": "header", "displayName": "", "group": "common", "label": "", "required": false, "javaType": "java.util.List", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The list of connection keys.", "constantName": "org.apache.camel.component.atmosphere.websocket.WebsocketConstants#CONNECTION_KEY_LIST" }, - "websocket.eventType": { "index": 2, "kind": "header", "displayName": "", "group": "consumer", "label": "consumer", "required": false, "javaType": "int", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "important": true, "description": "The type of event received. It can be ONOPEN_EVENT_TYPE, ONERROR_EVENT_TYPE or ONCLOSE_EVENT_TYPE.", "constantName": "org.apache.camel.component.atmosphere.websocket.WebsocketConstants#EVENT_TYPE" }, - "websocket.errorType": { "index": 3, "kind": "header", "displayName": "", "group": "consumer", "label": "consumer", "required": false, "javaType": "int", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The type of error that occurred. It can be MESSAGE_NOT_SENT_ERROR_TYPE.", "constantName": "org.apache.camel.component.atmosphere.websocket.WebsocketConstants#ERROR_TYPE" } + "CamelAtmosphereWebsocketConnectionKey": { "index": 0, "kind": "header", "displayName": "", "group": "common", "label": "", "required": false, "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The connection key.", "constantName": "org.apache.camel.component.atmosphere.websocket.WebsocketConstants#CONNECTION_KEY" }, + "CamelAtmosphereWebsocketConnectionKeyList": { "index": 1, "kind": "header", "displayName": "", "group": "common", "label": "", "required": false, "javaType": "java.util.List", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The list of connection keys.", "constantName": "org.apache.camel.component.atmosphere.websocket.WebsocketConstants#CONNECTION_KEY_LIST" }, + "CamelAtmosphereWebsocketEventType": { "index": 2, "kind": "header", "displayName": "", "group": "consumer", "label": "consumer", "required": false, "javaType": "int", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "important": true, "description": "The type of event received. It can be ONOPEN_EVENT_TYPE, ONERROR_EVENT_TYPE or ONCLOSE_EVENT_TYPE.", "constantName": "org.apache.camel.component.atmosphere.websocket.WebsocketConstants#EVENT_TYPE" }, + "CamelAtmosphereWebsocketErrorType": { "index": 3, "kind": "header", "displayName": "", "group": "consumer", "label": "consumer", "required": false, "javaType": "int", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The type of error that occurred. It can be MESSAGE_NOT_SENT_ERROR_TYPE.", "constantName": "org.apache.camel.component.atmosphere.websocket.WebsocketConstants#ERROR_TYPE" } }, "properties": { "servicePath": { "index": 0, "kind": "path", "displayName": "Service Path", "group": "common", "label": "", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Name of websocket endpoint" }, diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/main/important-headers.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/main/important-headers.json index d5eefe63ecb68..059f30e1b87c3 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/main/important-headers.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/main/important-headers.json @@ -2,6 +2,7 @@ "CamelAggregatedCorrelationKey", "CamelAggregatedSize", "CamelAsteriskEventName", + "CamelAtmosphereWebsocketEventType", "CamelExecExitValue", "CamelFileLength", "CamelFileName", diff --git a/components/camel-atmosphere-websocket/pom.xml b/components/camel-atmosphere-websocket/pom.xml index 19fb80d1ab1d3..384c1f1cbb856 100644 --- a/components/camel-atmosphere-websocket/pom.xml +++ b/components/camel-atmosphere-websocket/pom.xml @@ -76,6 +76,11 @@ ${awaitility-version} test + + org.assertj + assertj-core + test + diff --git a/components/camel-atmosphere-websocket/src/generated/resources/META-INF/org/apache/camel/component/atmosphere/websocket/atmosphere-websocket.json b/components/camel-atmosphere-websocket/src/generated/resources/META-INF/org/apache/camel/component/atmosphere/websocket/atmosphere-websocket.json index 181d8af69752b..8470227392767 100644 --- a/components/camel-atmosphere-websocket/src/generated/resources/META-INF/org/apache/camel/component/atmosphere/websocket/atmosphere-websocket.json +++ b/components/camel-atmosphere-websocket/src/generated/resources/META-INF/org/apache/camel/component/atmosphere/websocket/atmosphere-websocket.json @@ -40,10 +40,10 @@ "deserializationFilter": { "index": 12, "kind": "property", "displayName": "Deserialization Filter", "group": "security", "label": "advanced,security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Sets an ObjectInputFilter pattern (jdk.serialFilter syntax) applied when deserializing Java objects from requests or responses with Content-Type application\/x-java-serialized-object (only used when allowJavaSerializedObject or transferException is enabled). When not set, the JVM-wide jdk.serialFilter is used if present; otherwise a conservative default filter denying java.net. and otherwise allowing java., javax. and org.apache.camel. packages is applied." } }, "headers": { - "websocket.connectionKey": { "index": 0, "kind": "header", "displayName": "", "group": "common", "label": "", "required": false, "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The connection key.", "constantName": "org.apache.camel.component.atmosphere.websocket.WebsocketConstants#CONNECTION_KEY" }, - "websocket.connectionKey.list": { "index": 1, "kind": "header", "displayName": "", "group": "common", "label": "", "required": false, "javaType": "java.util.List", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The list of connection keys.", "constantName": "org.apache.camel.component.atmosphere.websocket.WebsocketConstants#CONNECTION_KEY_LIST" }, - "websocket.eventType": { "index": 2, "kind": "header", "displayName": "", "group": "consumer", "label": "consumer", "required": false, "javaType": "int", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "important": true, "description": "The type of event received. It can be ONOPEN_EVENT_TYPE, ONERROR_EVENT_TYPE or ONCLOSE_EVENT_TYPE.", "constantName": "org.apache.camel.component.atmosphere.websocket.WebsocketConstants#EVENT_TYPE" }, - "websocket.errorType": { "index": 3, "kind": "header", "displayName": "", "group": "consumer", "label": "consumer", "required": false, "javaType": "int", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The type of error that occurred. It can be MESSAGE_NOT_SENT_ERROR_TYPE.", "constantName": "org.apache.camel.component.atmosphere.websocket.WebsocketConstants#ERROR_TYPE" } + "CamelAtmosphereWebsocketConnectionKey": { "index": 0, "kind": "header", "displayName": "", "group": "common", "label": "", "required": false, "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The connection key.", "constantName": "org.apache.camel.component.atmosphere.websocket.WebsocketConstants#CONNECTION_KEY" }, + "CamelAtmosphereWebsocketConnectionKeyList": { "index": 1, "kind": "header", "displayName": "", "group": "common", "label": "", "required": false, "javaType": "java.util.List", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The list of connection keys.", "constantName": "org.apache.camel.component.atmosphere.websocket.WebsocketConstants#CONNECTION_KEY_LIST" }, + "CamelAtmosphereWebsocketEventType": { "index": 2, "kind": "header", "displayName": "", "group": "consumer", "label": "consumer", "required": false, "javaType": "int", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "important": true, "description": "The type of event received. It can be ONOPEN_EVENT_TYPE, ONERROR_EVENT_TYPE or ONCLOSE_EVENT_TYPE.", "constantName": "org.apache.camel.component.atmosphere.websocket.WebsocketConstants#EVENT_TYPE" }, + "CamelAtmosphereWebsocketErrorType": { "index": 3, "kind": "header", "displayName": "", "group": "consumer", "label": "consumer", "required": false, "javaType": "int", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The type of error that occurred. It can be MESSAGE_NOT_SENT_ERROR_TYPE.", "constantName": "org.apache.camel.component.atmosphere.websocket.WebsocketConstants#ERROR_TYPE" } }, "properties": { "servicePath": { "index": 0, "kind": "path", "displayName": "Service Path", "group": "common", "label": "", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Name of websocket endpoint" }, diff --git a/components/camel-atmosphere-websocket/src/main/java/org/apache/camel/component/atmosphere/websocket/WebsocketConstants.java b/components/camel-atmosphere-websocket/src/main/java/org/apache/camel/component/atmosphere/websocket/WebsocketConstants.java index c83e7377a73ae..00842a37a30c7 100644 --- a/components/camel-atmosphere-websocket/src/main/java/org/apache/camel/component/atmosphere/websocket/WebsocketConstants.java +++ b/components/camel-atmosphere-websocket/src/main/java/org/apache/camel/component/atmosphere/websocket/WebsocketConstants.java @@ -24,17 +24,17 @@ public final class WebsocketConstants { @Metadata(description = "The connection key.", javaType = "java.lang.String") - public static final String CONNECTION_KEY = "websocket.connectionKey"; + public static final String CONNECTION_KEY = "CamelAtmosphereWebsocketConnectionKey"; @Metadata(description = "The list of connection keys.", javaType = "java.util.List") - public static final String CONNECTION_KEY_LIST = "websocket.connectionKey.list"; - public static final String SEND_TO_ALL = "websocket.sendToAll"; + public static final String CONNECTION_KEY_LIST = "CamelAtmosphereWebsocketConnectionKeyList"; + public static final String SEND_TO_ALL = "CamelAtmosphereWebsocketSendToAll"; @Metadata(label = "consumer", description = "The type of event received. It can be `ONOPEN_EVENT_TYPE`, `ONERROR_EVENT_TYPE` or `ONCLOSE_EVENT_TYPE`.", javaType = "int", important = true) - public static final String EVENT_TYPE = "websocket.eventType"; + public static final String EVENT_TYPE = "CamelAtmosphereWebsocketEventType"; @Metadata(label = "consumer", description = "The type of error that occurred. It can be `MESSAGE_NOT_SENT_ERROR_TYPE`.", javaType = "int") - public static final String ERROR_TYPE = "websocket.errorType"; + public static final String ERROR_TYPE = "CamelAtmosphereWebsocketErrorType"; public static final int ONOPEN_EVENT_TYPE = 1; public static final int ONCLOSE_EVENT_TYPE = 0; diff --git a/components/camel-atmosphere-websocket/src/test/java/org/apache/camel/component/atmosphere/websocket/WebsocketConstantsTest.java b/components/camel-atmosphere-websocket/src/test/java/org/apache/camel/component/atmosphere/websocket/WebsocketConstantsTest.java new file mode 100644 index 0000000000000..cdd27b93792a1 --- /dev/null +++ b/components/camel-atmosphere-websocket/src/test/java/org/apache/camel/component/atmosphere/websocket/WebsocketConstantsTest.java @@ -0,0 +1,63 @@ +/* + * 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.component.atmosphere.websocket; + +import org.apache.camel.http.base.HttpHeaderFilterStrategy; +import org.apache.camel.spi.HeaderFilterStrategy; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Verifies that the Exchange header constants follow the {@code Camel} naming convention, and + * therefore sit inside the {@code Camel} namespace that the inherited {@link HttpHeaderFilterStrategy} handles. + */ +class WebsocketConstantsTest { + + private static final String[] HEADERS = { + WebsocketConstants.CONNECTION_KEY, + WebsocketConstants.CONNECTION_KEY_LIST, + WebsocketConstants.SEND_TO_ALL, + WebsocketConstants.EVENT_TYPE, + WebsocketConstants.ERROR_TYPE + }; + + private final HeaderFilterStrategy strategy = new HttpHeaderFilterStrategy(); + + @Test + void testHeaderNamesFollowCamelNamingConvention() { + assertThat(WebsocketConstants.CONNECTION_KEY).isEqualTo("CamelAtmosphereWebsocketConnectionKey"); + assertThat(WebsocketConstants.CONNECTION_KEY_LIST).isEqualTo("CamelAtmosphereWebsocketConnectionKeyList"); + assertThat(WebsocketConstants.SEND_TO_ALL).isEqualTo("CamelAtmosphereWebsocketSendToAll"); + assertThat(WebsocketConstants.EVENT_TYPE).isEqualTo("CamelAtmosphereWebsocketEventType"); + assertThat(WebsocketConstants.ERROR_TYPE).isEqualTo("CamelAtmosphereWebsocketErrorType"); + } + + @Test + void testHeadersAreFilteredByInheritedHttpHeaderFilterStrategy() { + // WebsocketEndpoint extends ServletEndpoint, so it inherits HttpHeaderFilterStrategy, which filters + // the Camel namespace case-insensitively in both directions + for (String header : HEADERS) { + assertThat(strategy.applyFilterToExternalHeaders(header, "aValue", null)) + .as("%s should be filtered when mapping external headers in", header) + .isTrue(); + assertThat(strategy.applyFilterToCamelHeaders(header, "aValue", null)) + .as("%s should be filtered when mapping Camel headers out", header) + .isTrue(); + } + } +} diff --git a/core/camel-util/src/main/java/org/apache/camel/util/ImportantHeaderUtils.java b/core/camel-util/src/main/java/org/apache/camel/util/ImportantHeaderUtils.java index 14f22f6bd80eb..ca8c37f5fcfad 100644 --- a/core/camel-util/src/main/java/org/apache/camel/util/ImportantHeaderUtils.java +++ b/core/camel-util/src/main/java/org/apache/camel/util/ImportantHeaderUtils.java @@ -30,6 +30,7 @@ public final class ImportantHeaderUtils { "CamelAggregatedCorrelationKey", "CamelAggregatedSize", "CamelAsteriskEventName", + "CamelAtmosphereWebsocketEventType", "CamelExecExitValue", "CamelFileLength", "CamelFileName", diff --git a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc index 5bb33211e540a..323652455c692 100644 --- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc +++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc @@ -1651,3 +1651,62 @@ from("undertow:http://0.0.0.0:8080/foo?headerFilterStrategy=#myStrategy") Routes that already supply a custom `headerFilterStrategy` or a custom `undertowHttpBinding` are unaffected. + +=== camel-atmosphere-websocket - potential breaking change + +The Exchange header constants in `WebsocketConstants` have been renamed to follow the +Camel naming convention used across the rest of the component catalog. The Java field +names are unchanged; only the header string values have changed: + +[options="header"] +|=== +| Constant | Previous value | New value +| `WebsocketConstants.CONNECTION_KEY` | `websocket.connectionKey` | `CamelAtmosphereWebsocketConnectionKey` +| `WebsocketConstants.CONNECTION_KEY_LIST` | `websocket.connectionKey.list` | `CamelAtmosphereWebsocketConnectionKeyList` +| `WebsocketConstants.SEND_TO_ALL` | `websocket.sendToAll` | `CamelAtmosphereWebsocketSendToAll` +| `WebsocketConstants.EVENT_TYPE` | `websocket.eventType` | `CamelAtmosphereWebsocketEventType` +| `WebsocketConstants.ERROR_TYPE` | `websocket.errorType` | `CamelAtmosphereWebsocketErrorType` +|=== + +Routes that reference the constant symbolically (for example +`setHeader(WebsocketConstants.CONNECTION_KEY, ...)`) continue to work without changes. +Routes that set the header by its literal string value must be updated to use the new +value: + +._Java-only: updated atmosphere-websocket header names in route_ +[source,java] +---- +// before +from("direct:next") + .setHeader("websocket.connectionKey", header("myKey")) + .to("atmosphere-websocket:///servicepath"); + +// after +from("direct:next") + .setHeader("CamelAtmosphereWebsocketConnectionKey", header("myKey")) + .to("atmosphere-websocket:///servicepath"); +---- + +The sibling websocket components are not affected by this change: `camel-undertow` +keeps the `websocket.*` values in `UndertowConstants` (as documented in the 4.18 +upgrade guide), and `camel-vertx-websocket` keeps its `CamelVertxWebsocket.*` values. + +Note that `WebsocketConstants.SEND_TO_ALL` is not read by this component. Broadcast is +selected through the `sendToAll` endpoint option, not through the header; the constant +is renamed here only to keep the class internally consistent. + +==== Behaviour change: cross-transport propagation of the dispatch headers + +Because the renamed header values now begin with `Camel`, they are filtered by the +standard transport `HeaderFilterStrategy` (`HttpHeaderFilterStrategy`, +`JmsHeaderFilterStrategy`, etc.) when crossing a transport boundary, by design — +`Camel*` headers are framework-internal and are not propagated over the wire. + +Routes that bridge an external transport (HTTP, JMS, ...) into an +`atmosphere-websocket:` producer and let the sender choose the dispatch target via +headers must therefore carry those values in non-`Camel`-prefixed application headers +and map them to the corresponding `WebsocketConstants` value in the route between the +transport `from` and the `atmosphere-websocket:` `to`. Allowing untrusted senders to +drive `WebsocketConstants.CONNECTION_KEY_LIST`, which selects the target peers and +takes precedence over `CONNECTION_KEY`, without such a mapping step is not the intended +use of the component. diff --git a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/AtmosphereWebsocketEndpointBuilderFactory.java b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/AtmosphereWebsocketEndpointBuilderFactory.java index 11dd4528a31b2..d8885c7ea002e 100644 --- a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/AtmosphereWebsocketEndpointBuilderFactory.java +++ b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/AtmosphereWebsocketEndpointBuilderFactory.java @@ -1606,10 +1606,11 @@ public static class AtmosphereWebsocketHeaderNameBuilder { * * Group: common * - * @return the name of the header {@code websocket.connectionKey}. + * @return the name of the header {@code + * AtmosphereWebsocketConnectionKey}. */ - public String websocketConnectionkey() { - return "websocket.connectionKey"; + public String atmosphereWebsocketConnectionKey() { + return "CamelAtmosphereWebsocketConnectionKey"; } /** * The list of connection keys. @@ -1618,10 +1619,11 @@ public String websocketConnectionkey() { * * Group: common * - * @return the name of the header {@code websocket.connectionKey.list}. + * @return the name of the header {@code + * AtmosphereWebsocketConnectionKeyList}. */ - public String websocketConnectionkeyList() { - return "websocket.connectionKey.list"; + public String atmosphereWebsocketConnectionKeyList() { + return "CamelAtmosphereWebsocketConnectionKeyList"; } /** * The type of event received. It can be ONOPEN_EVENT_TYPE, @@ -1631,10 +1633,10 @@ public String websocketConnectionkeyList() { * * Group: consumer * - * @return the name of the header {@code websocket.eventType}. + * @return the name of the header {@code AtmosphereWebsocketEventType}. */ - public String websocketEventtype() { - return "websocket.eventType"; + public String atmosphereWebsocketEventType() { + return "CamelAtmosphereWebsocketEventType"; } /** * The type of error that occurred. It can be @@ -1644,10 +1646,10 @@ public String websocketEventtype() { * * Group: consumer * - * @return the name of the header {@code websocket.errorType}. + * @return the name of the header {@code AtmosphereWebsocketErrorType}. */ - public String websocketErrortype() { - return "websocket.errorType"; + public String atmosphereWebsocketErrorType() { + return "CamelAtmosphereWebsocketErrorType"; } } static AtmosphereWebsocketEndpointBuilder endpointBuilder(String componentName, String path) {