This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push:
new 3790c927ea3e CAMEL-25229: camel-hazelcast - warn and document when a
user-supplied config has no serialization filter (#27255)
3790c927ea3e is described below
commit 3790c927ea3e6a53c5eb70c41ce99bf82895fd5d
Author: Andrea Cosentino <[email protected]>
AuthorDate: Fri Oct 2 09:53:00 2026 +0200
CAMEL-25229: camel-hazelcast - warn and document when a user-supplied
config has no serialization filter (#27255)
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
---
.../camel/catalog/docs/hazelcast-summary.adoc | 59 ++++++++
.../src/main/docs/hazelcast-summary.adoc | 59 ++++++++
.../hazelcast/HazelcastDefaultComponent.java | 27 ++++
...stUserConfigSerializationFilterWarningTest.java | 168 +++++++++++++++++++++
.../ROOT/pages/camel-4x-upgrade-guide-4_23.adoc | 6 +
5 files changed, 319 insertions(+)
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/hazelcast-summary.adoc
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/hazelcast-summary.adoc
index 2ae28709a40e..8db9e561f2e6 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/hazelcast-summary.adoc
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/hazelcast-summary.adoc
@@ -136,4 +136,63 @@ In the example above we set up this for the hazelcast map
component and setup ha
</bean>
------------------------------------------------------------------------------
+== Java serialization filter
+
+Hazelcast stores objects that implement `Serializable` or `Externalizable`
using Java serialization, and deserializes
+them when Camel reads them back from a map, queue, topic or any other data
structure. A Hazelcast
+`JavaSerializationFilterConfig` limits which classes Hazelcast deserializes.
+
+When Camel creates the Hazelcast instance itself, it applies a default filter.
The default allows class names that
+start with `java.`, `javax.` or `org.apache.camel.`, and rejects those that
start with `java.net.`. Camel creates the
+instance itself when:
+
+* an endpoint sets none of `hazelcastInstance`, `hazelcastInstanceName`,
`hazelcastConfig` or `hazelcastConfigUri`,
+and no instance is configured on the component;
+* a Hazelcast aggregation repository, idempotent repository, key-value
repository or route policy starts its own
+instance because none was given to it.
+
+Camel uses a configuration you supply as it is: a `Config` or `ClientConfig`
bean set with `hazelcastConfig`, a file
+set with `hazelcastConfigUri`, or an existing instance set with
`hazelcastInstance` or found with
+`hazelcastInstanceName`. Hazelcast's own default configuration declares no
serialization filter, so declare one in
+your configuration, and make it cover the classes your application stores in
the cluster:
+
+[source,xml]
+----
+<hazelcast xmlns="http://www.hazelcast.com/schema/config">
+ <serialization>
+ <java-serialization-filter>
+ <whitelist>
+ <prefix>java.</prefix>
+ <prefix>javax.</prefix>
+ <prefix>org.apache.camel.</prefix>
+ <prefix>com.example.model.</prefix>
+ </whitelist>
+ <blacklist>
+ <prefix>java.net.</prefix>
+ </blacklist>
+ </java-serialization-filter>
+ </serialization>
+</hazelcast>
+----
+
+A `hazelcast-client` configuration takes the same `serialization` element. In
Java, set the filter on the
+`SerializationConfig` of a `Config` or a `ClientConfig`:
+
+[source,java]
+----
+JavaSerializationFilterConfig filter = new JavaSerializationFilterConfig();
+filter.setWhitelist(new ClassFilter().addPrefixes("java.", "javax.",
"org.apache.camel.", "com.example.model."));
+filter.setBlacklist(new ClassFilter().addPrefixes("java.net."));
+
+Config config = new Config();
+config.getSerializationConfig().setJavaSerializationFilterConfig(filter);
+----
+
+Alternatively, set a JVM-wide filter with the `jdk.serialFilter` system
property, for example
+`+-Djdk.serialFilter='!java.net.**;java.**;javax.**;org.apache.camel.**;com.example.model.**;!*'+`.
It applies to all
+Java deserialization in the JVM, not only to Hazelcast.
+
+When Camel starts a Hazelcast member or client from a `Config` or
`ClientConfig` you supplied that declares no
+filter, and no JVM-wide filter is set, it logs a WARN.
+
diff --git a/components/camel-hazelcast/src/main/docs/hazelcast-summary.adoc
b/components/camel-hazelcast/src/main/docs/hazelcast-summary.adoc
index 2ae28709a40e..8db9e561f2e6 100644
--- a/components/camel-hazelcast/src/main/docs/hazelcast-summary.adoc
+++ b/components/camel-hazelcast/src/main/docs/hazelcast-summary.adoc
@@ -136,4 +136,63 @@ In the example above we set up this for the hazelcast map
component and setup ha
</bean>
------------------------------------------------------------------------------
+== Java serialization filter
+
+Hazelcast stores objects that implement `Serializable` or `Externalizable`
using Java serialization, and deserializes
+them when Camel reads them back from a map, queue, topic or any other data
structure. A Hazelcast
+`JavaSerializationFilterConfig` limits which classes Hazelcast deserializes.
+
+When Camel creates the Hazelcast instance itself, it applies a default filter.
The default allows class names that
+start with `java.`, `javax.` or `org.apache.camel.`, and rejects those that
start with `java.net.`. Camel creates the
+instance itself when:
+
+* an endpoint sets none of `hazelcastInstance`, `hazelcastInstanceName`,
`hazelcastConfig` or `hazelcastConfigUri`,
+and no instance is configured on the component;
+* a Hazelcast aggregation repository, idempotent repository, key-value
repository or route policy starts its own
+instance because none was given to it.
+
+Camel uses a configuration you supply as it is: a `Config` or `ClientConfig`
bean set with `hazelcastConfig`, a file
+set with `hazelcastConfigUri`, or an existing instance set with
`hazelcastInstance` or found with
+`hazelcastInstanceName`. Hazelcast's own default configuration declares no
serialization filter, so declare one in
+your configuration, and make it cover the classes your application stores in
the cluster:
+
+[source,xml]
+----
+<hazelcast xmlns="http://www.hazelcast.com/schema/config">
+ <serialization>
+ <java-serialization-filter>
+ <whitelist>
+ <prefix>java.</prefix>
+ <prefix>javax.</prefix>
+ <prefix>org.apache.camel.</prefix>
+ <prefix>com.example.model.</prefix>
+ </whitelist>
+ <blacklist>
+ <prefix>java.net.</prefix>
+ </blacklist>
+ </java-serialization-filter>
+ </serialization>
+</hazelcast>
+----
+
+A `hazelcast-client` configuration takes the same `serialization` element. In
Java, set the filter on the
+`SerializationConfig` of a `Config` or a `ClientConfig`:
+
+[source,java]
+----
+JavaSerializationFilterConfig filter = new JavaSerializationFilterConfig();
+filter.setWhitelist(new ClassFilter().addPrefixes("java.", "javax.",
"org.apache.camel.", "com.example.model."));
+filter.setBlacklist(new ClassFilter().addPrefixes("java.net."));
+
+Config config = new Config();
+config.getSerializationConfig().setJavaSerializationFilterConfig(filter);
+----
+
+Alternatively, set a JVM-wide filter with the `jdk.serialFilter` system
property, for example
+`+-Djdk.serialFilter='!java.net.**;java.**;javax.**;org.apache.camel.**;com.example.model.**;!*'+`.
It applies to all
+Java deserialization in the JVM, not only to Hazelcast.
+
+When Camel starts a Hazelcast member or client from a `Config` or
`ClientConfig` you supplied that declares no
+filter, and no JVM-wide filter is set, it logs a WARN.
+
diff --git
a/components/camel-hazelcast/src/main/java/org/apache/camel/component/hazelcast/HazelcastDefaultComponent.java
b/components/camel-hazelcast/src/main/java/org/apache/camel/component/hazelcast/HazelcastDefaultComponent.java
index 636d4af66318..9df779876a63 100644
---
a/components/camel-hazelcast/src/main/java/org/apache/camel/component/hazelcast/HazelcastDefaultComponent.java
+++
b/components/camel-hazelcast/src/main/java/org/apache/camel/component/hazelcast/HazelcastDefaultComponent.java
@@ -17,6 +17,7 @@
package org.apache.camel.component.hazelcast;
import java.io.InputStream;
+import java.io.ObjectInputFilter;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
@@ -25,6 +26,7 @@ import com.hazelcast.client.HazelcastClient;
import com.hazelcast.client.config.ClientConfig;
import com.hazelcast.client.config.XmlClientConfigBuilder;
import com.hazelcast.config.Config;
+import com.hazelcast.config.SerializationConfig;
import com.hazelcast.config.XmlConfigBuilder;
import com.hazelcast.core.Hazelcast;
import com.hazelcast.core.HazelcastInstance;
@@ -134,6 +136,7 @@ public abstract class HazelcastDefaultComponent extends
DefaultComponent {
protected HazelcastInstance getOrCreateHzInstance(CamelContext context,
Map<String, Object> parameters) throws Exception {
HazelcastInstance hzInstance = null;
Config config = null;
+ boolean userConfig = false;
// Query param named 'hazelcastInstance' (if exists) overrides the
instance that was set
hzInstance = resolveAndRemoveReferenceParameter(parameters,
HAZELCAST_INSTANCE_PARAM, HazelcastInstance.class);
@@ -167,6 +170,7 @@ public abstract class HazelcastDefaultComponent extends
DefaultComponent {
hzInstance = Hazelcast.newHazelcastInstance(config);
} else if (config != null) {
+ userConfig = true;
if (ObjectHelper.isNotEmpty(config.getInstanceName())) {
hzInstance =
Hazelcast.getOrCreateHazelcastInstance(config);
} else {
@@ -177,6 +181,9 @@ public abstract class HazelcastDefaultComponent extends
DefaultComponent {
if (hzInstance != null) {
if (this.customHazelcastInstances.add(hzInstance)) {
LOGGER.debug("Add managed HZ instance {}",
hzInstance.getName());
+ if (userConfig) {
+
warnIfNoSerializationFilter(config.getSerializationConfig(),
hzInstance.getName());
+ }
}
}
}
@@ -188,6 +195,7 @@ public abstract class HazelcastDefaultComponent extends
DefaultComponent {
throws Exception {
HazelcastInstance hzInstance = null;
ClientConfig config = null;
+ boolean userConfig = false;
// Query param named 'hazelcastInstance' (if exists) overrides the
instance that was set
hzInstance = resolveAndRemoveReferenceParameter(parameters,
HAZELCAST_INSTANCE_PARAM, HazelcastInstance.class);
@@ -221,16 +229,35 @@ public abstract class HazelcastDefaultComponent extends
DefaultComponent {
hzInstance = HazelcastClient.newHazelcastClient(config);
} else if (config != null) {
+ userConfig = true;
hzInstance = HazelcastClient.newHazelcastClient(config);
}
if (hzInstance != null) {
if (this.customHazelcastInstances.add(hzInstance)) {
LOGGER.debug("Add managed HZ instance {}",
hzInstance.getName());
+ if (userConfig) {
+
warnIfNoSerializationFilter(config.getSerializationConfig(),
hzInstance.getName());
+ }
}
}
}
return hzInstance == null ? hazelcastInstance : hzInstance;
}
+
+ /**
+ * Camel applies its default serialization filter only to the
configurations it builds itself, and uses a
+ * user-supplied configuration unchanged, so tell the user when that
configuration restricts nothing.
+ */
+ private static void warnIfNoSerializationFilter(SerializationConfig
serializationConfig, String instanceName) {
+ if (serializationConfig.getJavaSerializationFilterConfig() == null
+ && ObjectInputFilter.Config.getSerialFilter() == null) {
+ LOGGER.warn("The user-supplied configuration of Hazelcast instance
{} declares no Java serialization filter"
+ + " (JavaSerializationFilterConfig) and no JVM-wide
jdk.serialFilter is set, so Java deserialization"
+ + " in this instance is not restricted. Declare a
java-serialization-filter in the configuration,"
+ + " see the camel-hazelcast documentation.",
+ instanceName);
+ }
+ }
}
diff --git
a/components/camel-hazelcast/src/test/java/org/apache/camel/component/hazelcast/HazelcastUserConfigSerializationFilterWarningTest.java
b/components/camel-hazelcast/src/test/java/org/apache/camel/component/hazelcast/HazelcastUserConfigSerializationFilterWarningTest.java
new file mode 100644
index 000000000000..38470b3b66e9
--- /dev/null
+++
b/components/camel-hazelcast/src/test/java/org/apache/camel/component/hazelcast/HazelcastUserConfigSerializationFilterWarningTest.java
@@ -0,0 +1,168 @@
+/*
+ * 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.hazelcast;
+
+import java.util.List;
+import java.util.UUID;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import com.hazelcast.client.HazelcastClient;
+import com.hazelcast.client.config.ClientConfig;
+import com.hazelcast.cluster.Address;
+import com.hazelcast.config.ClassFilter;
+import com.hazelcast.config.Config;
+import com.hazelcast.config.JavaSerializationFilterConfig;
+import com.hazelcast.config.JoinConfig;
+import com.hazelcast.core.Hazelcast;
+import org.apache.camel.component.hazelcast.map.HazelcastMapComponent;
+import org.apache.camel.impl.DefaultCamelContext;
+import org.apache.camel.support.SimpleRegistry;
+import org.apache.camel.test.AvailablePortFinder;
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.Logger;
+import org.apache.logging.log4j.core.appender.AbstractAppender;
+import org.apache.logging.log4j.core.config.Property;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+class HazelcastUserConfigSerializationFilterWarningTest {
+
+ private final List<String> warnings = new CopyOnWriteArrayList<>();
+ private final AbstractAppender appender
+ = new AbstractAppender("SerializationFilterWarning", null, null,
true, Property.EMPTY_ARRAY) {
+ @Override
+ public void append(LogEvent event) {
+ if (event.getLevel() == Level.WARN) {
+ warnings.add(event.getMessage().getFormattedMessage());
+ }
+ }
+ };
+ private final SimpleRegistry registry = new SimpleRegistry();
+ private DefaultCamelContext context;
+
+ @BeforeEach
+ void captureWarnings() {
+ appender.start();
+ componentLogger().addAppender(appender);
+ // addAppender creates a non-additive logger config, which would hide
this logger from the root appenders
+ componentLogger().setAdditive(true);
+ }
+
+ @AfterEach
+ void tearDown() {
+ componentLogger().removeAppender(appender);
+ appender.stop();
+ if (context != null) {
+ context.stop();
+ }
+ HazelcastClient.shutdownAll();
+ Hazelcast.shutdownAll();
+ }
+
+ @Test
+ void warnsOnceForUserConfigWithoutFilter() {
+ try (AvailablePortFinder.Port port = AvailablePortFinder.find()) {
+ Config config = memberConfig(port.getPort());
+ registry.bind("userConfig", config);
+ startContext(new HazelcastMapComponent());
+
+
context.getEndpoint("hazelcast-map:cache-1?hazelcastConfig=#userConfig");
+
context.getEndpoint("hazelcast-map:cache-2?hazelcastConfig=#userConfig");
+
+ assertEquals(1, warningsFor(config.getInstanceName()));
+ }
+ }
+
+ @Test
+ void doesNotWarnForUserConfigWithFilter() {
+ try (AvailablePortFinder.Port port = AvailablePortFinder.find()) {
+ Config config = memberConfig(port.getPort());
+ JavaSerializationFilterConfig filter = new
JavaSerializationFilterConfig();
+ filter.setWhitelist(new ClassFilter().addPrefixes("java.",
"org.apache.camel."));
+
config.getSerializationConfig().setJavaSerializationFilterConfig(filter);
+ registry.bind("userConfig", config);
+ startContext(new HazelcastMapComponent());
+
+
context.getEndpoint("hazelcast-map:cache?hazelcastConfig=#userConfig");
+
+ assertEquals(0, warningsFor(config.getInstanceName()));
+ }
+ }
+
+ @Test
+ void doesNotWarnForCamelBuiltConfig() {
+ startContext(new HazelcastMapComponent());
+
+ HazelcastDefaultEndpoint endpoint = (HazelcastDefaultEndpoint)
context.getEndpoint("hazelcast-map:cache");
+
+ assertEquals(0,
warningsFor(endpoint.getHazelcastInstance().getName()));
+ }
+
+ @Test
+ void warnsForUserClientConfigWithoutFilter() {
+ try (AvailablePortFinder.Port port = AvailablePortFinder.find()) {
+ Config memberConfig = memberConfig(port.getPort());
+ Address member =
Hazelcast.newHazelcastInstance(memberConfig).getCluster().getLocalMember().getAddress();
+
+ ClientConfig clientConfig = new ClientConfig();
+ clientConfig.setInstanceName("user-client-config-" +
UUID.randomUUID());
+ clientConfig.setClusterName(memberConfig.getClusterName());
+ clientConfig.getNetworkConfig().addAddress(member.getHost() + ":"
+ member.getPort());
+
clientConfig.getConnectionStrategyConfig().getConnectionRetryConfig().setClusterConnectTimeoutMillis(30000);
+ registry.bind("userClientConfig", clientConfig);
+
+ HazelcastMapComponent component = new HazelcastMapComponent();
+
component.setHazelcastMode(HazelcastConstants.HAZELCAST_CLIENT_MODE);
+ startContext(component);
+
+
context.getEndpoint("hazelcast-map:cache?hazelcastConfig=#userClientConfig");
+
+ assertEquals(1, warningsFor(clientConfig.getInstanceName()));
+ }
+ }
+
+ private void startContext(HazelcastMapComponent component) {
+ context = new DefaultCamelContext(registry);
+ context.addComponent("hazelcast-map", component);
+ context.start();
+ }
+
+ private long warningsFor(String instanceName) {
+ return warnings.stream().filter(message ->
message.contains(instanceName)).count();
+ }
+
+ private static Logger componentLogger() {
+ return (Logger) LogManager.getLogger(HazelcastDefaultComponent.class);
+ }
+
+ private static Config memberConfig(int port) {
+ Config config = new Config();
+ config.setInstanceName("user-config-" + UUID.randomUUID());
+ config.setClusterName("user-config-" + UUID.randomUUID());
+ config.getNetworkConfig().setPort(port);
+ JoinConfig join = config.getNetworkConfig().getJoin();
+ join.getMulticastConfig().setEnabled(false);
+ join.getTcpIpConfig().setEnabled(false);
+ join.getAutoDetectionConfig().setEnabled(false);
+ return config;
+ }
+}
diff --git
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
index c15293a15c68..2e4dec9ba18a 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
@@ -1491,6 +1491,12 @@ The same default is now also applied to the
`ClientConfig` that Camel builds for
endpoints, when neither a referenced `ClientConfig` nor `hazelcastConfigUri`
is supplied. Client mode
previously behaved differently from node mode for an otherwise identical
endpoint configuration.
+Camel still uses a user-supplied `Config` or `ClientConfig` (a
`hazelcastConfig` bean or a
+`hazelcastConfigUri` file) unchanged. It now logs a WARN when it starts a
Hazelcast member or client from
+such a configuration that declares no `JavaSerializationFilterConfig`, unless
a JVM-wide `jdk.serialFilter`
+is set. To remove the warning, declare a `java-serialization-filter` in the
configuration, as described in
+the xref:components::hazelcast-summary.adoc[Hazelcast component] documentation.
+
=== camel-netty - NettyConverter.toByteArray returns a copy of the buffer's
readable bytes
The `ByteBuf` to `byte[]` type converter (`NettyConverter.toByteArray`, also
used when converting a Netty