CAMEL-10164: Add support for binding in rest to

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

Branch: refs/heads/master
Commit: 29cd3cdbd23a8b7f94ac8818e8df5ce50f066d88
Parents: 737b49c
Author: Claus Ibsen <davscl...@apache.org>
Authored: Mon Sep 5 16:18:26 2016 +0200
Committer: Claus Ibsen <davscl...@apache.org>
Committed: Mon Sep 5 16:26:13 2016 +0200

----------------------------------------------------------------------
 camel-core/src/main/docs/rest-component.adoc    |  48 +-
 .../rest/RestConsumerBindingProcessor.java      | 461 +++++++++++++++++++
 .../camel/component/rest/RestEndpoint.java      |   8 +-
 .../camel/component/rest/RestProducer.java      |  76 ++-
 .../rest/RestProducerBindingProcessor.java      | 124 ++---
 .../camel/model/rest/RestBindingDefinition.java |   2 +-
 .../binding/RestConsumerBindingProcessor.java   | 460 ------------------
 .../JettyRestProducerPojoInOutTest.java         |   8 +-
 .../src/test/resources/log4j2.properties        |   2 +-
 9 files changed, 658 insertions(+), 531 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/camel/blob/29cd3cdb/camel-core/src/main/docs/rest-component.adoc
----------------------------------------------------------------------
diff --git a/camel-core/src/main/docs/rest-component.adoc 
b/camel-core/src/main/docs/rest-component.adoc
index 2413940..910f271 100644
--- a/camel-core/src/main/docs/rest-component.adoc
+++ b/camel-core/src/main/docs/rest-component.adoc
@@ -54,9 +54,9 @@ The REST component supports 17 endpoint options which are 
listed below:
 | inType | common |  | String | To declare the incoming POJO binding type as a 
FQN class name
 | outType | common |  | String | To declare the outgoing POJO binding type as 
a FQN class name
 | produces | common |  | String | Media type such as: 'text/xml' or 
'application/json' this REST service returns.
+| routeId | common |  | String | Name of the route this REST services creates
 | bridgeErrorHandler | consumer | false | boolean | Allows for bridging the 
consumer to the Camel routing Error Handler which mean any exceptions occurred 
while the consumer is trying to pickup incoming messages or the likes will now 
be processed as a message and handled by the routing Error Handler. By default 
the consumer will use the org.apache.camel.spi.ExceptionHandler to deal with 
exceptions that will be logged at WARN/ERROR level and ignored.
 | description | consumer |  | String | Human description to document this REST 
service
-| routeId | consumer |  | String | Name of the route this REST services creates
 | exceptionHandler | consumer (advanced) |  | ExceptionHandler | To let the 
consumer use a custom ExceptionHandler. Notice if the option bridgeErrorHandler 
is enabled then this options is not in use. By default the consumer will deal 
with exceptions that will be logged at WARN/ERROR level and ignored.
 | apiDoc | producer |  | String | The swagger api doc resource to use. The 
resource is loaded from classpath by default and must be in JSon format.
 | host | producer |  | String | Host and port of HTTP service to use (override 
host in swagger schema)
@@ -162,6 +162,52 @@ to use http4 you can do:
 --------------------------------------------
 
 
+[[Rest-Producer-Binding]]
+Rest producer binding
+^^^^^^^^^^^^^^^^^^^^^
+
+The REST producer supports binding using JSon or XML like the rest-dsl does.
+
+For example to use jetty with json binding mode turned on you can configure 
this in the rest configuration:
+
+[source,java]
+--------------------------------------------
+  
restConfiguration().component("jetty").host("localhost").port(8080).bindingMode(RestBindingMode.json);
+
+  from("direct:start")
+    .to("rest:post:user");
+--------------------------------------------
+
+Then when calling the REST service using rest producer it will automatic bind 
any POJOs to json before calling the REST service:
+
+[source,java]
+--------------------------------------------
+  UserPojo user = new UserPojo();
+  user.setId(123);
+  user.setName("Donald Duck");
+
+  template.sendBody("direct:start", user);
+--------------------------------------------
+
+In the example above we send a POJO instance `UserPojo` as the message body. 
And because we have turned on JSon binding
+in the rest configuration, then the POJO will be marshalled from POJO to JSon 
before calling the REST service.
+
+However if you want to also perform binding for the response message (eg what 
the REST service send back as response) you
+would need to configure the `outType` option to specify what is the classname 
of the POJO to unmarshal from JSon to POJO.
+
+For example if the REST service returns a JSon payload that binds to 
`com.foo.MyResponsePojo` you can configure this as shown:
+
+[source,java]
+--------------------------------------------
+  
restConfiguration().component("jetty").host("localhost").port(8080).bindingMode(RestBindingMode.json);
+
+  from("direct:start")
+    .to("rest:post:user?outType=com.foo.MyResponsePojo");
+--------------------------------------------
+
+IMPORTANT: You must configure `outType` option if you want POJO binding to 
happen for the response messages received from calling the REST service.
+
+
 [[Rest-Moreexamples]]
 More examples
 ^^^^^^^^^^^^^

http://git-wip-us.apache.org/repos/asf/camel/blob/29cd3cdb/camel-core/src/main/java/org/apache/camel/component/rest/RestConsumerBindingProcessor.java
----------------------------------------------------------------------
diff --git 
a/camel-core/src/main/java/org/apache/camel/component/rest/RestConsumerBindingProcessor.java
 
