You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by da...@apache.org on 2014/12/08 18:31:56 UTC

[1/2] camel git commit: CAMEL-8133: rest-dsl make it easy to enable and configure CORS. Also fixed an issue with the skip option.

Repository: camel
Updated Branches:
  refs/heads/camel-2.14.x a0100ba07 -> 4cbee5f11
  refs/heads/master 8cfabc840 -> 7cbd69884


CAMEL-8133: rest-dsl make it easy to enable and configure CORS. Also fixed an issue with the skip option.


Project: http://git-wip-us.apache.org/repos/asf/camel/repo
Commit: http://git-wip-us.apache.org/repos/asf/camel/commit/7cbd6988
Tree: http://git-wip-us.apache.org/repos/asf/camel/tree/7cbd6988
Diff: http://git-wip-us.apache.org/repos/asf/camel/diff/7cbd6988

Branch: refs/heads/master
Commit: 7cbd69884385eb340e74dc215c4247deff7c2ef2
Parents: 8cfabc8
Author: Claus Ibsen <da...@apache.org>
Authored: Mon Dec 8 18:31:27 2014 +0100
Committer: Claus Ibsen <da...@apache.org>
Committed: Mon Dec 8 18:31:27 2014 +0100

----------------------------------------------------------------------
 .../camel/model/rest/RestBindingDefinition.java | 29 ++++++--
 .../model/rest/RestConfigurationDefinition.java | 53 +++++++++++++
 .../apache/camel/model/rest/RestDefinition.java | 35 ++++++++-
 .../apache/camel/model/rest/VerbDefinition.java | 11 +++
 .../processor/binding/RestBindingProcessor.java | 63 +++++++++++++++-
 .../org/apache/camel/spi/RestConfiguration.java | 51 +++++++++++++
 .../rest/FromRestGetCorsCustomTest.java         | 78 ++++++++++++++++++++
 .../component/rest/FromRestGetCorsTest.java     | 77 +++++++++++++++++++
 8 files changed, 390 insertions(+), 7 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/camel/blob/7cbd6988/camel-core/src/main/java/org/apache/camel/model/rest/RestBindingDefinition.java
----------------------------------------------------------------------
diff --git a/camel-core/src/main/java/org/apache/camel/model/rest/RestBindingDefinition.java b/camel-core/src/main/java/org/apache/camel/model/rest/RestBindingDefinition.java
index 3f07d26..abe2a06 100644
--- a/camel-core/src/main/java/org/apache/camel/model/rest/RestBindingDefinition.java
+++ b/camel-core/src/main/java/org/apache/camel/model/rest/RestBindingDefinition.java
@@ -54,6 +54,9 @@ public class RestBindingDefinition extends NoOutputDefinition<RestBindingDefinit
     @XmlAttribute
     private Boolean skipBindingOnErrorCode;
 
+    @XmlAttribute
+    private Boolean enableCORS;
+
     @Override
     public String toString() {
         return "RestBinding";
@@ -69,18 +72,26 @@ public class RestBindingDefinition extends NoOutputDefinition<RestBindingDefinit
 
         CamelContext context = routeContext.getCamelContext();
 
-        // the default binding mode can be overridden per rest verb
+        // these options can be overriden per rest verb
         String mode = context.getRestConfiguration().getBindingMode().name();
         if (bindingMode != null) {
             mode = bindingMode.name();
         }
+        boolean cors = context.getRestConfiguration().isEnableCORS();
+        if (enableCORS != null) {
+            cors = enableCORS;
+        }
+        boolean skip = context.getRestConfiguration().isSkipBindingOnErrorCode();
+        if (skipBindingOnErrorCode != null) {
+            skip = skipBindingOnErrorCode;
+        }
 
-        // skip by default
-        boolean skip = skipBindingOnErrorCode == null || skipBindingOnErrorCode;
+        // cors headers
+        Map<String, String> corsHeaders = context.getRestConfiguration().getCorsHeaders();
 
         if (mode == null || "off".equals(mode)) {
             // binding mode is off, so create a off mode binding processor
-            return new RestBindingProcessor(null, null, null, null, consumes, produces, mode, skip);
+            return new RestBindingProcessor(null, null, null, null, consumes, produces, mode, skip, cors, corsHeaders);
         }
 
         // setup json data format
@@ -182,7 +193,7 @@ public class RestBindingDefinition extends NoOutputDefinition<RestBindingDefinit
             context.addService(outJaxb);
         }
 
-        return new RestBindingProcessor(json, jaxb, outJson, outJaxb, consumes, produces, mode, skip);
+        return new RestBindingProcessor(json, jaxb, outJson, outJaxb, consumes, produces, mode, skip, cors, corsHeaders);
     }
 
     private void setAdditionalConfiguration(CamelContext context, DataFormat dataFormat) throws Exception {
@@ -242,4 +253,12 @@ public class RestBindingDefinition extends NoOutputDefinition<RestBindingDefinit
     public void setSkipBindingOnErrorCode(Boolean skipBindingOnErrorCode) {
         this.skipBindingOnErrorCode = skipBindingOnErrorCode;
     }
+
+    public Boolean getEnableCORS() {
+        return enableCORS;
+    }
+
+    public void setEnableCORS(Boolean enableCORS) {
+        this.enableCORS = enableCORS;
+    }
 }

