CAMEL-11149: SPI - Allow to plugin different headers map implementation
Project: http://git-wip-us.apache.org/repos/asf/camel/repo Commit: http://git-wip-us.apache.org/repos/asf/camel/commit/5b22da69 Tree: http://git-wip-us.apache.org/repos/asf/camel/tree/5b22da69 Diff: http://git-wip-us.apache.org/repos/asf/camel/diff/5b22da69 Branch: refs/heads/master Commit: 5b22da69159d06f71743d09625782595690508c1 Parents: 888fdf7 Author: Claus Ibsen <davscl...@apache.org> Authored: Wed May 24 15:22:25 2017 +0200 Committer: Claus Ibsen <davscl...@apache.org> Committed: Thu May 25 11:09:52 2017 +0200 ---------------------------------------------------------------------- .../java/org/apache/camel/CamelContext.java | 11 ++++ .../src/main/java/org/apache/camel/Message.java | 6 ++ .../camel/component/mock/MockEndpoint.java | 6 +- .../apache/camel/impl/DefaultCamelContext.java | 13 ++++ .../org/apache/camel/impl/DefaultExchange.java | 11 ++-- .../camel/impl/DefaultHeadersMapFactory.java | 47 ++++++++++++++ .../org/apache/camel/impl/DefaultMessage.java | 12 ++-- .../org/apache/camel/spi/HeadersMapFactory.java | 57 ++++++++++++++++ .../impl/DefaultHeadersMapFactoryTest.java | 68 ++++++++++++++++++++ 9 files changed, 217 insertions(+), 14 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/camel/blob/5b22da69/camel-core/src/main/java/org/apache/camel/CamelContext.java ---------------------------------------------------------------------- diff --git a/camel-core/src/main/java/org/apache/camel/CamelContext.java b/camel-core/src/main/java/org/apache/camel/CamelContext.java index 00702d1..ff0eb9a 100644 --- a/camel-core/src/main/java/org/apache/camel/CamelContext.java +++ b/camel-core/src/main/java/org/apache/camel/CamelContext.java @@ -53,6 +53,7 @@ import org.apache.camel.spi.EndpointStrategy; import org.apache.camel.spi.ExecutorServiceManager; import org.apache.camel.spi.FactoryFinder; import org.apache.camel.spi.FactoryFinderResolver; +import org.apache.camel.spi.HeadersMapFactory; import org.apache.camel.spi.InflightRepository; import org.apache.camel.spi.Injector; import org.apache.camel.spi.InterceptStrategy; @@ -1988,4 +1989,14 @@ public interface CamelContext extends SuspendableService, RuntimeConfiguration { */ SSLContextParameters getSSLContextParameters(); + /** + * Gets the {@link HeadersMapFactory} to use. + */ + HeadersMapFactory getHeadersMapFactory(); + + /** + * Sets a custom {@link HeadersMapFactory} to be used. + */ + void setHeadersMapFactory(HeadersMapFactory factory); + } http://git-wip-us.apache.org/repos/asf/camel/blob/5b22da69/camel-core/src/main/java/org/apache/camel/Message.java ---------------------------------------------------------------------- diff --git a/camel-core/src/main/java/org/apache/camel/Message.java b/camel-core/src/main/java/org/apache/camel/Message.java index 3b45316..d420a19 100644 --- a/camel-core/src/main/java/org/apache/camel/Message.java +++ b/camel-core/src/main/java/org/apache/camel/Message.java @@ -21,6 +21,8 @@ import java.util.Set; import java.util.function.Supplier; import javax.activation.DataHandler; +import org.apache.camel.spi.HeadersMapFactory; + /** * Implements the <a * href="http://camel.apache.org/message.html">Message</a> pattern and @@ -28,6 +30,8 @@ import javax.activation.DataHandler; * <p/> * See {@link org.apache.camel.impl.DefaultMessage DefaultMessage} for how headers * is represented in Camel using a {@link org.apache.camel.util.CaseInsensitiveMap CaseInsensitiveMap}. + * The implementation of the map can be configured by the {@link HeadersMapFactory} which can be set + * on the {@link CamelContext}. The default implementation uses the {@link org.apache.camel.util.CaseInsensitiveMap CaseInsensitiveMap}. * * @version */ @@ -172,6 +176,8 @@ public interface Message { * <p/> * See {@link org.apache.camel.impl.DefaultMessage DefaultMessage} for how headers * is represented in Camel using a {@link org.apache.camel.util.CaseInsensitiveMap CaseInsensitiveMap}. + * The implementation of the map can be configured by the {@link HeadersMapFactory} which can be set + * on the {@link CamelContext}. The default implementation uses the {@link org.apache.camel.util.CaseInsensitiveMap CaseInsensitiveMap}. * <p/> * <b>Important:</b> If you want to walk the returned {@link Map} and fetch all the keys and values, you should use * the {@link java.util.Map#entrySet()} method, which ensure you get the keys in the original case. http://git-wip-us.apache.org/repos/asf/camel/blob/5b22da69/camel-core/src/main/java/org/apache/camel/component/mock/MockEndpoint.java ---------------------------------------------------------------------- diff --git a/camel-core/src/main/java/org/apache/camel/component/mock/MockEndpoint.java b/camel-core/src/main/java/org/apache/camel/component/mock/MockEndpoint.java index a45d03c..fb10f5f 100644 --- a/camel-core/src/main/java/org/apache/camel/component/mock/MockEndpoint.java +++ b/camel-core/src/main/java/org/apache/camel/component/mock/MockEndpoint.java @@ -523,7 +523,7 @@ public class MockEndpoint extends DefaultEndpoint implements BrowsableEndpoint { */ public void expectedHeaderReceived(final String name, final Object value) { if (expectedHeaderValues == null) { - expectedHeaderValues = new CaseInsensitiveMap(); + expectedHeaderValues = getCamelContext().getHeadersMapFactory().newMap(); // we just wants to expects to be called once expects(new Runnable() { public void run() { @@ -1310,7 +1310,7 @@ public class MockEndpoint extends DefaultEndpoint implements BrowsableEndpoint { if (expectedHeaderValues != null) { if (actualHeaderValues == null) { - actualHeaderValues = new CaseInsensitiveMap(); + actualHeaderValues = getCamelContext().getHeadersMapFactory().newMap(); } if (in.hasHeaders()) { actualHeaderValues.putAll(in.getHeaders()); @@ -1319,7 +1319,7 @@ public class MockEndpoint extends DefaultEndpoint implements BrowsableEndpoint { if (expectedPropertyValues != null) { if (actualPropertyValues == null) { - actualPropertyValues = new ConcurrentHashMap<String, Object>(); + actualPropertyValues = getCamelContext().getHeadersMapFactory().newMap(); } actualPropertyValues.putAll(copy.getProperties()); } http://git-wip-us.apache.org/repos/asf/camel/blob/5b22da69/camel-core/src/main/java/org/apache/camel/impl/DefaultCamelContext.java ---------------------------------------------------------------------- diff --git a/camel-core/src/main/java/org/apache/camel/impl/DefaultCamelContext.java b/camel-core/src/main/java/org/apache/camel/impl/DefaultCamelContext.java index 13cf55b..7a1c772 100644 --- a/camel-core/src/main/java/org/apache/camel/impl/DefaultCamelContext.java +++ b/camel-core/src/main/java/org/apache/camel/impl/DefaultCamelContext.java @@ -136,6 +136,7 @@ import org.apache.camel.spi.EventNotifier; import org.apache.camel.spi.ExecutorServiceManager; import org.apache.camel.spi.FactoryFinder; import org.apache.camel.spi.FactoryFinderResolver; +import org.apache.camel.spi.HeadersMapFactory; import org.apache.camel.spi.InflightRepository; import org.apache.camel.spi.Injector; import org.apache.camel.spi.InterceptStrategy; @@ -235,6 +236,7 @@ public class DefaultCamelContext extends ServiceSupport implements ModelCamelCon private List<InterceptStrategy> interceptStrategies = new ArrayList<InterceptStrategy>(); private List<RoutePolicyFactory> routePolicyFactories = new ArrayList<RoutePolicyFactory>(); private Set<LogListener> logListeners = new LinkedHashSet<>(); + private HeadersMapFactory headersMapFactory = new DefaultHeadersMapFactory(); // special flags to control the first startup which can are special private volatile boolean firstStartDone; @@ -304,6 +306,7 @@ public class DefaultCamelContext extends ServiceSupport implements ModelCamelCon private final RuntimeCamelCatalog runtimeCamelCatalog = new DefaultRuntimeCamelCatalog(this, true); private SSLContextParameters sslContextParameters; private final ThreadLocal<Set<String>> componentsInCreation = ThreadLocal.withInitial(HashSet::new); + /** * Creates the {@link CamelContext} using {@link JndiRegistry} as registry, * but will silently fallback and use {@link SimpleRegistry} if JNDI cannot be used. @@ -4475,6 +4478,16 @@ public class DefaultCamelContext extends ServiceSupport implements ModelCamelCon return this.sslContextParameters; } + @Override + public HeadersMapFactory getHeadersMapFactory() { + return headersMapFactory; + } + + @Override + public void setHeadersMapFactory(HeadersMapFactory headersMapFactory) { + this.headersMapFactory = headersMapFactory; + } + protected Map<String, RouteService> getRouteServices() { return routeServices; } http://git-wip-us.apache.org/repos/asf/camel/blob/5b22da69/camel-core/src/main/java/org/apache/camel/impl/DefaultExchange.java ---------------------------------------------------------------------- diff --git a/camel-core/src/main/java/org/apache/camel/impl/DefaultExchange.java b/camel-core/src/main/java/org/apache/camel/impl/DefaultExchange.java index d1bbab2..0ee5e8f 100644 --- a/camel-core/src/main/java/org/apache/camel/impl/DefaultExchange.java +++ b/camel-core/src/main/java/org/apache/camel/impl/DefaultExchange.java @@ -128,24 +128,21 @@ public final class DefaultExchange implements Exchange { return exchange; } - private static Map<String, Object> safeCopyHeaders(Map<String, Object> headers) { + private Map<String, Object> safeCopyHeaders(Map<String, Object> headers) { if (headers == null) { return null; } - Map<String, Object> answer = new CaseInsensitiveMap(); - answer.putAll(headers); - return answer; + return context.getHeadersMapFactory().fromMap(headers); } @SuppressWarnings("unchecked") - private static Map<String, Object> safeCopyProperties(Map<String, Object> properties) { + private Map<String, Object> safeCopyProperties(Map<String, Object> properties) { if (properties == null) { return null; } - // TODO: properties should use same map kind as headers - Map<String, Object> answer = new ConcurrentHashMap<String, Object>(properties); + Map<String, Object> answer = context.getHeadersMapFactory().fromMap(properties); // safe copy message history using a defensive copy List<MessageHistory> history = (List<MessageHistory>) answer.remove(Exchange.MESSAGE_HISTORY); http://git-wip-us.apache.org/repos/asf/camel/blob/5b22da69/camel-core/src/main/java/org/apache/camel/impl/DefaultHeadersMapFactory.java ---------------------------------------------------------------------- diff --git a/camel-core/src/main/java/org/apache/camel/impl/DefaultHeadersMapFactory.java b/camel-core/src/main/java/org/apache/camel/impl/DefaultHeadersMapFactory.java new file mode 100644 index 0000000..efc7e5e --- /dev/null +++ b/camel-core/src/main/java/org/apache/camel/impl/DefaultHeadersMapFactory.java @@ -0,0 +1,47 @@ +/** + * 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.impl; + +import java.util.Map; + +import org.apache.camel.spi.HeadersMapFactory; +import org.apache.camel.util.CaseInsensitiveMap; + +/** + * Default {@link HeadersMapFactory} which uses the {@link org.apache.camel.util.CaseInsensitiveMap CaseInsensitiveMap}. + * This implementation uses a {@link org.apache.camel.util.CaseInsensitiveMap} storing the headers. + * This allows us to be able to lookup headers using case insensitive keys, making it easier for end users + * as they do not have to be worried about using exact keys. + * See more details at {@link org.apache.camel.util.CaseInsensitiveMap}. + */ +public class DefaultHeadersMapFactory implements HeadersMapFactory { + + @Override + public Map<String, Object> newMap() { + return new CaseInsensitiveMap(); + } + + @Override + public Map<String, Object> fromMap(Map<String, Object> map) { + return new CaseInsensitiveMap(map); + } + + @Override + public boolean isInstanceOf(Map<String, Object> map) { + return map instanceof CaseInsensitiveMap; + } +} http://git-wip-us.apache.org/repos/asf/camel/blob/5b22da69/camel-core/src/main/java/org/apache/camel/impl/DefaultMessage.java ---------------------------------------------------------------------- diff --git a/camel-core/src/main/java/org/apache/camel/impl/DefaultMessage.java b/camel-core/src/main/java/org/apache/camel/impl/DefaultMessage.java index 57bedfa..d6f44f4 100644 --- a/camel-core/src/main/java/org/apache/camel/impl/DefaultMessage.java +++ b/camel-core/src/main/java/org/apache/camel/impl/DefaultMessage.java @@ -24,7 +24,9 @@ import java.util.function.Supplier; import javax.activation.DataHandler; import org.apache.camel.Attachment; +import org.apache.camel.CamelContext; import org.apache.camel.Exchange; +import org.apache.camel.spi.HeadersMapFactory; import org.apache.camel.util.AttachmentMap; import org.apache.camel.util.CaseInsensitiveMap; import org.apache.camel.util.EndpointHelper; @@ -37,6 +39,8 @@ import org.apache.camel.util.ObjectHelper; * This allows us to be able to lookup headers using case insensitive keys, making it easier for end users * as they do not have to be worried about using exact keys. * See more details at {@link org.apache.camel.util.CaseInsensitiveMap}. + * The implementation of the map can be configured by the {@link HeadersMapFactory} which can be set + * on the {@link CamelContext}. The default implementation uses the {@link org.apache.camel.util.CaseInsensitiveMap CaseInsensitiveMap}. * * @version */ @@ -204,11 +208,11 @@ public class DefaultMessage extends MessageSupport { } public void setHeaders(Map<String, Object> headers) { - if (headers instanceof CaseInsensitiveMap) { + if (getExchange().getContext().getHeadersMapFactory().isInstanceOf(headers)) { this.headers = headers; } else { - // wrap it in a case insensitive map - this.headers = new CaseInsensitiveMap(headers); + // create a new map + this.headers = getExchange().getContext().getHeadersMapFactory().fromMap(headers); } } @@ -233,7 +237,7 @@ public class DefaultMessage extends MessageSupport { * the underlying inbound transport */ protected Map<String, Object> createHeaders() { - Map<String, Object> map = new CaseInsensitiveMap(); + Map<String, Object> map = getExchange().getContext().getHeadersMapFactory().newMap(); populateInitialHeaders(map); return map; } http://git-wip-us.apache.org/repos/asf/camel/blob/5b22da69/camel-core/src/main/java/org/apache/camel/spi/HeadersMapFactory.java ---------------------------------------------------------------------- diff --git a/camel-core/src/main/java/org/apache/camel/spi/HeadersMapFactory.java b/camel-core/src/main/java/org/apache/camel/spi/HeadersMapFactory.java new file mode 100644 index 0000000..bb4556d --- /dev/null +++ b/camel-core/src/main/java/org/apache/camel/spi/HeadersMapFactory.java @@ -0,0 +1,57 @@ +/** + * 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.spi; + +import java.util.Map; + +import org.apache.camel.Message; + +/** + * Factory to create the {@link Map} implementation to use for storing headers and properties + * on {@link Message} and {@link org.apache.camel.Exchange}. + * + * @see org.apache.camel.impl.DefaultHeadersMapFactory + */ +public interface HeadersMapFactory { + + /** + * Creates a new empty {@link Map} + * + * @return new empty map + */ + Map<String, Object> newMap(); + + /** + * Creates a new {@link Map} and copies over all the content from the existing map. + * <p/> + * The copy of the content should use defensive copy, so the returned map + * can add/remove/change the content without affecting the existing map. + * + * @param map existing map to copy over (must use defensive copy) + * @return new map with the content from the existing map + */ + Map<String, Object> fromMap(Map<String, Object> map); + + /** + * Whether the given {@link Map} implementation is created by this factory? + * + * @return <tt>true</tt> if created from this factory, <tt>false</tt> if not + */ + boolean isInstanceOf(Map<String, Object> map); + +} + http://git-wip-us.apache.org/repos/asf/camel/blob/5b22da69/camel-core/src/test/java/org/apache/camel/impl/DefaultHeadersMapFactoryTest.java ---------------------------------------------------------------------- diff --git a/camel-core/src/test/java/org/apache/camel/impl/DefaultHeadersMapFactoryTest.java b/camel-core/src/test/java/org/apache/camel/impl/DefaultHeadersMapFactoryTest.java new file mode 100644 index 0000000..c23d3e6 --- /dev/null +++ b/camel-core/src/test/java/org/apache/camel/impl/DefaultHeadersMapFactoryTest.java @@ -0,0 +1,68 @@ +/** + * 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.impl; + +import java.util.HashMap; +import java.util.Map; + +import junit.framework.TestCase; + +/** + * @version + */ +public class DefaultHeadersMapFactoryTest extends TestCase { + + public void testLookupCaseAgnostic() { + Map<String, Object> map = new DefaultHeadersMapFactory().newMap(); + assertNull(map.get("foo")); + + map.put("foo", "cheese"); + + assertEquals("cheese", map.get("foo")); + assertEquals("cheese", map.get("Foo")); + assertEquals("cheese", map.get("FOO")); + } + + public void testConstructFromOther() { + Map<String, Object> other = new DefaultHeadersMapFactory().newMap(); + other.put("Foo", "cheese"); + other.put("bar", 123); + + Map<String, Object> map = new DefaultHeadersMapFactory().fromMap(other); + + assertEquals("cheese", map.get("FOO")); + assertEquals("cheese", map.get("foo")); + assertEquals("cheese", map.get("Foo")); + + assertEquals(123, map.get("BAR")); + assertEquals(123, map.get("bar")); + assertEquals(123, map.get("BaR")); + } + + public void testIsInstance() { + Map<String, Object> map = new DefaultHeadersMapFactory().newMap(); + + Map<String, Object> other = new DefaultHeadersMapFactory().fromMap(map); + other.put("Foo", "cheese"); + other.put("bar", 123); + + assertTrue(new DefaultHeadersMapFactory().isInstanceOf(map)); + assertTrue(new DefaultHeadersMapFactory().isInstanceOf(other)); + assertFalse(new DefaultHeadersMapFactory().isInstanceOf(new HashMap<>())); + } + +} \ No newline at end of file