b/camel-core/src/main/java/org/apache/camel/component/rest/RestConsumerBindingProcessor.java
new file mode 100644
index 0000000..888fc2f
--- /dev/null
+++ 
b/camel-core/src/main/java/org/apache/camel/component/rest/RestConsumerBindingProcessor.java
@@ -0,0 +1,461 @@
+/**
+ * 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 java.util.Locale;
+import java.util.Map;
+
+import org.apache.camel.AsyncCallback;
+import org.apache.camel.AsyncProcessor;
+import org.apache.camel.CamelContext;
+import org.apache.camel.CamelContextAware;
+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.processor.binding.BindingException;
+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;
+import org.apache.camel.util.ExchangeHelper;
+import org.apache.camel.util.MessageHelper;
+import org.apache.camel.util.ObjectHelper;
+import org.apache.camel.util.ServiceHelper;
+
+/**
+ * A {@link org.apache.camel.Processor} that binds the REST DSL incoming and 
outgoing messages
+ * from sources of json or xml to Java Objects.
+ * <p/>
+ * The binding uses {@link org.apache.camel.spi.DataFormat} for the actual 
work to transform
+ * from xml/json to Java Objects and reverse again.
+ */
+public class RestConsumerBindingProcessor extends ServiceSupport implements 
AsyncProcessor {
+
+    private final CamelContext camelContext;
+    private final AsyncProcessor jsonUnmarshal;
+    private final AsyncProcessor xmlUnmarshal;
+    private final AsyncProcessor jsonMarshal;
+    private final AsyncProcessor xmlMarshal;
+    private final String consumes;
+    private final String produces;
+    private final String bindingMode;
+    private final boolean skipBindingOnErrorCode;
+    private final boolean enableCORS;
+    private final Map<String, String> corsHeaders;
+    private final Map<String, String> queryDefaultValues;
+
+    public RestConsumerBindingProcessor(CamelContext camelContext, DataFormat 
jsonDataFormat, DataFormat xmlDataFormat,
+                                        DataFormat outJsonDataFormat, 
DataFormat outXmlDataFormat,
+                                        String consumes, String produces, 
String bindingMode,
+                                        boolean skipBindingOnErrorCode, 
boolean enableCORS,
+                                        Map<String, String> corsHeaders,
+                                        Map<String, String> 
queryDefaultValues) {
+
+        this.camelContext = camelContext;
+
+        if (jsonDataFormat != null) {
+            this.jsonUnmarshal = new UnmarshalProcessor(jsonDataFormat);
+        } else {
+            this.jsonUnmarshal = null;
+        }
+        if (outJsonDataFormat != null) {
+            this.jsonMarshal = new MarshalProcessor(outJsonDataFormat);
+        } else if (jsonDataFormat != null) {
+            this.jsonMarshal = new MarshalProcessor(jsonDataFormat);
+        } else {
+            this.jsonMarshal = null;
+        }
+
+        if (xmlDataFormat != null) {
+            this.xmlUnmarshal = new UnmarshalProcessor(xmlDataFormat);
+        } else {
+            this.xmlUnmarshal = null;
+        }
+        if (outXmlDataFormat != null) {
+            this.xmlMarshal = new MarshalProcessor(outXmlDataFormat);
+        } else if (xmlDataFormat != null) {
+            this.xmlMarshal = new MarshalProcessor(xmlDataFormat);
+        } else {
+            this.xmlMarshal = null;
+        }
+
+        this.consumes = consumes;
+        this.produces = produces;
+        this.bindingMode = bindingMode;
+        this.skipBindingOnErrorCode = skipBindingOnErrorCode;
+        this.enableCORS = enableCORS;
+        this.corsHeaders = corsHeaders;
+        this.queryDefaultValues = queryDefaultValues;
+    }
+
+    @Override
+    public void process(Exchange exchange) throws Exception {
+        AsyncProcessorHelper.process(this, exchange);
+    }
+
+    @Override
+    public boolean process(Exchange exchange, final AsyncCallback callback) {
+        if (enableCORS) {
+            exchange.addOnCompletion(new 
RestConsumerBindingCORSOnCompletion(corsHeaders));
+        }
+
+        String method = exchange.getIn().getHeader(Exchange.HTTP_METHOD, 
String.class);
+        if ("OPTIONS".equalsIgnoreCase(method)) {
+            // for OPTIONS methods then we should not route at all as its part 
of CORS
+            exchange.setProperty(Exchange.ROUTE_STOP, true);
+            callback.done(true);
+            return true;
+        }
+
+        boolean isXml = false;
+        boolean isJson = false;
+
+        String contentType = ExchangeHelper.getContentType(exchange);
+        if (contentType != null) {
+            isXml = contentType.toLowerCase(Locale.ENGLISH).contains("xml");
+            isJson = contentType.toLowerCase(Locale.ENGLISH).contains("json");
+        }
+        // if content type could not tell us if it was json or xml, then 
fallback to if the binding was configured with
+        // that information in the consumes
+        if (!isXml && !isJson) {
+            isXml = consumes != null && 
consumes.toLowerCase(Locale.ENGLISH).contains("xml");
+            isJson = consumes != null && 
consumes.toLowerCase(Locale.ENGLISH).contains("json");
+        }
+
+        // only allow xml/json if the binding mode allows that
+        isXml &= bindingMode.equals("auto") || bindingMode.contains("xml");
+        isJson &= bindingMode.equals("auto") || bindingMode.contains("json");
+
+        // if we do not yet know if its xml or json, then use the binding mode 
to know the mode
+        if (!isJson && !isXml) {
+            isXml = bindingMode.equals("auto") || bindingMode.contains("xml");
+            isJson = bindingMode.equals("auto") || 
bindingMode.contains("json");
+        }
+
+        String accept = exchange.getIn().getHeader("Accept", String.class);
+
+        String body = null;
+        if (exchange.getIn().getBody() != null) {
+
+            // okay we have a binding mode, so need to check for empty body as 
that can cause the marshaller to fail
+            // as they assume a non-empty body
+            if (isXml || isJson) {
+                // we have binding enabled, so we need to know if there body 
is empty or not
+                // so force reading the body as a String which we can work with
+                body = MessageHelper.extractBodyAsString(exchange.getIn());
+                if (body != null) {
+                    exchange.getIn().setBody(body);
+
+                    if (isXml && isJson) {
+                        // we have still not determined between xml or json, 
so check the body if its xml based or not
+                        isXml = body.startsWith("<");
+                        isJson = !isXml;
+                    }
+                }
+            }
+        }
+
+        // add missing default values which are mapped as headers
+        if (queryDefaultValues != null) {
+            for (Map.Entry<String, String> entry : 
queryDefaultValues.entrySet()) {
+                if (exchange.getIn().getHeader(entry.getKey()) == null) {
+                    exchange.getIn().setHeader(entry.getKey(), 
entry.getValue());
+                }
+            }
+        }
+
+        // favor json over xml
+        if (isJson && jsonUnmarshal != null) {
+            // add reverse operation
+            exchange.addOnCompletion(new 
RestConsumerBindingMarshalOnCompletion(exchange.getFromRouteId(), jsonMarshal, 
xmlMarshal, false, accept));
+            if (ObjectHelper.isNotEmpty(body)) {
+                return jsonUnmarshal.process(exchange, callback);
+            } else {
+                callback.done(true);
+                return true;
+            }
+        } else if (isXml && xmlUnmarshal != null) {
+            // add reverse operation
+            exchange.addOnCompletion(new 
RestConsumerBindingMarshalOnCompletion(exchange.getFromRouteId(), jsonMarshal, 
xmlMarshal, true, accept));
+            if (ObjectHelper.isNotEmpty(body)) {
+                return xmlUnmarshal.process(exchange, callback);
+            } else {
+                callback.done(true);
+                return true;
+            }
+        }
+
+        // we could not bind
+        if ("off".equals(bindingMode) || bindingMode.equals("auto")) {
+            // okay for auto we do not mind if we could not bind
+            exchange.addOnCompletion(new 
RestConsumerBindingMarshalOnCompletion(exchange.getFromRouteId(), jsonMarshal, 
xmlMarshal, false, accept));
+            callback.done(true);
+            return true;
+        } else {
+            if (bindingMode.contains("xml")) {
+                exchange.setException(new BindingException("Cannot bind to xml 
as message body is not xml compatible", exchange));
+            } else {
+                exchange.setException(new BindingException("Cannot bind to 
json as message body is not json compatible", exchange));
+            }
+            callback.done(true);
+            return true;
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "RestConsumerBindingProcessor";
+    }
+
+    @Override
+    protected void doStart() throws Exception {
+        // inject CamelContext before starting
+        if (jsonMarshal instanceof CamelContextAware) {
+            ((CamelContextAware) jsonMarshal).setCamelContext(camelContext);
+        }
+        if (jsonUnmarshal instanceof CamelContextAware) {
+            ((CamelContextAware) jsonUnmarshal).setCamelContext(camelContext);
+        }
+        if (xmlMarshal instanceof CamelContextAware) {
+            ((CamelContextAware) xmlMarshal).setCamelContext(camelContext);
+        }
+        if (xmlUnmarshal instanceof CamelContextAware) {
+            ((CamelContextAware) xmlUnmarshal).setCamelContext(camelContext);
+        }
+        ServiceHelper.startServices(jsonMarshal, jsonUnmarshal, xmlMarshal, 
xmlUnmarshal);
+    }
+
+    @Override
+    protected void doStop() throws Exception {
+        ServiceHelper.stopServices(jsonMarshal, jsonUnmarshal, xmlMarshal, 
xmlUnmarshal);
+    }
+
+    /**
+     * An {@link org.apache.camel.spi.Synchronization} that does the reverse 
operation
+     * of marshalling from POJO to json/xml
+     */
+    private final class RestConsumerBindingMarshalOnCompletion extends 
SynchronizationAdapter {
+
+        private final AsyncProcessor jsonMarshal;
+        private final AsyncProcessor xmlMarshal;
+        private final String routeId;
+        private boolean wasXml;
+        private String accept;
+
+        private RestConsumerBindingMarshalOnCompletion(String routeId, 
AsyncProcessor jsonMarshal, AsyncProcessor xmlMarshal, boolean wasXml, String 
accept) {
+            this.routeId = routeId;
+            this.jsonMarshal = jsonMarshal;
+            this.xmlMarshal = xmlMarshal;
+            this.wasXml = wasXml;
+            this.accept = accept;
+        }
+
+        @Override
+        public void onAfterRoute(Route route, Exchange exchange) {
+            // we use the onAfterRoute callback, to ensure the data has been 
marshalled before
+            // the consumer writes the response back
+
+            // only trigger when it was the 1st route that was done
+            if (!routeId.equals(route.getId())) {
+                return;
+            }
+
+            // only marshal if there was no exception
+            if (exchange.getException() != null) {
+                return;
+            }
+
+            if (skipBindingOnErrorCode) {
+                Integer code = exchange.hasOut() ? 
exchange.getOut().getHeader(Exchange.HTTP_RESPONSE_CODE, Integer.class) : 
exchange.getIn().getHeader(Exchange.HTTP_RESPONSE_CODE, Integer.class);
+                // if there is a custom http error code then skip binding
+                if (code != null && code >= 300) {
+                    return;
+                }
+            }
+
+            boolean isXml = false;
+            boolean isJson = false;
+
+            // accept takes precedence
+            if (accept != null) {
+                isXml = accept.toLowerCase(Locale.ENGLISH).contains("xml");
+                isJson = accept.toLowerCase(Locale.ENGLISH).contains("json");
+            }
+            // fallback to content type if still undecided
+            if (!isXml && !isJson) {
+                String contentType = ExchangeHelper.getContentType(exchange);
+                if (contentType != null) {
+                    isXml = 
contentType.toLowerCase(Locale.ENGLISH).contains("xml");
+                    isJson = 
contentType.toLowerCase(Locale.ENGLISH).contains("json");
+                }
+            }
+            // if content type could not tell us if it was json or xml, then 
fallback to if the binding was configured with
+            // that information in the consumes
+            if (!isXml && !isJson) {
+                isXml = produces != null && 
produces.toLowerCase(Locale.ENGLISH).contains("xml");
+                isJson = produces != null && 
produces.toLowerCase(Locale.ENGLISH).contains("json");
+            }
+
+            // only allow xml/json if the binding mode allows that (when off 
we still want to know if its xml or json)
+            if (bindingMode != null) {
+                isXml &= bindingMode.equals("off") || 
bindingMode.equals("auto") || bindingMode.contains("xml");
+                isJson &= bindingMode.equals("off") || 
bindingMode.equals("auto") || bindingMode.contains("json");
+
+                // if we do not yet know if its xml or json, then use the 
binding mode to know the mode
+                if (!isJson && !isXml) {
+                    isXml = bindingMode.equals("auto") || 
bindingMode.contains("xml");
+                    isJson = bindingMode.equals("auto") || 
bindingMode.contains("json");
+                }
+            }
+
+            // in case we have not yet been able to determine if xml or json, 
then use the same as in the unmarshaller
+            if (isXml && isJson) {
+                isXml = wasXml;
+                isJson = !wasXml;
+            }
+
+            // need to prepare exchange first
+            ExchangeHelper.prepareOutToIn(exchange);
+
+            // ensure there is a content type header (even if binding is off)
+            ensureHeaderContentType(produces, isXml, isJson, exchange);
+
+            if (bindingMode == null || "off".equals(bindingMode)) {
+                // binding is off, so no message body binding
+                return;
+            }
+
+            // is there any marshaller at all
+            if (jsonMarshal == null && xmlMarshal == null) {
+                return;
+            }
+
+            // is the body empty
+            if ((exchange.hasOut() && exchange.getOut().getBody() == null) || 
(!exchange.hasOut() && exchange.getIn().getBody() == null)) {
+                return;
+            }
+
+            String contentType = 
exchange.getIn().getHeader(Exchange.CONTENT_TYPE, String.class);
+            // need to lower-case so the contains check below can match if 
using upper case
+            contentType = contentType.toLowerCase(Locale.US);
+            try {
+                // favor json over xml
+                if (isJson && jsonMarshal != null) {
+                    // only marshal if its json content type
+                    if (contentType.contains("json")) {
+                        jsonMarshal.process(exchange);
+                    }
+                } else if (isXml && xmlMarshal != null) {
+                    // only marshal if its xml content type
+                    if (contentType.contains("xml")) {
+                        xmlMarshal.process(exchange);
+                    }
+                } else {
+                    // we could not bind
+                    if (bindingMode.equals("auto")) {
+                        // okay for auto we do not mind if we could not bind
+                    } else {
+                        if (bindingMode.contains("xml")) {
+                            exchange.setException(new BindingException("Cannot 
bind to xml as message body is not xml compatible", exchange));
+                        } else {
+                            exchange.setException(new BindingException("Cannot 
bind to json as message body is not json compatible", exchange));
+                        }
+                    }
+                }
+            } catch (Throwable e) {
+                exchange.setException(e);
+            }
+        }
+
+        private void ensureHeaderContentType(String contentType, boolean 
isXml, boolean isJson, Exchange exchange) {
+            // favor given content type
+            if (contentType != null) {
+                String type = ExchangeHelper.getContentType(exchange);
+                if (type == null) {
+                    exchange.getIn().setHeader(Exchange.CONTENT_TYPE, 
contentType);
+                }
+            }
+
+            // favor json over xml
+            if (isJson) {
+                // make sure there is a content-type with json
+                String type = ExchangeHelper.getContentType(exchange);
+                if (type == null) {
+                    exchange.getIn().setHeader(Exchange.CONTENT_TYPE, 
"application/json");
+                }
+            } else if (isXml) {
+                // make sure there is a content-type with xml
+                String type = ExchangeHelper.getContentType(exchange);
+                if (type == null) {
+                    exchange.getIn().setHeader(Exchange.CONTENT_TYPE, 
"application/xml");
+                }
+            }
+        }
+
+        @Override
+        public String toString() {
+            return "RestConsumerBindingMarshalOnCompletion";
+        }
+    }
+
+    private final class RestConsumerBindingCORSOnCompletion extends 
SynchronizationAdapter {
+
+        private final Map<String, String> corsHeaders;
+
+        private RestConsumerBindingCORSOnCompletion(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 "RestConsumerBindingCORSOnCompletion";
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/camel/blob/29cd3cdb/camel-core/src/main/java/org/apache/camel/component/rest/RestEndpoint.java
----------------------------------------------------------------------
diff --git 
a/camel-core/src/main/java/org/apache/camel/component/rest/RestEndpoint.java 
b/camel-core/src/main/java/org/apache/camel/component/rest/RestEndpoint.java
index 04b4ced..29cde1d 100644
--- a/camel-core/src/main/java/org/apache/camel/component/rest/RestEndpoint.java
+++ b/camel-core/src/main/java/org/apache/camel/component/rest/RestEndpoint.java
@@ -67,7 +67,7 @@ public class RestEndpoint extends DefaultEndpoint {
     private String inType;
     @UriParam(label = "common")
     private String outType;
-    @UriParam(label = "consumer")
+    @UriParam(label = "common")
     private String routeId;
     @UriParam(label = "consumer")
     private String description;
@@ -326,7 +326,11 @@ public class RestEndpoint extends DefaultEndpoint {
                 producer = factory.createProducer(getCamelContext(), host, 
method, path, uriTemplate, queryParameters, consumes, produces, parameters);
             }
             RestConfiguration config = 
getCamelContext().getRestConfiguration(cname, true);
-            return new RestProducer(this, producer, config);
+            RestProducer answer = new RestProducer(this, producer, config);
+            answer.setOutType(outType);
+            answer.setType(inType);
+
+            return answer;
         } else {
             throw new IllegalStateException("Cannot find RestProducerFactory 
in Registry or as a Component to use");
         }

http://git-wip-us.apache.org/repos/asf/camel/blob/29cd3cdb/camel-core/src/main/java/org/apache/camel/component/rest/RestProducer.java
----------------------------------------------------------------------
diff --git 
a/camel-core/src/main/java/org/apache/camel/component/rest/RestProducer.java 
b/camel-core/src/main/java/org/apache/camel/component/rest/RestProducer.java
index 97976d6..9ada4e4 100644
--- a/camel-core/src/main/java/org/apache/camel/component/rest/RestProducer.java
+++ b/camel-core/src/main/java/org/apache/camel/component/rest/RestProducer.java
@@ -20,6 +20,8 @@ import java.net.URLDecoder;
 import java.util.HashMap;
 import java.util.Map;
 
+import javax.xml.bind.JAXBContext;
+
 import org.apache.camel.AsyncCallback;
 import org.apache.camel.AsyncProcessor;
 import org.apache.camel.CamelContext;
@@ -29,10 +31,11 @@ import org.apache.camel.Producer;
 import org.apache.camel.impl.DefaultAsyncProducer;
 import org.apache.camel.spi.DataFormat;
 import org.apache.camel.spi.RestConfiguration;
-import org.apache.camel.tools.apt.helper.CollectionStringBuffer;
 import org.apache.camel.util.AsyncProcessorConverterHelper;
+import org.apache.camel.util.CollectionStringBuffer;
 import org.apache.camel.util.EndpointHelper;
 import org.apache.camel.util.FileUtil;
+import org.apache.camel.util.IntrospectionSupport;
 import org.apache.camel.util.ServiceHelper;
 import org.apache.camel.util.URISupport;
 
@@ -43,15 +46,17 @@ public class RestProducer extends DefaultAsyncProducer {
 
     private final CamelContext camelContext;
     private final RestConfiguration configuration;
+    private boolean prepareUriTemplate = true;
     private String bindingMode;
     private Boolean skipBindingOnErrorCode;
+    private String type;
+    private String outType;
 
     // the producer of the Camel component that is used as the HTTP client to 
call the REST service
     private AsyncProcessor producer;
+    // if binding is enabled then this processor should be used to wrap the 
call with binding before/after
     private AsyncProcessor binding;
 
-    private boolean prepareUriTemplate = true;
-
     public RestProducer(Endpoint endpoint, Producer producer, 
RestConfiguration configuration) {
         super(endpoint);
         this.camelContext = endpoint.getCamelContext();
@@ -61,12 +66,7 @@ public class RestProducer extends DefaultAsyncProducer {
 
     @Override
     public boolean process(Exchange exchange, AsyncCallback callback) {
-        // TODO: request bind to consumes context-type
-        // TODO: response bind to content-type returned in response
-        // TODO: binding
         // TODO: binding get type/outType from api-doc if possible
-        // TODO: binding reverse only enabled if outType configured
-        // TODO move consumer binding processor to this pacakge so they are 
both the same place
 
         try {
             prepareExchange(exchange);
@@ -116,6 +116,22 @@ public class RestProducer extends DefaultAsyncProducer {
         this.skipBindingOnErrorCode = skipBindingOnErrorCode;
     }
 
+    public String getType() {
+        return type;
+    }
+
+    public void setType(String type) {
+        this.type = type;
+    }
+
+    public String getOutType() {
+        return outType;
+    }
+
+    public void setOutType(String outType) {
+        this.outType = outType;
+    }
+
     protected void prepareExchange(Exchange exchange) throws Exception {
         boolean hasPath = false;
 
@@ -249,7 +265,26 @@ public class RestProducer extends DefaultAsyncProducer {
         }
 
         if (json != null) {
+            Class<?> clazz = null;
+            if (type != null) {
+                String typeName = type.endsWith("[]") ? type.substring(0, 
type.length() - 2) : type;
+                clazz = 
camelContext.getClassResolver().resolveMandatoryClass(typeName);
+            }
+            if (clazz != null) {
+                
IntrospectionSupport.setProperty(camelContext.getTypeConverter(), json, 
"unmarshalType", clazz);
+                
IntrospectionSupport.setProperty(camelContext.getTypeConverter(), json, 
"useList", type.endsWith("[]"));
+            }
             setAdditionalConfiguration(configuration, camelContext, json, 
"json.in.");
+
+            Class<?> outClazz = null;
+            if (outType != null) {
+                String typeName = outType.endsWith("[]") ? 
outType.substring(0, outType.length() - 2) : outType;
+                outClazz = 
camelContext.getClassResolver().resolveMandatoryClass(typeName);
+            }
+            if (outClazz != null) {
+                
IntrospectionSupport.setProperty(camelContext.getTypeConverter(), outJson, 
"unmarshalType", outClazz);
+                
IntrospectionSupport.setProperty(camelContext.getTypeConverter(), outJson, 
"useList", outType.endsWith("[]"));
+            }
             setAdditionalConfiguration(configuration, camelContext, outJson, 
"json.out.");
         }
 
@@ -274,11 +309,34 @@ public class RestProducer extends DefaultAsyncProducer {
         }
 
         if (jaxb != null) {
+            Class<?> clazz = null;
+            if (type != null) {
+                String typeName = type.endsWith("[]") ? type.substring(0, 
type.length() - 2) : type;
+                clazz = 
camelContext.getClassResolver().resolveMandatoryClass(typeName);
+            }
+            if (clazz != null) {
+                JAXBContext jc = JAXBContext.newInstance(clazz);
+                
IntrospectionSupport.setProperty(camelContext.getTypeConverter(), jaxb, 
"context", jc);
+            }
             setAdditionalConfiguration(configuration, camelContext, jaxb, 
"xml.in.");
+
+            Class<?> outClazz = null;
+            if (outType != null) {
+                String typeName = outType.endsWith("[]") ? 
outType.substring(0, outType.length() - 2) : outType;
+                outClazz = 
camelContext.getClassResolver().resolveMandatoryClass(typeName);
+            }
+            if (outClazz != null) {
+                JAXBContext jc = JAXBContext.newInstance(outClazz);
+                
IntrospectionSupport.setProperty(camelContext.getTypeConverter(), outJaxb, 
"context", jc);
+            } else if (clazz != null) {
+                // fallback and use the context from the input
+                JAXBContext jc = JAXBContext.newInstance(clazz);
+                
IntrospectionSupport.setProperty(camelContext.getTypeConverter(), outJaxb, 
"context", jc);
+            }
             setAdditionalConfiguration(configuration, camelContext, outJaxb, 
"xml.out.");
         }
 
-        return new RestProducerBindingProcessor(producer, camelContext, json, 
jaxb, outJson, outJaxb, mode, skip);
+        return new RestProducerBindingProcessor(producer, camelContext, json, 
jaxb, outJson, outJaxb, mode, skip, type, outType);
     }
 
     private void setAdditionalConfiguration(RestConfiguration config, 
CamelContext context,

http://git-wip-us.apache.org/repos/asf/camel/blob/29cd3cdb/camel-core/src/main/java/org/apache/camel/component/rest/RestProducerBindingProcessor.java
----------------------------------------------------------------------
diff --git 
a/camel-core/src/main/java/org/apache/camel/component/rest/RestProducerBindingProcessor.java
 
b/camel-core/src/main/java/org/apache/camel/component/rest/RestProducerBindingProcessor.java
index f17001d..7c254e3 100644
--- 
a/camel-core/src/main/java/org/apache/camel/component/rest/RestProducerBindingProcessor.java
+++ 
b/camel-core/src/main/java/org/apache/camel/component/rest/RestProducerBindingProcessor.java
@@ -29,11 +29,17 @@ import org.apache.camel.processor.MarshalProcessor;
 import org.apache.camel.processor.UnmarshalProcessor;
 import org.apache.camel.processor.binding.BindingException;
 import org.apache.camel.spi.DataFormat;
-import org.apache.camel.support.SynchronizationAdapter;
 import org.apache.camel.util.ExchangeHelper;
 import org.apache.camel.util.ObjectHelper;
 import org.apache.camel.util.ServiceHelper;
 
+/**
+ * A {@link org.apache.camel.Processor} that binds the REST producer request 
and reply messages
+ * from sources of json or xml to Java Objects.
+ * <p/>
+ * The binding uses {@link org.apache.camel.spi.DataFormat} for the actual 
work to transform
+ * from xml/json to Java Objects and reverse again.
+ */
 public class RestProducerBindingProcessor extends DelegateAsyncProcessor {
 
     private final CamelContext camelContext;
@@ -43,44 +49,49 @@ public class RestProducerBindingProcessor extends 
DelegateAsyncProcessor {
     private final AsyncProcessor xmlMarshal;
     private final String bindingMode;
     private final boolean skipBindingOnErrorCode;
+    private final String type;
+    private final String outType;
 
     public RestProducerBindingProcessor(AsyncProcessor processor, CamelContext 
camelContext,
                                         DataFormat jsonDataFormat, DataFormat 
xmlDataFormat,
                                         DataFormat outJsonDataFormat, 
DataFormat outXmlDataFormat,
-                                        String bindingMode, boolean 
skipBindingOnErrorCode) {
+                                        String bindingMode, boolean 
skipBindingOnErrorCode,
+                                        String type, String outType) {
 
         super(processor);
 
         this.camelContext = camelContext;
 
         if (jsonDataFormat != null) {
-            this.jsonUnmarshal = new UnmarshalProcessor(jsonDataFormat);
+            this.jsonUnmarshal = new UnmarshalProcessor(outJsonDataFormat);
         } else {
             this.jsonUnmarshal = null;
         }
         if (outJsonDataFormat != null) {
-            this.jsonMarshal = new MarshalProcessor(outJsonDataFormat);
-        } else if (jsonDataFormat != null) {
             this.jsonMarshal = new MarshalProcessor(jsonDataFormat);
+        } else if (jsonDataFormat != null) {
+            this.jsonMarshal = new MarshalProcessor(outJsonDataFormat);
         } else {
             this.jsonMarshal = null;
         }
 
         if (xmlDataFormat != null) {
-            this.xmlUnmarshal = new UnmarshalProcessor(xmlDataFormat);
+            this.xmlUnmarshal = new UnmarshalProcessor(outXmlDataFormat);
         } else {
             this.xmlUnmarshal = null;
         }
         if (outXmlDataFormat != null) {
-            this.xmlMarshal = new MarshalProcessor(outXmlDataFormat);
-        } else if (xmlDataFormat != null) {
             this.xmlMarshal = new MarshalProcessor(xmlDataFormat);
+        } else if (xmlDataFormat != null) {
+            this.xmlMarshal = new MarshalProcessor(outXmlDataFormat);
         } else {
             this.xmlMarshal = null;
         }
 
         this.bindingMode = bindingMode;
         this.skipBindingOnErrorCode = skipBindingOnErrorCode;
+        this.type = type;
+        this.outType = outType;
     }
 
     @Override
@@ -116,21 +127,26 @@ public class RestProducerBindingProcessor extends 
DelegateAsyncProcessor {
         boolean isXml = false;
         boolean isJson = false;
 
-        // skip binding for empty/null body
+        // skip before binding for empty/null body
         Object body = exchange.getIn().getBody();
         if (ObjectHelper.isEmpty(body)) {
-            // TODO: add reverse operation to call before callback
+            if (outType != null) {
+                // wrap callback to add reverse operation if we know the 
output type from the REST service
+                callback = new RestProducerBindingUnmarshalCallback(exchange, 
callback, jsonMarshal, xmlMarshal, false);
+            }
             // okay now we can continue routing to the producer
             return getProcessor().process(exchange, callback);
         }
 
-        // we only need to perform binding if the message body is POJO based
+        // we only need to perform before binding if the message body is POJO 
based
         // if its convertable to stream based then its not POJO based
         InputStream is = 
camelContext.getTypeConverter().tryConvertTo(InputStream.class, exchange, body);
         if (is != null) {
             exchange.getIn().setBody(is);
-            // add reverse operation
-            exchange.addOnCompletion(new 
RestProducerBindingUnmarshalOnCompletion(jsonMarshal, xmlMarshal, false));
+            if (outType != null) {
+                // wrap callback to add reverse operation if we know the 
output type from the REST service
+                callback = new RestProducerBindingUnmarshalCallback(exchange, 
callback, jsonMarshal, xmlMarshal, false);
+            }
             // okay now we can continue routing to the producer
             return getProcessor().process(exchange, callback);
         }
@@ -153,7 +169,6 @@ public class RestProducerBindingProcessor extends 
DelegateAsyncProcessor {
 
         // favor json over xml
         if (isJson && jsonMarshal != null) {
-            // TODO: add reverse operation to call before callback
             try {
                 jsonMarshal.process(exchange);
             } catch (Exception e) {
@@ -164,12 +179,13 @@ public class RestProducerBindingProcessor extends 
DelegateAsyncProcessor {
             }
             // need to prepare exchange first
             ExchangeHelper.prepareOutToIn(exchange);
-            // add reverse operation
-            exchange.addOnCompletion(new 
RestProducerBindingUnmarshalOnCompletion(jsonMarshal, xmlMarshal, false));
+            if (outType != null) {
+                // wrap callback to add reverse operation if we know the 
output type from the REST service
+                callback = new RestProducerBindingUnmarshalCallback(exchange, 
callback, jsonMarshal, xmlMarshal, false);
+            }
             // okay now we can continue routing to the producer
             return getProcessor().process(exchange, callback);
         } else if (isXml && xmlMarshal != null) {
-            // TODO: add reverse operation to call before callback
             try {
                 xmlMarshal.process(exchange);
             } catch (Exception e) {
@@ -180,16 +196,20 @@ public class RestProducerBindingProcessor extends 
DelegateAsyncProcessor {
             }
             // need to prepare exchange first
             ExchangeHelper.prepareOutToIn(exchange);
-            // add reverse operation
-            exchange.addOnCompletion(new 
RestProducerBindingUnmarshalOnCompletion(jsonMarshal, xmlMarshal, false));
+            if (outType != null) {
+                // wrap callback to add reverse operation if we know the 
output type from the REST service
+                callback = new RestProducerBindingUnmarshalCallback(exchange, 
callback, jsonMarshal, xmlMarshal, true);
+            }
             // okay now we can continue routing to the producer
             return getProcessor().process(exchange, callback);
         }
 
         // we could not bind
         if ("off".equals(bindingMode) || bindingMode.equals("auto")) {
-            // add reverse operation
-            exchange.addOnCompletion(new 
RestProducerBindingUnmarshalOnCompletion(jsonMarshal, xmlMarshal, false));
+            if (outType != null) {
+                // wrap callback to add reverse operation if we know the 
output type from the REST service
+                callback = new RestProducerBindingUnmarshalCallback(exchange, 
callback, jsonMarshal, xmlMarshal, false);
+            }
             // okay now we can continue routing to the producer
             return getProcessor().process(exchange, callback);
         } else {
@@ -204,20 +224,36 @@ public class RestProducerBindingProcessor extends 
DelegateAsyncProcessor {
         }
     }
 
-    private final class RestProducerBindingUnmarshalOnCompletion extends 
SynchronizationAdapter {
+    private final class RestProducerBindingUnmarshalCallback implements 
AsyncCallback {
 
+        private final Exchange exchange;
+        private final AsyncCallback callback;
         private final AsyncProcessor jsonMarshal;
         private final AsyncProcessor xmlMarshal;
         private boolean wasXml;
 
-        private RestProducerBindingUnmarshalOnCompletion(AsyncProcessor 
jsonMarshal, AsyncProcessor xmlMarshal, boolean wasXml) {
+        private RestProducerBindingUnmarshalCallback(Exchange exchange, 
AsyncCallback callback,
+                                                     AsyncProcessor 
jsonMarshal, AsyncProcessor xmlMarshal, boolean wasXml) {
+            this.exchange = exchange;
+            this.callback = callback;
             this.jsonMarshal = jsonMarshal;
             this.xmlMarshal = xmlMarshal;
             this.wasXml = wasXml;
         }
 
         @Override
-        public void onDone(Exchange exchange) {
+        public void done(boolean doneSync) {
+            try {
+                doDone();
+            } catch (Throwable e) {
+                exchange.setException(e);
+            } finally {
+                // ensure callback is called
+                callback.done(doneSync);
+            }
+        }
+
+        private void doDone() {
             // only unmarshal if there was no exception
             if (exchange.getException() != null) {
                 return;
@@ -234,20 +270,11 @@ public class RestProducerBindingProcessor extends 
DelegateAsyncProcessor {
             boolean isXml = false;
             boolean isJson = false;
 
-            // fallback to content type if still undecided
-            if (!isXml && !isJson) {
-                String contentType = ExchangeHelper.getContentType(exchange);
-                if (contentType != null) {
-                    isXml = 
contentType.toLowerCase(Locale.ENGLISH).contains("xml");
-                    isJson = 
contentType.toLowerCase(Locale.ENGLISH).contains("json");
-                }
-            }
-            // if content type could not tell us if it was json or xml, then 
fallback to if the binding was configured with
-            // that information in the consumes
-            if (!isXml && !isJson) {
-                // TODO:
-//                isXml = produces != null && 
produces.toLowerCase(Locale.ENGLISH).contains("xml");
-//                isJson = produces != null && 
produces.toLowerCase(Locale.ENGLISH).contains("json");
+            // check the content-type if its json or xml
+            String contentType = ExchangeHelper.getContentType(exchange);
+            if (contentType != null) {
+                isXml = 
contentType.toLowerCase(Locale.ENGLISH).contains("xml");
+                isJson = 
contentType.toLowerCase(Locale.ENGLISH).contains("json");
             }
 
             // only allow xml/json if the binding mode allows that (when off 
we still want to know if its xml or json)
@@ -272,8 +299,7 @@ public class RestProducerBindingProcessor extends 
DelegateAsyncProcessor {
             ExchangeHelper.prepareOutToIn(exchange);
 
             // ensure there is a content type header (even if binding is off)
-            // TODO:
-            // ensureHeaderContentType(produces, isXml, isJson, exchange);
+            ensureHeaderContentType(isXml, isJson, exchange);
 
             if (bindingMode == null || "off".equals(bindingMode)) {
                 // binding is off, so no message body binding
@@ -290,7 +316,7 @@ public class RestProducerBindingProcessor extends 
DelegateAsyncProcessor {
                 return;
             }
 
-            String contentType = 
exchange.getIn().getHeader(Exchange.CONTENT_TYPE, String.class);
+            contentType = exchange.getIn().getHeader(Exchange.CONTENT_TYPE, 
String.class);
             // need to lower-case so the contains check below can match if 
using upper case
             contentType = contentType.toLowerCase(Locale.US);
             try {
@@ -311,9 +337,9 @@ public class RestProducerBindingProcessor extends 
DelegateAsyncProcessor {
                         // okay for auto we do not mind if we could not bind
                     } else {
                         if (bindingMode.contains("xml")) {
-                            exchange.setException(new BindingException("Cannot 
bind to xml as message body is not xml compatible", exchange));
+                            exchange.setException(new BindingException("Cannot 
bind from xml as message body is not xml compatible", exchange));
                         } else {
-                            exchange.setException(new BindingException("Cannot 
bind to json as message body is not json compatible", exchange));
+                            exchange.setException(new BindingException("Cannot 
bind from json as message body is not json compatible", exchange));
                         }
                     }
                 }
@@ -322,15 +348,7 @@ public class RestProducerBindingProcessor extends 
DelegateAsyncProcessor {
             }
         }
 
-        private void ensureHeaderContentType(String contentType, boolean 
isXml, boolean isJson, Exchange exchange) {
-            // favor given content type
-            if (contentType != null) {
-                String type = ExchangeHelper.getContentType(exchange);
-                if (type == null) {
-                    exchange.getIn().setHeader(Exchange.CONTENT_TYPE, 
contentType);
-                }
-            }
-
+        private void ensureHeaderContentType(boolean isXml, boolean isJson, 
Exchange exchange) {
             // favor json over xml
             if (isJson) {
                 // make sure there is a content-type with json
@@ -349,7 +367,7 @@ public class RestProducerBindingProcessor extends 
DelegateAsyncProcessor {
 
         @Override
         public String toString() {
-            return "RestProducerBindingUnmarshalOnCompletion";
+            return "RestProducerBindingUnmarshalCallback";
         }
     }
 

http://git-wip-us.apache.org/repos/asf/camel/blob/29cd3cdb/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 e943d2d..8aeef30 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
@@ -28,7 +28,7 @@ import javax.xml.bind.annotation.XmlTransient;
 import org.apache.camel.CamelContext;
 import org.apache.camel.Processor;
 import org.apache.camel.model.NoOutputDefinition;
-import org.apache.camel.processor.binding.RestConsumerBindingProcessor;
+import org.apache.camel.component.rest.RestConsumerBindingProcessor;
 import org.apache.camel.spi.DataFormat;
 import org.apache.camel.spi.Metadata;
 import org.apache.camel.spi.RestConfiguration;

http://git-wip-us.apache.org/repos/asf/camel/blob/29cd3cdb/camel-core/src/main/java/org/apache/camel/processor/binding/RestConsumerBindingProcessor.java
----------------------------------------------------------------------
diff --git 
a/camel-core/src/main/java/org/apache/camel/processor/binding/RestConsumerBindingProcessor.java
 
b/camel-core/src/main/java/org/apache/camel/processor/binding/RestConsumerBindingProcessor.java
deleted file mode 100644
index d9f0656..0000000
--- 
a/camel-core/src/main/java/org/apache/camel/processor/binding/RestConsumerBindingProcessor.java
+++ /dev/null
@@ -1,460 +0,0 @@
-/**
- * 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.processor.binding;
-
-import java.util.Locale;
-import java.util.Map;
-
-import org.apache.camel.AsyncCallback;
-import org.apache.camel.AsyncProcessor;
-import org.apache.camel.CamelContext;
-import org.apache.camel.CamelContextAware;
-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;
-import org.apache.camel.util.ExchangeHelper;
-import org.apache.camel.util.MessageHelper;
-import org.apache.camel.util.ObjectHelper;
-import org.apache.camel.util.ServiceHelper;
-
-/**
- * A {@link org.apache.camel.Processor} that binds the REST DSL incoming and 
outgoing messages
- * from sources of json or xml to Java Objects.
- * <p/>
- * The binding uses {@link org.apache.camel.spi.DataFormat} for the actual 
work to transform
- * from xml/json to Java Objects and reverse again.
- */
-public class RestConsumerBindingProcessor extends ServiceSupport implements 
AsyncProcessor {
-
-    private final CamelContext camelContext;
-    private final AsyncProcessor jsonUnmarshal;
-    private final AsyncProcessor xmlUnmarshal;
-    private final AsyncProcessor jsonMarshal;
-    private final AsyncProcessor xmlMarshal;
-    private final String consumes;
-    private final String produces;
-    private final String bindingMode;
-    private final boolean skipBindingOnErrorCode;
-    private final boolean enableCORS;
-    private final Map<String, String> corsHeaders;
-    private final Map<String, String> queryDefaultValues;
-
-    public RestConsumerBindingProcessor(CamelContext camelContext, DataFormat 
jsonDataFormat, DataFormat xmlDataFormat,
-                                        DataFormat outJsonDataFormat, 
DataFormat outXmlDataFormat,
-                                        String consumes, String produces, 
String bindingMode,
-                                        boolean skipBindingOnErrorCode, 
boolean enableCORS,
-                                        Map<String, String> corsHeaders,
-                                        Map<String, String> 
queryDefaultValues) {
-
-        this.camelContext = camelContext;
-
-        if (jsonDataFormat != null) {
-            this.jsonUnmarshal = new UnmarshalProcessor(jsonDataFormat);
-        } else {
-            this.jsonUnmarshal = null;
-        }
-        if (outJsonDataFormat != null) {
-            this.jsonMarshal = new MarshalProcessor(outJsonDataFormat);
-        } else if (jsonDataFormat != null) {
-            this.jsonMarshal = new MarshalProcessor(jsonDataFormat);
-        } else {
-            this.jsonMarshal = null;
-        }
-
-        if (xmlDataFormat != null) {
-            this.xmlUnmarshal = new UnmarshalProcessor(xmlDataFormat);
-        } else {
-            this.xmlUnmarshal = null;
-        }
-        if (outXmlDataFormat != null) {
-            this.xmlMarshal = new MarshalProcessor(outXmlDataFormat);
-        } else if (xmlDataFormat != null) {
-            this.xmlMarshal = new MarshalProcessor(xmlDataFormat);
-        } else {
-            this.xmlMarshal = null;
-        }
-
-        this.consumes = consumes;
-        this.produces = produces;
-        this.bindingMode = bindingMode;
-        this.skipBindingOnErrorCode = skipBindingOnErrorCode;
-        this.enableCORS = enableCORS;
-        this.corsHeaders = corsHeaders;
-        this.queryDefaultValues = queryDefaultValues;
-    }
-
-    @Override
-    public void process(Exchange exchange) throws Exception {
-        AsyncProcessorHelper.process(this, exchange);
-    }
-
-    @Override
-    public boolean process(Exchange exchange, final AsyncCallback callback) {
-        if (enableCORS) {
-            exchange.addOnCompletion(new 
RestBindingCORSOnCompletion(corsHeaders));
-        }
-
-        String method = exchange.getIn().getHeader(Exchange.HTTP_METHOD, 
String.class);
-        if ("OPTIONS".equalsIgnoreCase(method)) {
-            // for OPTIONS methods then we should not route at all as its part 
of CORS
-            exchange.setProperty(Exchange.ROUTE_STOP, true);
-            callback.done(true);
-            return true;
-        }
-
-        boolean isXml = false;
-        boolean isJson = false;
-
-        String contentType = ExchangeHelper.getContentType(exchange);
-        if (contentType != null) {
-            isXml = contentType.toLowerCase(Locale.ENGLISH).contains("xml");
-            isJson = contentType.toLowerCase(Locale.ENGLISH).contains("json");
-        }
-        // if content type could not tell us if it was json or xml, then 
fallback to if the binding was configured with
-        // that information in the consumes
-        if (!isXml && !isJson) {
-            isXml = consumes != null && 
consumes.toLowerCase(Locale.ENGLISH).contains("xml");
-            isJson = consumes != null && 
consumes.toLowerCase(Locale.ENGLISH).contains("json");
-        }
-
-        // only allow xml/json if the binding mode allows that
-        isXml &= bindingMode.equals("auto") || bindingMode.contains("xml");
-        isJson &= bindingMode.equals("auto") || bindingMode.contains("json");
-
-        // if we do not yet know if its xml or json, then use the binding mode 
to know the mode
-        if (!isJson && !isXml) {
-            isXml = bindingMode.equals("auto") || bindingMode.contains("xml");
-            isJson = bindingMode.equals("auto") || 
bindingMode.contains("json");
-        }
-
-        String accept = exchange.getIn().getHeader("Accept", String.class);
-
-        String body = null;
-        if (exchange.getIn().getBody() != null) {
-
-            // okay we have a binding mode, so need to check for empty body as 
that can cause the marshaller to fail
-            // as they assume a non-empty body
-            if (isXml || isJson) {
-                // we have binding enabled, so we need to know if there body 
is empty or not
-                // so force reading the body as a String which we can work with
-                body = MessageHelper.extractBodyAsString(exchange.getIn());
-                if (body != null) {
-                    exchange.getIn().setBody(body);
-
-                    if (isXml && isJson) {
-                        // we have still not determined between xml or json, 
so check the body if its xml based or not
-                        isXml = body.startsWith("<");
-                        isJson = !isXml;
-                    }
-                }
-            }
-        }
-
-        // add missing default values which are mapped as headers
-        if (queryDefaultValues != null) {
-            for (Map.Entry<String, String> entry : 
queryDefaultValues.entrySet()) {
-                if (exchange.getIn().getHeader(entry.getKey()) == null) {
-                    exchange.getIn().setHeader(entry.getKey(), 
entry.getValue());
-                }
-            }
-        }
-
-        // favor json over xml
-        if (isJson && jsonUnmarshal != null) {
-            // add reverse operation
-            exchange.addOnCompletion(new 
RestBindingMarshalOnCompletion(exchange.getFromRouteId(), jsonMarshal, 
xmlMarshal, false, accept));
-            if (ObjectHelper.isNotEmpty(body)) {
-                return jsonUnmarshal.process(exchange, callback);
-            } else {
-                callback.done(true);
-                return true;
-            }
-        } else if (isXml && xmlUnmarshal != null) {
-            // add reverse operation
-            exchange.addOnCompletion(new 
RestBindingMarshalOnCompletion(exchange.getFromRouteId(), jsonMarshal, 
xmlMarshal, true, accept));
-            if (ObjectHelper.isNotEmpty(body)) {
-                return xmlUnmarshal.process(exchange, callback);
-            } else {
-                callback.done(true);
-                return true;
-            }
-        }
-
-        // we could not bind
-        if ("off".equals(bindingMode) || bindingMode.equals("auto")) {
-            // okay for auto we do not mind if we could not bind
-            exchange.addOnCompletion(new 
RestBindingMarshalOnCompletion(exchange.getFromRouteId(), jsonMarshal, 
xmlMarshal, false, accept));
-            callback.done(true);
-            return true;
-        } else {
-            if (bindingMode.contains("xml")) {
-                exchange.setException(new BindingException("Cannot bind to xml 
as message body is not xml compatible", exchange));
-            } else {
-                exchange.setException(new BindingException("Cannot bind to 
json as message body is not json compatible", exchange));
-            }
-            callback.done(true);
-            return true;
-        }
-    }
-
-    @Override
-    public String toString() {
-        return "RestBindingProcessor";
-    }
-
-    @Override
-    protected void doStart() throws Exception {
-        // inject CamelContext before starting
-        if (jsonMarshal instanceof CamelContextAware) {
-            ((CamelContextAware) jsonMarshal).setCamelContext(camelContext);
-        }
-        if (jsonUnmarshal instanceof CamelContextAware) {
-            ((CamelContextAware) jsonUnmarshal).setCamelContext(camelContext);
-        }
-        if (xmlMarshal instanceof CamelContextAware) {
-            ((CamelContextAware) xmlMarshal).setCamelContext(camelContext);
-        }
-        if (xmlUnmarshal instanceof CamelContextAware) {
-            ((CamelContextAware) xmlUnmarshal).setCamelContext(camelContext);
-        }
-        ServiceHelper.startServices(jsonMarshal, jsonUnmarshal, xmlMarshal, 
xmlUnmarshal);
-    }
-
-    @Override
-    protected void doStop() throws Exception {
-        ServiceHelper.stopServices(jsonMarshal, jsonUnmarshal, xmlMarshal, 
xmlUnmarshal);
-    }
-
-    /**
-     * An {@link org.apache.camel.spi.Synchronization} that does the reverse 
operation
-     * of marshalling from POJO to json/xml
-     */
-    private final class RestBindingMarshalOnCompletion extends 
SynchronizationAdapter {
-
-        private final AsyncProcessor jsonMarshal;
-        private final AsyncProcessor xmlMarshal;
-        private final String routeId;
-        private boolean wasXml;
-        private String accept;
-
-        private RestBindingMarshalOnCompletion(String routeId, AsyncProcessor 
jsonMarshal, AsyncProcessor xmlMarshal, boolean wasXml, String accept) {
-            this.routeId = routeId;
-            this.jsonMarshal = jsonMarshal;
-            this.xmlMarshal = xmlMarshal;
-            this.wasXml = wasXml;
-            this.accept = accept;
-        }
-
-        @Override
-        public void onAfterRoute(Route route, Exchange exchange) {
-            // we use the onAfterRoute callback, to ensure the data has been 
marshalled before
-            // the consumer writes the response back
-
-            // only trigger when it was the 1st route that was done
-            if (!routeId.equals(route.getId())) {
-                return;
-            }
-
-            // only marshal if there was no exception
-            if (exchange.getException() != null) {
-                return;
-            }
-
-            if (skipBindingOnErrorCode) {
-                Integer code = exchange.hasOut() ? 
exchange.getOut().getHeader(Exchange.HTTP_RESPONSE_CODE, Integer.class) : 
exchange.getIn().getHeader(Exchange.HTTP_RESPONSE_CODE, Integer.class);
-                // if there is a custom http error code then skip binding
-                if (code != null && code >= 300) {
-                    return;
-                }
-            }
-
-            boolean isXml = false;
-            boolean isJson = false;
-
-            // accept takes precedence
-            if (accept != null) {
-                isXml = accept.toLowerCase(Locale.ENGLISH).contains("xml");
-                isJson = accept.toLowerCase(Locale.ENGLISH).contains("json");
-            }
-            // fallback to content type if still undecided
-            if (!isXml && !isJson) {
-                String contentType = ExchangeHelper.getContentType(exchange);
-                if (contentType != null) {
-                    isXml = 
contentType.toLowerCase(Locale.ENGLISH).contains("xml");
-                    isJson = 
contentType.toLowerCase(Locale.ENGLISH).contains("json");
-                }
-            }
-            // if content type could not tell us if it was json or xml, then 
fallback to if the binding was configured with
-            // that information in the consumes
-            if (!isXml && !isJson) {
-                isXml = produces != null && 
produces.toLowerCase(Locale.ENGLISH).contains("xml");
-                isJson = produces != null && 
produces.toLowerCase(Locale.ENGLISH).contains("json");
-            }
-
-            // only allow xml/json if the binding mode allows that (when off 
we still want to know if its xml or json)
-            if (bindingMode != null) {
-                isXml &= bindingMode.equals("off") || 
bindingMode.equals("auto") || bindingMode.contains("xml");
-                isJson &= bindingMode.equals("off") || 
bindingMode.equals("auto") || bindingMode.contains("json");
-
-                // if we do not yet know if its xml or json, then use the 
binding mode to know the mode
-                if (!isJson && !isXml) {
-                    isXml = bindingMode.equals("auto") || 
bindingMode.contains("xml");
-                    isJson = bindingMode.equals("auto") || 
bindingMode.contains("json");
-                }
-            }
-
-            // in case we have not yet been able to determine if xml or json, 
then use the same as in the unmarshaller
-            if (isXml && isJson) {
-                isXml = wasXml;
-                isJson = !wasXml;
-            }
-
-            // need to prepare exchange first
-            ExchangeHelper.prepareOutToIn(exchange);
-
-            // ensure there is a content type header (even if binding is off)
-            ensureHeaderContentType(produces, isXml, isJson, exchange);
-
-            if (bindingMode == null || "off".equals(bindingMode)) {
-                // binding is off, so no message body binding
-                return;
-            }
-
-            // is there any marshaller at all
-            if (jsonMarshal == null && xmlMarshal == null) {
-                return;
-            }
-
-            // is the body empty
-            if ((exchange.hasOut() && exchange.getOut().getBody() == null) || 
(!exchange.hasOut() && exchange.getIn().getBody() == null)) {
-                return;
-            }
-
-            String contentType = 
exchange.getIn().getHeader(Exchange.CONTENT_TYPE, String.class);
-            // need to lower-case so the contains check below can match if 
using upper case
-            contentType = contentType.toLowerCase(Locale.US);
-            try {
-                // favor json over xml
-                if (isJson && jsonMarshal != null) {
-                    // only marshal if its json content type
-                    if (contentType.contains("json")) {
-                        jsonMarshal.process(exchange);
-                    }
-                } else if (isXml && xmlMarshal != null) {
-                    // only marshal if its xml content type
-                    if (contentType.contains("xml")) {
-                        xmlMarshal.process(exchange);
-                    }
-                } else {
-                    // we could not bind
-                    if (bindingMode.equals("auto")) {
-                        // okay for auto we do not mind if we could not bind
-                    } else {
-                        if (bindingMode.contains("xml")) {
-                            exchange.setException(new BindingException("Cannot 
bind to xml as message body is not xml compatible", exchange));
-                        } else {
-                            exchange.setException(new BindingException("Cannot 
bind to json as message body is not json compatible", exchange));
-                        }
-                    }
-                }
-            } catch (Throwable e) {
-                exchange.setException(e);
-            }
-        }
-
-        private void ensureHeaderContentType(String contentType, boolean 
isXml, boolean isJson, Exchange exchange) {
-            // favor given content type
-            if (contentType != null) {
-                String type = ExchangeHelper.getContentType(exchange);
-                if (type == null) {
-                    exchange.getIn().setHeader(Exchange.CONTENT_TYPE, 
contentType);
-                }
-            }
-
-            // favor json over xml
-            if (isJson) {
-                // make sure there is a content-type with json
-                String type = ExchangeHelper.getContentType(exchange);
-                if (type == null) {
-                    exchange.getIn().setHeader(Exchange.CONTENT_TYPE, 
"application/json");
-                }
-            } else if (isXml) {
-                // make sure there is a content-type with xml
-                String type = ExchangeHelper.getContentType(exchange);
-                if (type == null) {
-                    exchange.getIn().setHeader(Exchange.CONTENT_TYPE, 
"application/xml");
-                }
-            }
-        }
-
-        @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/29cd3cdb/components/camel-jetty9/src/test/java/org/apache/camel/component/jetty/rest/producer/JettyRestProducerPojoInOutTest.java
----------------------------------------------------------------------
diff --git 
a/components/camel-jetty9/src/test/java/org/apache/camel/component/jetty/rest/producer/JettyRestProducerPojoInOutTest.java
 
b/components/camel-jetty9/src/test/java/org/apache/camel/component/jetty/rest/producer/JettyRestProducerPojoInOutTest.java
index bd4f2bf..a0fae86 100644
--- 
a/components/camel-jetty9/src/test/java/org/apache/camel/component/jetty/rest/producer/JettyRestProducerPojoInOutTest.java
+++ 
b/components/camel-jetty9/src/test/java/org/apache/camel/component/jetty/rest/producer/JettyRestProducerPojoInOutTest.java
@@ -69,7 +69,8 @@ public class JettyRestProducerPojoInOutTest extends 
BaseJettyTest {
         user.setId(123);
         user.setName("Donald Duck");
 
-        CountryPojo pojo = fluentTemplate.to("rest:post:users/lives")
+        // must provide outType parameter to tell Camel to bind the output 
from the REST service from json to POJO
+        CountryPojo pojo = 
fluentTemplate.to("rest:post:users/lives?outType=org.apache.camel.component.jetty.rest.CountryPojo")
                 .withHeader(Exchange.CONTENT_TYPE, "application/json")
                 .withBody(user).request(CountryPojo.class);
 
@@ -90,10 +91,9 @@ public class JettyRestProducerPojoInOutTest extends 
BaseJettyTest {
                 // use the rest DSL to define the rest services
                 rest("/users/")
                         // just return the default country here
-                        .get("lives").to("direct:start")
-                        
.post("lives").type(UserPojo.class).outType(CountryPojo.class)
+                    .get("lives").to("direct:start")
+                    
.post("lives").type(UserPojo.class).outType(CountryPojo.class)
                         .route()
-                        .log("Lives where")
                         .bean(new UserService(), "livesWhere");
 
                 CountryPojo country = new CountryPojo();

http://git-wip-us.apache.org/repos/asf/camel/blob/29cd3cdb/components/camel-jetty9/src/test/resources/log4j2.properties
----------------------------------------------------------------------
diff --git a/components/camel-jetty9/src/test/resources/log4j2.properties 
b/components/camel-jetty9/src/test/resources/log4j2.properties
index d54c30d..c863dc3 100644
--- a/components/camel-jetty9/src/test/resources/log4j2.properties
+++ b/components/camel-jetty9/src/test/resources/log4j2.properties
@@ -25,4 +25,4 @@ appender.out.name = out
 appender.out.layout.type = PatternLayout
 appender.out.layout.pattern = %d [%-15.15t] %-5p %-30.30c{1} - %m%n
 rootLogger.level = INFO
-rootLogger.appenderRef.out.ref = out
+rootLogger.appenderRef.file.ref = file

Reply via email to