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

Reply via email to