http://git-wip-us.apache.org/repos/asf/camel/blob/7cbd6988/camel-core/src/main/java/org/apache/camel/model/rest/RestConfigurationDefinition.java
----------------------------------------------------------------------
diff --git a/camel-core/src/main/java/org/apache/camel/model/rest/RestConfigurationDefinition.java b/camel-core/src/main/java/org/apache/camel/model/rest/RestConfigurationDefinition.java
index 50d3879..b033dc6 100644
--- a/camel-core/src/main/java/org/apache/camel/model/rest/RestConfigurationDefinition.java
+++ b/camel-core/src/main/java/org/apache/camel/model/rest/RestConfigurationDefinition.java
@@ -62,6 +62,9 @@ public class RestConfigurationDefinition {
     private Boolean skipBindingOnErrorCode;
 
     @XmlAttribute
+    private Boolean enableCORS;
+
+    @XmlAttribute
     private String jsonDataFormat;
 
     @XmlAttribute
@@ -79,6 +82,9 @@ public class RestConfigurationDefinition {
     @XmlElement(name = "dataFormatProperty")
     private List<RestPropertyDefinition> dataFormatProperties = new ArrayList<RestPropertyDefinition>();
 
+    @XmlElement(name = "corsHeaders")
+    private List<RestPropertyDefinition> corsHeaders = new ArrayList<RestPropertyDefinition>();
+
     public String getComponent() {
         return component;
     }
@@ -143,6 +149,14 @@ public class RestConfigurationDefinition {
         this.skipBindingOnErrorCode = skipBindingOnErrorCode;
     }
 
+    public Boolean getEnableCORS() {
+        return enableCORS;
+    }
+
+    public void setEnableCORS(Boolean enableCORS) {
+        this.enableCORS = enableCORS;
+    }
+
     public String getJsonDataFormat() {
         return jsonDataFormat;
     }
@@ -191,6 +205,14 @@ public class RestConfigurationDefinition {
         this.dataFormatProperties = dataFormatProperties;
     }
 
+    public List<RestPropertyDefinition> getCorsHeaders() {
+        return corsHeaders;
+    }
+
+    public void setCorsHeaders(List<RestPropertyDefinition> corsHeaders) {
+        this.corsHeaders = corsHeaders;
+    }
+
     // Fluent API
     //-------------------------------------------------------------------------
 
@@ -270,6 +292,14 @@ public class RestConfigurationDefinition {
     }
 
     /**
+     * To specify whether to enable CORS which means Camel will automatic include CORS in the HTTP headers in the response.
+     */
+    public RestConfigurationDefinition enableCORS(boolean enableCORS) {
+        setEnableCORS(enableCORS);
+        return this;
+    }
+
+    /**
      * To use a specific json data format
      * <p/>
      * <b>Important:</b> This option is only for setting a custom name of the data format, not to refer to an existing data format instance.
@@ -337,6 +367,17 @@ public class RestConfigurationDefinition {
         return this;
     }
 
+    /**
+     * For configuring CORS headers
+     */
+    public RestConfigurationDefinition corsHeaderProperty(String key, String value) {
+        RestPropertyDefinition prop = new RestPropertyDefinition();
+        prop.setKey(key);
+        prop.setValue(value);
+        getCorsHeaders().add(prop);
+        return this;
+    }
+
     // Implementation
     //-------------------------------------------------------------------------
 
@@ -373,6 +414,9 @@ public class RestConfigurationDefinition {
         if (skipBindingOnErrorCode != null) {
             answer.setSkipBindingOnErrorCode(skipBindingOnErrorCode);
         }
+        if (enableCORS != null) {
+            answer.setEnableCORS(enableCORS);
+        }
         if (jsonDataFormat != null) {
             answer.setJsonDataFormat(jsonDataFormat);
         }
@@ -415,6 +459,15 @@ public class RestConfigurationDefinition {
             }
             answer.setDataFormatProperties(props);
         }
+        if (!corsHeaders.isEmpty()) {
+            Map<String, String> props = new HashMap<String, String>();
+            for (RestPropertyDefinition prop : corsHeaders) {
+                String key = prop.getKey();
+                String value = CamelContextHelper.parseText(context, prop.getValue());
+                props.put(key, value);
+            }
+            answer.setCorsHeaders(props);
+        }
         return answer;
     }
 

http://git-wip-us.apache.org/repos/asf/camel/blob/7cbd6988/camel-core/src/main/java/org/apache/camel/model/rest/RestDefinition.java
----------------------------------------------------------------------
diff --git a/camel-core/src/main/java/org/apache/camel/model/rest/RestDefinition.java b/camel-core/src/main/java/org/apache/camel/model/rest/RestDefinition.java
index 479f830..64128cd 100644
--- a/camel-core/src/main/java/org/apache/camel/model/rest/RestDefinition.java
+++ b/camel-core/src/main/java/org/apache/camel/model/rest/RestDefinition.java
@@ -56,6 +56,9 @@ public class RestDefinition extends OptionalIdentifiedDefinition<RestDefinition>
     @XmlAttribute
     private Boolean skipBindingOnErrorCode;
 
+    @XmlAttribute
+    private Boolean enableCORS;
+
     @XmlElementRef
     private List<VerbDefinition> verbs = new ArrayList<VerbDefinition>();
 
@@ -111,7 +114,15 @@ public class RestDefinition extends OptionalIdentifiedDefinition<RestDefinition>
     public void setSkipBindingOnErrorCode(Boolean skipBindingOnErrorCode) {
         this.skipBindingOnErrorCode = skipBindingOnErrorCode;
     }
-    
+
+    public Boolean getEnableCORS() {
+        return enableCORS;
+    }
+
+    public void setEnableCORS(Boolean enableCORS) {
+        this.enableCORS = enableCORS;
+    }
+
     // Fluent API
     //-------------------------------------------------------------------------
 
@@ -305,6 +316,18 @@ public class RestDefinition extends OptionalIdentifiedDefinition<RestDefinition>
         return this;
     }
 
+    public RestDefinition enableCORS(boolean enableCORS) {
+        if (getVerbs().isEmpty()) {
+            this.enableCORS = enableCORS;
+        } else {
+            // add on last verb as that is how the Java DSL works
+            VerbDefinition verb = getVerbs().get(getVerbs().size() - 1);
+            verb.setEnableCORS(enableCORS);
+        }
+
+        return this;
+    }
+
     /**
      * Routes directly to the given endpoint.
      * <p/>
@@ -405,6 +428,16 @@ public class RestDefinition extends OptionalIdentifiedDefinition<RestDefinition>
             } else {
                 binding.setBindingMode(getBindingMode());
             }
+            if (verb.getSkipBindingOnErrorCode() != null) {
+                binding.setSkipBindingOnErrorCode(verb.getSkipBindingOnErrorCode());
+            } else {
+                binding.setSkipBindingOnErrorCode(getSkipBindingOnErrorCode());
+            }
+            if (verb.getEnableCORS() != null) {
+                binding.setEnableCORS(verb.getEnableCORS());
+            } else {
+                binding.setEnableCORS(getEnableCORS());
+            }
             route.getOutputs().add(0, binding);
 
             // create the from endpoint uri which is using the rest component

http://git-wip-us.apache.org/repos/asf/camel/blob/7cbd6988/camel-core/src/main/java/org/apache/camel/model/rest/VerbDefinition.java
----------------------------------------------------------------------
diff --git a/camel-core/src/main/java/org/apache/camel/model/rest/VerbDefinition.java b/camel-core/src/main/java/org/apache/camel/model/rest/VerbDefinition.java
index 4ba401c..af56af1 100644
--- a/camel-core/src/main/java/org/apache/camel/model/rest/VerbDefinition.java
+++ b/camel-core/src/main/java/org/apache/camel/model/rest/VerbDefinition.java
@@ -51,6 +51,9 @@ public class VerbDefinition extends OptionalIdentifiedDefinition<VerbDefinition>
     private Boolean skipBindingOnErrorCode;
 
     @XmlAttribute
+    private Boolean enableCORS;
+
+    @XmlAttribute
     private String type;
 
     @XmlAttribute
@@ -129,6 +132,14 @@ public class VerbDefinition extends OptionalIdentifiedDefinition<VerbDefinition>
         this.skipBindingOnErrorCode = skipBindingOnErrorCode;
     }
 
+    public Boolean getEnableCORS() {
+        return enableCORS;
+    }
+
+    public void setEnableCORS(Boolean enableCORS) {
+        this.enableCORS = enableCORS;
+    }
+
     public String getType() {
         return type;
     }

http://git-wip-us.apache.org/repos/asf/camel/blob/7cbd6988/camel-core/src/main/java/org/apache/camel/processor/binding/RestBindingProcessor.java
----------------------------------------------------------------------
diff --git a/camel-core/src/main/java/org/apache/camel/processor/binding/RestBindingProcessor.java b/camel-core/src/main/java/org/apache/camel/processor/binding/RestBindingProcessor.java
index 9be0118..8a0de0d 100644
--- a/camel-core/src/main/java/org/apache/camel/processor/binding/RestBindingProcessor.java
+++ b/camel-core/src/main/java/org/apache/camel/processor/binding/RestBindingProcessor.java
@@ -17,14 +17,17 @@
 package org.apache.camel.processor.binding;
 
 import java.util.Locale;
+import java.util.Map;
 
 import org.apache.camel.AsyncCallback;
 import org.apache.camel.AsyncProcessor;
 import org.apache.camel.Exchange;
+import org.apache.camel.Message;
 import org.apache.camel.Route;
 import org.apache.camel.processor.MarshalProcessor;
 import org.apache.camel.processor.UnmarshalProcessor;
 import org.apache.camel.spi.DataFormat;
+import org.apache.camel.spi.RestConfiguration;
 import org.apache.camel.support.ServiceSupport;
 import org.apache.camel.support.SynchronizationAdapter;
 import org.apache.camel.util.AsyncProcessorHelper;
@@ -49,10 +52,14 @@ public class RestBindingProcessor extends ServiceSupport implements AsyncProcess
     private final String produces;
     private final String bindingMode;
     private final boolean skipBindingOnErrorCode;
+    private final boolean enableCORS;
+    private final Map<String, String> corsHeaders;
 
     public RestBindingProcessor(DataFormat jsonDataFormat, DataFormat xmlDataFormat,
                                 DataFormat outJsonDataFormat, DataFormat outXmlDataFormat,
-                                String consumes, String produces, String bindingMode, boolean skipBindingOnErrorCode) {
+                                String consumes, String produces, String bindingMode,
+                                boolean skipBindingOnErrorCode, boolean enableCORS,
+                                Map<String, String> corsHeaders) {
 
         if (jsonDataFormat != null) {
             this.jsonUnmarshal = new UnmarshalProcessor(jsonDataFormat);
@@ -84,6 +91,8 @@ public class RestBindingProcessor extends ServiceSupport implements AsyncProcess
         this.produces = produces;
         this.bindingMode = bindingMode;
         this.skipBindingOnErrorCode = skipBindingOnErrorCode;
+        this.enableCORS = enableCORS;
+        this.corsHeaders = corsHeaders;
     }
 
     @Override
@@ -93,6 +102,10 @@ public class RestBindingProcessor extends ServiceSupport implements AsyncProcess
 
     @Override
     public boolean process(Exchange exchange, final AsyncCallback callback) {
+        if (enableCORS) {
+            exchange.addOnCompletion(new RestBindingCORSOnCompletion(corsHeaders));
+        }
+
         if (bindingMode == null || "off".equals(bindingMode)) {
             // binding is off
             callback.done(true);
@@ -339,6 +352,54 @@ public class RestBindingProcessor extends ServiceSupport implements AsyncProcess
                 exchange.setException(e);
             }
         }
+
+        @Override
+        public String toString() {
+            return "RestBindingMarshalOnCompletion";
+        }
+    }
+
+    private final class RestBindingCORSOnCompletion extends SynchronizationAdapter {
+
+        private final Map<String, String> corsHeaders;
+
+        private RestBindingCORSOnCompletion(Map<String, String> corsHeaders) {
+            this.corsHeaders = corsHeaders;
+        }
+
+        @Override
+        public void onAfterRoute(Route route, Exchange exchange) {
+            // add the CORS headers after routing, but before the consumer writes the response
+            Message msg = exchange.hasOut() ? exchange.getOut() : exchange.getIn();
+
+            // use default value if none has been configured
+            String allowOrigin = corsHeaders != null ? corsHeaders.get("Access-Control-Allow-Origin") : null;
+            if (allowOrigin == null) {
+                allowOrigin = RestConfiguration.CORS_ACCESS_CONTROL_ALLOW_ORIGIN;
+            }
+            String allowMethods = corsHeaders != null ? corsHeaders.get("Access-Control-Allow-Methods") : null;
+            if (allowMethods == null) {
+                allowMethods = RestConfiguration.CORS_ACCESS_CONTROL_ALLOW_METHODS;
+            }
+            String allowHeaders = corsHeaders != null ? corsHeaders.get("Access-Control-Allow-Headers") : null;
+            if (allowHeaders == null) {
+                allowHeaders = RestConfiguration.CORS_ACCESS_CONTROL_ALLOW_HEADERS;
+            }
+            String maxAge = corsHeaders != null ? corsHeaders.get("Access-Control-Max-Age") : null;
+            if (maxAge == null) {
+                maxAge = RestConfiguration.CORS_ACCESS_CONTROL_MAX_AGE;
+            }
+
+            msg.setHeader("Access-Control-Allow-Origin", allowOrigin);
+            msg.setHeader("Access-Control-Allow-Methods", allowMethods);
+            msg.setHeader("Access-Control-Allow-Headers", allowHeaders);
+            msg.setHeader("Access-Control-Max-Age", maxAge);
+        }
+
+        @Override
+        public String toString() {
+            return "RestBindingCORSOnCompletion";
+        }
     }
 
 }

http://git-wip-us.apache.org/repos/asf/camel/blob/7cbd6988/camel-core/src/main/java/org/apache/camel/spi/RestConfiguration.java
----------------------------------------------------------------------
diff --git a/camel-core/src/main/java/org/apache/camel/spi/RestConfiguration.java b/camel-core/src/main/java/org/apache/camel/spi/RestConfiguration.java
index 50d64f7..5daecd9 100644
--- a/camel-core/src/main/java/org/apache/camel/spi/RestConfiguration.java
+++ b/camel-core/src/main/java/org/apache/camel/spi/RestConfiguration.java
@@ -24,6 +24,11 @@ import java.util.Map;
  */
 public class RestConfiguration {
 
+    public static final String CORS_ACCESS_CONTROL_ALLOW_ORIGIN = "*";
+    public static final String CORS_ACCESS_CONTROL_ALLOW_METHODS = "GET, HEAD, POST, PUT, DELETE, TRACE, OPTIONS, CONNECT, PATCH";
+    public static final String CORS_ACCESS_CONTROL_MAX_AGE = "3600";
+    public static final String CORS_ACCESS_CONTROL_ALLOW_HEADERS = "Origin, Accept, X-Requested-With, Content-Type, Access-Control-Request-Method, Access-Control-Request-Headers";
+
     public enum RestBindingMode {
         auto, off, json, xml, json_xml
     }
@@ -40,12 +45,14 @@ public class RestConfiguration {
     private RestHostNameResolver restHostNameResolver = RestHostNameResolver.localHostName;
     private RestBindingMode bindingMode = RestBindingMode.off;
     private boolean skipBindingOnErrorCode = true;
+    private boolean enableCORS;
     private String jsonDataFormat;
     private String xmlDataFormat;
     private Map<String, Object> componentProperties;
     private Map<String, Object> endpointProperties;
     private Map<String, Object> consumerProperties;
     private Map<String, Object> dataFormatProperties;
+    private Map<String, String> corsHeaders;
 
     /**
      * Gets the name of the Camel component to use as the REST consumer
@@ -198,6 +205,8 @@ public class RestConfiguration {
      * Whether to skip binding output if there is a custom HTTP error code, and instead use the response body as-is.
      * <p/>
      * This option is default <tt>true</tt>.
+     *
+     * @return whether to skip binding on error code
      */
     public boolean isSkipBindingOnErrorCode() {
         return skipBindingOnErrorCode;
@@ -207,12 +216,36 @@ public class RestConfiguration {
      * Whether to skip binding output if there is a custom HTTP error code, and instead use the response body as-is.
      * <p/>
      * This option is default <tt>true</tt>.
+     *
+     * @param skipBindingOnErrorCode whether to skip binding on error code
      */
     public void setSkipBindingOnErrorCode(boolean skipBindingOnErrorCode) {
         this.skipBindingOnErrorCode = skipBindingOnErrorCode;
     }
 
     /**
+     * To specify whether to enable CORS which means Camel will automatic include CORS in the HTTP headers in the response.
+     * <p/>
+     * This option is default <tt>false</tt>
+     *
+     * @return whether CORS is enabled or not
+     */
+    public boolean isEnableCORS() {
+        return enableCORS;
+    }
+
+    /**
+     * To specify whether to enable CORS which means Camel will automatic include CORS in the HTTP headers in the response.
+     * <p/>
+     * This option is default <tt>false</tt>
+     *
+     * @param enableCORS <tt>true</tt> to enable CORS
+     */
+    public void setEnableCORS(boolean enableCORS) {
+        this.enableCORS = enableCORS;
+    }
+
+    /**
      * Gets the name of the json data format.
      * <p/>
      * <b>Important:</b> This option is only for setting a custom name of the data format, not to refer to an existing data format instance.
@@ -327,4 +360,22 @@ public class RestConfiguration {
     public void setDataFormatProperties(Map<String, Object> dataFormatProperties) {
         this.dataFormatProperties = dataFormatProperties;
     }
+
+    /**
+     * Gets the CORS headers to use if CORS has been enabled.
+     *
+     * @return the CORS headers
+     */
+    public Map<String, String> getCorsHeaders() {
+        return corsHeaders;
+    }
+
+    /**
+     * Sets the CORS headers to use if CORS has been enabled.
+     *
+     * @param corsHeaders the CORS headers
+     */
+    public void setCorsHeaders(Map<String, String> corsHeaders) {
+        this.corsHeaders = corsHeaders;
+    }
 }

http://git-wip-us.apache.org/repos/asf/camel/blob/7cbd6988/camel-core/src/test/java/org/apache/camel/component/rest/FromRestGetCorsCustomTest.java
----------------------------------------------------------------------
diff --git a/camel-core/src/test/java/org/apache/camel/component/rest/FromRestGetCorsCustomTest.java b/camel-core/src/test/java/org/apache/camel/component/rest/FromRestGetCorsCustomTest.java
new file mode 100644
index 0000000..997fbbf
--- /dev/null
+++ b/camel-core/src/test/java/org/apache/camel/component/rest/FromRestGetCorsCustomTest.java
@@ -0,0 +1,78 @@
+/**
+ * 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.rest;
+
+import org.apache.camel.ContextTestSupport;
+import org.apache.camel.Exchange;
+import org.apache.camel.Processor;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.impl.JndiRegistry;
+import org.apache.camel.spi.RestConfiguration;
+
+public class FromRestGetCorsCustomTest extends ContextTestSupport {
+
+    @Override
+    protected JndiRegistry createRegistry() throws Exception {
+        JndiRegistry jndi = super.createRegistry();
+        jndi.bind("dummy-rest", new DummyRestConsumerFactory());
+        return jndi;
+    }
+
+    public void testCors() throws Exception {
+        // the rest becomes routes and the input is a seda endpoint created by the DummyRestConsumerFactory
+        getMockEndpoint("mock:update").expectedMessageCount(1);
+
+        Exchange out = template.request("seda:post-say-bye", new Processor() {
+            @Override
+            public void process(Exchange exchange) throws Exception {
+                exchange.getIn().setBody("I was here");
+            }
+        });
+        assertNotNull(out);
+
+        assertEquals(out.getOut().getHeader("Access-Control-Allow-Origin"), "myserver");
+        assertEquals(out.getOut().getHeader("Access-Control-Allow-Methods"), RestConfiguration.CORS_ACCESS_CONTROL_ALLOW_METHODS);
+        assertEquals(out.getOut().getHeader("Access-Control-Allow-Headers"), RestConfiguration.CORS_ACCESS_CONTROL_ALLOW_HEADERS);
+        assertEquals(out.getOut().getHeader("Access-Control-Max-Age"), "180");
+
+        assertMockEndpointsSatisfied();
+    }
+
+    @Override
+    protected RouteBuilder createRouteBuilder() throws Exception {
+        return new RouteBuilder() {
+            @Override
+            public void configure() throws Exception {
+                restConfiguration().enableCORS(true).corsHeaderProperty("Access-Control-Allow-Origin", "myserver");
+                restConfiguration().enableCORS(true).corsHeaderProperty("Access-Control-Max-Age", "180");
+
+                rest("/say/hello")
+                    .get().to("direct:hello");
+
+                rest("/say/bye")
+                    .get().consumes("application/json").to("direct:bye")
+                    .post().to("mock:update");
+
+                from("direct:hello")
+                    .transform().constant("Hello World");
+
+                from("direct:bye")
+                    .transform().constant("Bye World");
+            }
+        };
+    }
+}

http://git-wip-us.apache.org/repos/asf/camel/blob/7cbd6988/camel-core/src/test/java/org/apache/camel/component/rest/FromRestGetCorsTest.java
----------------------------------------------------------------------
diff --git a/camel-core/src/test/java/org/apache/camel/component/rest/FromRestGetCorsTest.java b/camel-core/src/test/java/org/apache/camel/component/rest/FromRestGetCorsTest.java
new file mode 100644
index 0000000..b0b213c
--- /dev/null
+++ b/camel-core/src/test/java/org/apache/camel/component/rest/FromRestGetCorsTest.java
@@ -0,0 +1,77 @@
+/**
+ * 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.rest;
+
+import org.apache.camel.ContextTestSupport;
+import org.apache.camel.Exchange;
+import org.apache.camel.Processor;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.impl.JndiRegistry;
+import org.apache.camel.spi.RestConfiguration;
+
+public class FromRestGetCorsTest extends ContextTestSupport {
+
+    @Override
+    protected JndiRegistry createRegistry() throws Exception {
+        JndiRegistry jndi = super.createRegistry();
+        jndi.bind("dummy-rest", new DummyRestConsumerFactory());
+        return jndi;
+    }
+
+    public void testCors() throws Exception {
+        // the rest becomes routes and the input is a seda endpoint created by the DummyRestConsumerFactory
+        getMockEndpoint("mock:update").expectedMessageCount(1);
+
+        Exchange out = template.request("seda:post-say-bye", new Processor() {
+            @Override
+            public void process(Exchange exchange) throws Exception {
+                exchange.getIn().setBody("I was here");
+            }
+        });
+        assertNotNull(out);
+
+        assertEquals(out.getOut().getHeader("Access-Control-Allow-Origin"), RestConfiguration.CORS_ACCESS_CONTROL_ALLOW_ORIGIN);
+        assertEquals(out.getOut().getHeader("Access-Control-Allow-Methods"), RestConfiguration.CORS_ACCESS_CONTROL_ALLOW_METHODS);
+        assertEquals(out.getOut().getHeader("Access-Control-Allow-Headers"), RestConfiguration.CORS_ACCESS_CONTROL_ALLOW_HEADERS);
+        assertEquals(out.getOut().getHeader("Access-Control-Max-Age"), RestConfiguration.CORS_ACCESS_CONTROL_MAX_AGE);
+
+        assertMockEndpointsSatisfied();
+    }
+
+    @Override
+    protected RouteBuilder createRouteBuilder() throws Exception {
+        return new RouteBuilder() {
+            @Override
+            public void configure() throws Exception {
+                restConfiguration().enableCORS(true);
+
+                rest("/say/hello")
+                    .get().to("direct:hello");
+
+                rest("/say/bye")
+                    .get().consumes("application/json").to("direct:bye")
+                    .post().to("mock:update");
+
+                from("direct:hello")
+                    .transform().constant("Hello World");
+
+                from("direct:bye")
+                    .transform().constant("Bye World");
+            }
+        };
+    }
+}


[2/2] camel git commit: CAMEL-8133: rest-dsl make it easy to enable and configure CORS. Also fixed an issue with the skip option.

Posted by da...@apache.org.
CAMEL-8133: rest-dsl make it easy to enable and configure CORS. Also fixed an issue with the skip option.


Project: http://git-wip-us.apache.org/repos/asf/camel/repo
Commit: http://git-wip-us.apache.org/repos/asf/camel/commit/4cbee5f1
Tree: http://git-wip-us.apache.org/repos/asf/camel/tree/4cbee5f1
Diff: http://git-wip-us.apache.org/repos/asf/camel/diff/4cbee5f1

Branch: refs/heads/camel-2.14.x
Commit: 4cbee5f1171c38a0f5f8678823a9ab520b8d6090
Parents: a0100ba
Author: Claus Ibsen <da...@apache.org>
Authored: Mon Dec 8 18:31:27 2014 +0100
Committer: Claus Ibsen <da...@apache.org>
Committed: Mon Dec 8 18:31:43 2014 +0100

----------------------------------------------------------------------
 .../camel/model/rest/RestBindingDefinition.java | 29 ++++++--
 .../model/rest/RestConfigurationDefinition.java | 53 +++++++++++++
 .../apache/camel/model/rest/RestDefinition.java | 35 ++++++++-
 .../apache/camel/model/rest/VerbDefinition.java | 11 +++
 .../processor/binding/RestBindingProcessor.java | 63 +++++++++++++++-
 .../org/apache/camel/spi/RestConfiguration.java | 51 +++++++++++++
 .../rest/FromRestGetCorsCustomTest.java         | 78 ++++++++++++++++++++
 .../component/rest/FromRestGetCorsTest.java     | 77 +++++++++++++++++++
 8 files changed, 390 insertions(+), 7 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/camel/blob/4cbee5f1/camel-core/src/main/java/org/apache/camel/model/rest/RestBindingDefinition.java
----------------------------------------------------------------------
diff --git a/camel-core/src/main/java/org/apache/camel/model/rest/RestBindingDefinition.java b/camel-core/src/main/java/org/apache/camel/model/rest/RestBindingDefinition.java
index 3f07d26..abe2a06 100644
--- a/camel-core/src/main/java/org/apache/camel/model/rest/RestBindingDefinition.java
+++ b/camel-core/src/main/java/org/apache/camel/model/rest/RestBindingDefinition.java
@@ -54,6 +54,9 @@ public class RestBindingDefinition extends NoOutputDefinition<RestBindingDefinit
     @XmlAttribute
     private Boolean skipBindingOnErrorCode;
 
+    @XmlAttribute
+    private Boolean enableCORS;
+
     @Override
     public String toString() {
         return "RestBinding";
@@ -69,18 +72,26 @@ public class RestBindingDefinition extends NoOutputDefinition<RestBindingDefinit
 
         CamelContext context = routeContext.getCamelContext();
 
-        // the default binding mode can be overridden per rest verb
+        // these options can be overriden per rest verb
         String mode = context.getRestConfiguration().getBindingMode().name();
         if (bindingMode != null) {
             mode = bindingMode.name();
         }
+        boolean cors = context.getRestConfiguration().isEnableCORS();
+        if (enableCORS != null) {
+            cors = enableCORS;
+        }
+        boolean skip = context.getRestConfiguration().isSkipBindingOnErrorCode();
+        if (skipBindingOnErrorCode != null) {
+            skip = skipBindingOnErrorCode;
+        }
 
-        // skip by default
-        boolean skip = skipBindingOnErrorCode == null || skipBindingOnErrorCode;
+        // cors headers
+        Map<String, String> corsHeaders = context.getRestConfiguration().getCorsHeaders();
 
         if (mode == null || "off".equals(mode)) {
             // binding mode is off, so create a off mode binding processor
-            return new RestBindingProcessor(null, null, null, null, consumes, produces, mode, skip);
+            return new RestBindingProcessor(null, null, null, null, consumes, produces, mode, skip, cors, corsHeaders);
         }
 
         // setup json data format
@@ -182,7 +193,7 @@ public class RestBindingDefinition extends NoOutputDefinition<RestBindingDefinit
             context.addService(outJaxb);
         }
 
-        return new RestBindingProcessor(json, jaxb, outJson, outJaxb, consumes, produces, mode, skip);
+        return new RestBindingProcessor(json, jaxb, outJson, outJaxb, consumes, produces, mode, skip, cors, corsHeaders);
     }
 
     private void setAdditionalConfiguration(CamelContext context, DataFormat dataFormat) throws Exception {
@@ -242,4 +253,12 @@ public class RestBindingDefinition extends NoOutputDefinition<RestBindingDefinit
     public void setSkipBindingOnErrorCode(Boolean skipBindingOnErrorCode) {
         this.skipBindingOnErrorCode = skipBindingOnErrorCode;
     }
+
+    public Boolean getEnableCORS() {
+        return enableCORS;
+    }
+
+    public void setEnableCORS(Boolean enableCORS) {
+        this.enableCORS = enableCORS;
+    }
 }

http://git-wip-us.apache.org/repos/asf/camel/blob/4cbee5f1/camel-core/src/main/java/org/apache/camel/model/rest/RestConfigurationDefinition.java
----------------------------------------------------------------------
diff --git a/camel-core/src/main/java/org/apache/camel/model/rest/RestConfigurationDefinition.java b/camel-core/src/main/java/org/apache/camel/model/rest/RestConfigurationDefinition.java
index 50d3879..b033dc6 100644
--- a/camel-core/src/main/java/org/apache/camel/model/rest/RestConfigurationDefinition.java
+++ b/camel-core/src/main/java/org/apache/camel/model/rest/RestConfigurationDefinition.java
@@ -62,6 +62,9 @@ public class RestConfigurationDefinition {
     private Boolean skipBindingOnErrorCode;
 
     @XmlAttribute
+    private Boolean enableCORS;
+
+    @XmlAttribute
     private String jsonDataFormat;
 
     @XmlAttribute
@@ -79,6 +82,9 @@ public class RestConfigurationDefinition {
     @XmlElement(name = "dataFormatProperty")
     private List<RestPropertyDefinition> dataFormatProperties = new ArrayList<RestPropertyDefinition>();
 
+    @XmlElement(name = "corsHeaders")
+    private List<RestPropertyDefinition> corsHeaders = new ArrayList<RestPropertyDefinition>();
+
     public String getComponent() {
         return component;
     }
@@ -143,6 +149,14 @@ public class RestConfigurationDefinition {
         this.skipBindingOnErrorCode = skipBindingOnErrorCode;
     }
 
+    public Boolean getEnableCORS() {
+        return enableCORS;
+    }
+
+    public void setEnableCORS(Boolean enableCORS) {
+        this.enableCORS = enableCORS;
+    }
+
     public String getJsonDataFormat() {
         return jsonDataFormat;
     }
@@ -191,6 +205,14 @@ public class RestConfigurationDefinition {
         this.dataFormatProperties = dataFormatProperties;
     }
 
+    public List<RestPropertyDefinition> getCorsHeaders() {
+        return corsHeaders;
+    }
+
+    public void setCorsHeaders(List<RestPropertyDefinition> corsHeaders) {
+        this.corsHeaders = corsHeaders;
+    }
+
     // Fluent API
     //-------------------------------------------------------------------------
 
@@ -270,6 +292,14 @@ public class RestConfigurationDefinition {
     }
 
     /**
+     * To specify whether to enable CORS which means Camel will automatic include CORS in the HTTP headers in the response.
+     */
+    public RestConfigurationDefinition enableCORS(boolean enableCORS) {
+        setEnableCORS(enableCORS);
+        return this;
+    }
+
+    /**
      * To use a specific json data format
      * <p/>
      * <b>Important:</b> This option is only for setting a custom name of the data format, not to refer to an existing data format instance.
@@ -337,6 +367,17 @@ public class RestConfigurationDefinition {
         return this;
     }
 
+    /**
+     * For configuring CORS headers
+     */
+    public RestConfigurationDefinition corsHeaderProperty(String key, String value) {
+        RestPropertyDefinition prop = new RestPropertyDefinition();
+        prop.setKey(key);
+        prop.setValue(value);
+        getCorsHeaders().add(prop);
+        return this;
+    }
+
     // Implementation
     //-------------------------------------------------------------------------
 
@@ -373,6 +414,9 @@ public class RestConfigurationDefinition {
         if (skipBindingOnErrorCode != null) {
             answer.setSkipBindingOnErrorCode(skipBindingOnErrorCode);
         }
+        if (enableCORS != null) {
+            answer.setEnableCORS(enableCORS);
+        }
         if (jsonDataFormat != null) {
             answer.setJsonDataFormat(jsonDataFormat);
         }
@@ -415,6 +459,15 @@ public class RestConfigurationDefinition {
             }
             answer.setDataFormatProperties(props);
         }
+        if (!corsHeaders.isEmpty()) {
+            Map<String, String> props = new HashMap<String, String>();
+            for (RestPropertyDefinition prop : corsHeaders) {
+                String key = prop.getKey();
+                String value = CamelContextHelper.parseText(context, prop.getValue());
+                props.put(key, value);
+            }
+            answer.setCorsHeaders(props);
+        }
         return answer;
     }
 

http://git-wip-us.apache.org/repos/asf/camel/blob/4cbee5f1/camel-core/src/main/java/org/apache/camel/model/rest/RestDefinition.java
----------------------------------------------------------------------
diff --git a/camel-core/src/main/java/org/apache/camel/model/rest/RestDefinition.java b/camel-core/src/main/java/org/apache/camel/model/rest/RestDefinition.java
index 479f830..64128cd 100644
--- a/camel-core/src/main/java/org/apache/camel/model/rest/RestDefinition.java
+++ b/camel-core/src/main/java/org/apache/camel/model/rest/RestDefinition.java
@@ -56,6 +56,9 @@ public class RestDefinition extends OptionalIdentifiedDefinition<RestDefinition>
     @XmlAttribute
     private Boolean skipBindingOnErrorCode;
 
+    @XmlAttribute
+    private Boolean enableCORS;
+
     @XmlElementRef
     private List<VerbDefinition> verbs = new ArrayList<VerbDefinition>();
 
@@ -111,7 +114,15 @@ public class RestDefinition extends OptionalIdentifiedDefinition<RestDefinition>
     public void setSkipBindingOnErrorCode(Boolean skipBindingOnErrorCode) {
         this.skipBindingOnErrorCode = skipBindingOnErrorCode;
     }
-    
+
+    public Boolean getEnableCORS() {
+        return enableCORS;
+    }
+
+    public void setEnableCORS(Boolean enableCORS) {
+        this.enableCORS = enableCORS;
+    }
+
     // Fluent API
     //-------------------------------------------------------------------------
 
@@ -305,6 +316,18 @@ public class RestDefinition extends OptionalIdentifiedDefinition<RestDefinition>
         return this;
     }
 
+    public RestDefinition enableCORS(boolean enableCORS) {
+        if (getVerbs().isEmpty()) {
+            this.enableCORS = enableCORS;
+        } else {
+            // add on last verb as that is how the Java DSL works
+            VerbDefinition verb = getVerbs().get(getVerbs().size() - 1);
+            verb.setEnableCORS(enableCORS);
+        }
+
+        return this;
+    }
+
     /**
      * Routes directly to the given endpoint.
      * <p/>
@@ -405,6 +428,16 @@ public class RestDefinition extends OptionalIdentifiedDefinition<RestDefinition>
             } else {
                 binding.setBindingMode(getBindingMode());
             }
+            if (verb.getSkipBindingOnErrorCode() != null) {
+                binding.setSkipBindingOnErrorCode(verb.getSkipBindingOnErrorCode());
+            } else {
+                binding.setSkipBindingOnErrorCode(getSkipBindingOnErrorCode());
+            }
+            if (verb.getEnableCORS() != null) {
+                binding.setEnableCORS(verb.getEnableCORS());
+            } else {
+                binding.setEnableCORS(getEnableCORS());
+            }
             route.getOutputs().add(0, binding);
 
             // create the from endpoint uri which is using the rest component

http://git-wip-us.apache.org/repos/asf/camel/blob/4cbee5f1/camel-core/src/main/java/org/apache/camel/model/rest/VerbDefinition.java
----------------------------------------------------------------------
diff --git a/camel-core/src/main/java/org/apache/camel/model/rest/VerbDefinition.java b/camel-core/src/main/java/org/apache/camel/model/rest/VerbDefinition.java
index 4ba401c..af56af1 100644
--- a/camel-core/src/main/java/org/apache/camel/model/rest/VerbDefinition.java
+++ b/camel-core/src/main/java/org/apache/camel/model/rest/VerbDefinition.java
@@ -51,6 +51,9 @@ public class VerbDefinition extends OptionalIdentifiedDefinition<VerbDefinition>
     private Boolean skipBindingOnErrorCode;
 
     @XmlAttribute
+    private Boolean enableCORS;
+
+    @XmlAttribute
     private String type;
 
     @XmlAttribute
@@ -129,6 +132,14 @@ public class VerbDefinition extends OptionalIdentifiedDefinition<VerbDefinition>
         this.skipBindingOnErrorCode = skipBindingOnErrorCode;
     }
 
+    public Boolean getEnableCORS() {
+        return enableCORS;
+    }
+
+    public void setEnableCORS(Boolean enableCORS) {
+        this.enableCORS = enableCORS;
+    }
+
     public String getType() {
         return type;
     }

http://git-wip-us.apache.org/repos/asf/camel/blob/4cbee5f1/camel-core/src/main/java/org/apache/camel/processor/binding/RestBindingProcessor.java
----------------------------------------------------------------------
diff --git a/camel-core/src/main/java/org/apache/camel/processor/binding/RestBindingProcessor.java b/camel-core/src/main/java/org/apache/camel/processor/binding/RestBindingProcessor.java
index 9be0118..8a0de0d 100644
--- a/camel-core/src/main/java/org/apache/camel/processor/binding/RestBindingProcessor.java
+++ b/camel-core/src/main/java/org/apache/camel/processor/binding/RestBindingProcessor.java
@@ -17,14 +17,17 @@
 package org.apache.camel.processor.binding;
 
 import java.util.Locale;
+import java.util.Map;
 
 import org.apache.camel.AsyncCallback;
 import org.apache.camel.AsyncProcessor;
 import org.apache.camel.Exchange;
+import org.apache.camel.Message;
 import org.apache.camel.Route;
 import org.apache.camel.processor.MarshalProcessor;
 import org.apache.camel.processor.UnmarshalProcessor;
 import org.apache.camel.spi.DataFormat;
+import org.apache.camel.spi.RestConfiguration;
 import org.apache.camel.support.ServiceSupport;
 import org.apache.camel.support.SynchronizationAdapter;
 import org.apache.camel.util.AsyncProcessorHelper;
@@ -49,10 +52,14 @@ public class RestBindingProcessor extends ServiceSupport implements AsyncProcess
     private final String produces;
     private final String bindingMode;
     private final boolean skipBindingOnErrorCode;
+    private final boolean enableCORS;
+    private final Map<String, String> corsHeaders;
 
     public RestBindingProcessor(DataFormat jsonDataFormat, DataFormat xmlDataFormat,
                                 DataFormat outJsonDataFormat, DataFormat outXmlDataFormat,
-                                String consumes, String produces, String bindingMode, boolean skipBindingOnErrorCode) {
+                                String consumes, String produces, String bindingMode,
+                                boolean skipBindingOnErrorCode, boolean enableCORS,
+                                Map<String, String> corsHeaders) {
 
         if (jsonDataFormat != null) {
             this.jsonUnmarshal = new UnmarshalProcessor(jsonDataFormat);
@@ -84,6 +91,8 @@ public class RestBindingProcessor extends ServiceSupport implements AsyncProcess
         this.produces = produces;
         this.bindingMode = bindingMode;
         this.skipBindingOnErrorCode = skipBindingOnErrorCode;
+        this.enableCORS = enableCORS;
+        this.corsHeaders = corsHeaders;
     }
 
     @Override
@@ -93,6 +102,10 @@ public class RestBindingProcessor extends ServiceSupport implements AsyncProcess
 
     @Override
     public boolean process(Exchange exchange, final AsyncCallback callback) {
+        if (enableCORS) {
+            exchange.addOnCompletion(new RestBindingCORSOnCompletion(corsHeaders));
+        }
+
         if (bindingMode == null || "off".equals(bindingMode)) {
             // binding is off
             callback.done(true);
@@ -339,6 +352,54 @@ public class RestBindingProcessor extends ServiceSupport implements AsyncProcess
                 exchange.setException(e);
             }
         }
+
+        @Override
+        public String toString() {
+            return "RestBindingMarshalOnCompletion";
+        }
+    }
+
+    private final class RestBindingCORSOnCompletion extends SynchronizationAdapter {
+
+        private final Map<String, String> corsHeaders;
+
+        private RestBindingCORSOnCompletion(Map<String, String> corsHeaders) {
+            this.corsHeaders = corsHeaders;
+        }
+
+        @Override
+        public void onAfterRoute(Route route, Exchange exchange) {
+            // add the CORS headers after routing, but before the consumer writes the response
+            Message msg = exchange.hasOut() ? exchange.getOut() : exchange.getIn();
+
+            // use default value if none has been configured
+            String allowOrigin = corsHeaders != null ? corsHeaders.get("Access-Control-Allow-Origin") : null;
+            if (allowOrigin == null) {
+                allowOrigin = RestConfiguration.CORS_ACCESS_CONTROL_ALLOW_ORIGIN;
+            }
+            String allowMethods = corsHeaders != null ? corsHeaders.get("Access-Control-Allow-Methods") : null;
+            if (allowMethods == null) {
+                allowMethods = RestConfiguration.CORS_ACCESS_CONTROL_ALLOW_METHODS;
+            }
+            String allowHeaders = corsHeaders != null ? corsHeaders.get("Access-Control-Allow-Headers") : null;
+            if (allowHeaders == null) {
+                allowHeaders = RestConfiguration.CORS_ACCESS_CONTROL_ALLOW_HEADERS;
+            }
+            String maxAge = corsHeaders != null ? corsHeaders.get("Access-Control-Max-Age") : null;
+            if (maxAge == null) {
+                maxAge = RestConfiguration.CORS_ACCESS_CONTROL_MAX_AGE;
+            }
+
+            msg.setHeader("Access-Control-Allow-Origin", allowOrigin);
+            msg.setHeader("Access-Control-Allow-Methods", allowMethods);
+            msg.setHeader("Access-Control-Allow-Headers", allowHeaders);
+            msg.setHeader("Access-Control-Max-Age", maxAge);
+        }
+
+        @Override
+        public String toString() {
+            return "RestBindingCORSOnCompletion";
+        }
     }
 
 }

http://git-wip-us.apache.org/repos/asf/camel/blob/4cbee5f1/camel-core/src/main/java/org/apache/camel/spi/RestConfiguration.java
----------------------------------------------------------------------
diff --git a/camel-core/src/main/java/org/apache/camel/spi/RestConfiguration.java b/camel-core/src/main/java/org/apache/camel/spi/RestConfiguration.java
index 50d64f7..5daecd9 100644
--- a/camel-core/src/main/java/org/apache/camel/spi/RestConfiguration.java
+++ b/camel-core/src/main/java/org/apache/camel/spi/RestConfiguration.java
@@ -24,6 +24,11 @@ import java.util.Map;
  */
 public class RestConfiguration {
 
+    public static final String CORS_ACCESS_CONTROL_ALLOW_ORIGIN = "*";
+    public static final String CORS_ACCESS_CONTROL_ALLOW_METHODS = "GET, HEAD, POST, PUT, DELETE, TRACE, OPTIONS, CONNECT, PATCH";
+    public static final String CORS_ACCESS_CONTROL_MAX_AGE = "3600";
+    public static final String CORS_ACCESS_CONTROL_ALLOW_HEADERS = "Origin, Accept, X-Requested-With, Content-Type, Access-Control-Request-Method, Access-Control-Request-Headers";
+
     public enum RestBindingMode {
         auto, off, json, xml, json_xml
     }
@@ -40,12 +45,14 @@ public class RestConfiguration {
     private RestHostNameResolver restHostNameResolver = RestHostNameResolver.localHostName;
     private RestBindingMode bindingMode = RestBindingMode.off;
     private boolean skipBindingOnErrorCode = true;
+    private boolean enableCORS;
     private String jsonDataFormat;
     private String xmlDataFormat;
     private Map<String, Object> componentProperties;
     private Map<String, Object> endpointProperties;
     private Map<String, Object> consumerProperties;
     private Map<String, Object> dataFormatProperties;
+    private Map<String, String> corsHeaders;
 
     /**
      * Gets the name of the Camel component to use as the REST consumer
@@ -198,6 +205,8 @@ public class RestConfiguration {
      * Whether to skip binding output if there is a custom HTTP error code, and instead use the response body as-is.
      * <p/>
      * This option is default <tt>true</tt>.
+     *
+     * @return whether to skip binding on error code
      */
     public boolean isSkipBindingOnErrorCode() {
         return skipBindingOnErrorCode;
@@ -207,12 +216,36 @@ public class RestConfiguration {
      * Whether to skip binding output if there is a custom HTTP error code, and instead use the response body as-is.
      * <p/>
      * This option is default <tt>true</tt>.
+     *
+     * @param skipBindingOnErrorCode whether to skip binding on error code
      */
     public void setSkipBindingOnErrorCode(boolean skipBindingOnErrorCode) {
         this.skipBindingOnErrorCode = skipBindingOnErrorCode;
     }
 
     /**
+     * To specify whether to enable CORS which means Camel will automatic include CORS in the HTTP headers in the response.
+     * <p/>
+     * This option is default <tt>false</tt>
+     *
+     * @return whether CORS is enabled or not
+     */
+    public boolean isEnableCORS() {
+        return enableCORS;
+    }
+
+    /**
+     * To specify whether to enable CORS which means Camel will automatic include CORS in the HTTP headers in the response.
+     * <p/>
+     * This option is default <tt>false</tt>
+     *
+     * @param enableCORS <tt>true</tt> to enable CORS
+     */
+    public void setEnableCORS(boolean enableCORS) {
+        this.enableCORS = enableCORS;
+    }
+
+    /**
      * Gets the name of the json data format.
      * <p/>
      * <b>Important:</b> This option is only for setting a custom name of the data format, not to refer to an existing data format instance.
@@ -327,4 +360,22 @@ public class RestConfiguration {
     public void setDataFormatProperties(Map<String, Object> dataFormatProperties) {
         this.dataFormatProperties = dataFormatProperties;
     }
+
+    /**
+     * Gets the CORS headers to use if CORS has been enabled.
+     *
+     * @return the CORS headers
+     */
+    public Map<String, String> getCorsHeaders() {
+        return corsHeaders;
+    }
+
+    /**
+     * Sets the CORS headers to use if CORS has been enabled.
+     *
+     * @param corsHeaders the CORS headers
+     */
+    public void setCorsHeaders(Map<String, String> corsHeaders) {
+        this.corsHeaders = corsHeaders;
+    }
 }

http://git-wip-us.apache.org/repos/asf/camel/blob/4cbee5f1/camel-core/src/test/java/org/apache/camel/component/rest/FromRestGetCorsCustomTest.java
----------------------------------------------------------------------
diff --git a/camel-core/src/test/java/org/apache/camel/component/rest/FromRestGetCorsCustomTest.java b/camel-core/src/test/java/org/apache/camel/component/rest/FromRestGetCorsCustomTest.java
new file mode 100644
index 0000000..997fbbf
--- /dev/null
+++ b/camel-core/src/test/java/org/apache/camel/component/rest/FromRestGetCorsCustomTest.java
@@ -0,0 +1,78 @@
+/**
+ * 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.rest;
+
+import org.apache.camel.ContextTestSupport;
+import org.apache.camel.Exchange;
+import org.apache.camel.Processor;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.impl.JndiRegistry;
+import org.apache.camel.spi.RestConfiguration;
+
+public class FromRestGetCorsCustomTest extends ContextTestSupport {
+
+    @Override
+    protected JndiRegistry createRegistry() throws Exception {
+        JndiRegistry jndi = super.createRegistry();
+        jndi.bind("dummy-rest", new DummyRestConsumerFactory());
+        return jndi;
+    }
+
+    public void testCors() throws Exception {
+        // the rest becomes routes and the input is a seda endpoint created by the DummyRestConsumerFactory
+        getMockEndpoint("mock:update").expectedMessageCount(1);
+
+        Exchange out = template.request("seda:post-say-bye", new Processor() {
+            @Override
+            public void process(Exchange exchange) throws Exception {
+                exchange.getIn().setBody("I was here");
+            }
+        });
+        assertNotNull(out);
+
+        assertEquals(out.getOut().getHeader("Access-Control-Allow-Origin"), "myserver");
+        assertEquals(out.getOut().getHeader("Access-Control-Allow-Methods"), RestConfiguration.CORS_ACCESS_CONTROL_ALLOW_METHODS);
+        assertEquals(out.getOut().getHeader("Access-Control-Allow-Headers"), RestConfiguration.CORS_ACCESS_CONTROL_ALLOW_HEADERS);
+        assertEquals(out.getOut().getHeader("Access-Control-Max-Age"), "180");
+
+        assertMockEndpointsSatisfied();
+    }
+
+    @Override
+    protected RouteBuilder createRouteBuilder() throws Exception {
+        return new RouteBuilder() {
+            @Override
+            public void configure() throws Exception {
+                restConfiguration().enableCORS(true).corsHeaderProperty("Access-Control-Allow-Origin", "myserver");
+                restConfiguration().enableCORS(true).corsHeaderProperty("Access-Control-Max-Age", "180");
+
+                rest("/say/hello")
+                    .get().to("direct:hello");
+
+                rest("/say/bye")
+                    .get().consumes("application/json").to("direct:bye")
+                    .post().to("mock:update");
+
+                from("direct:hello")
+                    .transform().constant("Hello World");
+
+                from("direct:bye")
+                    .transform().constant("Bye World");
+            }
+        };
+    }
+}

http://git-wip-us.apache.org/repos/asf/camel/blob/4cbee5f1/camel-core/src/test/java/org/apache/camel/component/rest/FromRestGetCorsTest.java
----------------------------------------------------------------------
diff --git a/camel-core/src/test/java/org/apache/camel/component/rest/FromRestGetCorsTest.java b/camel-core/src/test/java/org/apache/camel/component/rest/FromRestGetCorsTest.java
new file mode 100644
index 0000000..b0b213c
--- /dev/null
+++ b/camel-core/src/test/java/org/apache/camel/component/rest/FromRestGetCorsTest.java
@@ -0,0 +1,77 @@
+/**
+ * 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.rest;
+
+import org.apache.camel.ContextTestSupport;
+import org.apache.camel.Exchange;
+import org.apache.camel.Processor;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.impl.JndiRegistry;
+import org.apache.camel.spi.RestConfiguration;
+
+public class FromRestGetCorsTest extends ContextTestSupport {
+
+    @Override
+    protected JndiRegistry createRegistry() throws Exception {
+        JndiRegistry jndi = super.createRegistry();
+        jndi.bind("dummy-rest", new DummyRestConsumerFactory());
+        return jndi;
+    }
+
+    public void testCors() throws Exception {
+        // the rest becomes routes and the input is a seda endpoint created by the DummyRestConsumerFactory
+        getMockEndpoint("mock:update").expectedMessageCount(1);
+
+        Exchange out = template.request("seda:post-say-bye", new Processor() {
+            @Override
+            public void process(Exchange exchange) throws Exception {
+                exchange.getIn().setBody("I was here");
+            }
+        });
+        assertNotNull(out);
+
+        assertEquals(out.getOut().getHeader("Access-Control-Allow-Origin"), RestConfiguration.CORS_ACCESS_CONTROL_ALLOW_ORIGIN);
+        assertEquals(out.getOut().getHeader("Access-Control-Allow-Methods"), RestConfiguration.CORS_ACCESS_CONTROL_ALLOW_METHODS);
+        assertEquals(out.getOut().getHeader("Access-Control-Allow-Headers"), RestConfiguration.CORS_ACCESS_CONTROL_ALLOW_HEADERS);
+        assertEquals(out.getOut().getHeader("Access-Control-Max-Age"), RestConfiguration.CORS_ACCESS_CONTROL_MAX_AGE);
+
+        assertMockEndpointsSatisfied();
+    }
+
+    @Override
+    protected RouteBuilder createRouteBuilder() throws Exception {
+        return new RouteBuilder() {
+            @Override
+            public void configure() throws Exception {
+                restConfiguration().enableCORS(true);
+
+                rest("/say/hello")
+                    .get().to("direct:hello");
+
+                rest("/say/bye")
+                    .get().consumes("application/json").to("direct:bye")
+                    .post().to("mock:update");
+
+                from("direct:hello")
+                    .transform().constant("Hello World");
+
+                from("direct:bye")
+                    .transform().constant("Bye World");
+            }
+        };
+    }
+}