Repository: camel Updated Branches: refs/heads/master 264ded97b -> 4d6f2a901
CAMEL-10908 Introduce DataTypeAware interface and let MessageSupport implement it This also solves CAMEL-10890 Project: http://git-wip-us.apache.org/repos/asf/camel/repo Commit: http://git-wip-us.apache.org/repos/asf/camel/commit/0366bc15 Tree: http://git-wip-us.apache.org/repos/asf/camel/tree/0366bc15 Diff: http://git-wip-us.apache.org/repos/asf/camel/diff/0366bc15 Branch: refs/heads/master Commit: 0366bc15c8ab8e6229246203c1fa4723c0f5a251 Parents: 264ded9 Author: Tomohisa Igarashi <tm.igara...@gmail.com> Authored: Wed Mar 1 09:06:41 2017 +0900 Committer: Tomohisa Igarashi <tm.igara...@gmail.com> Committed: Wed Mar 1 13:51:01 2017 +0900 ---------------------------------------------------------------------- .../java/org/apache/camel/DynamicRouter.java | 2 +- .../main/java/org/apache/camel/Exchange.java | 3 -- .../org/apache/camel/impl/MessageSupport.java | 28 +++++++++- .../apache/camel/model/InputTypeDefinition.java | 6 ++- .../camel/model/OutputTypeDefinition.java | 6 ++- .../org/apache/camel/model/RouteDefinition.java | 38 ++++++++++--- .../apache/camel/processor/ContractAdvice.java | 57 ++++++++++---------- .../camel/processor/RestBindingAdvice.java | 27 ++++++---- .../java/org/apache/camel/spi/DataType.java | 11 ++++ .../org/apache/camel/spi/DataTypeAware.java | 51 ++++++++++++++++++ .../java/org/apache/camel/spi/Transformer.java | 12 ++--- .../java/org/apache/camel/spi/Validator.java | 16 +++--- .../transformer/TransformerContractTest.java | 9 +++- .../impl/transformer/TransformerRouteTest.java | 3 +- .../impl/validator/ValidatorRouteTest.java | 11 ++-- .../impl/validator/SpringValidatorRouteTest.xml | 3 -- .../transformer/demo/client/CamelClient.java | 15 ++++-- .../transformer/OrderRouteSpringTest.java | 14 +++-- 18 files changed, 219 insertions(+), 93 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/camel/blob/0366bc15/camel-core/src/main/java/org/apache/camel/DynamicRouter.java ---------------------------------------------------------------------- diff --git a/camel-core/src/main/java/org/apache/camel/DynamicRouter.java b/camel-core/src/main/java/org/apache/camel/DynamicRouter.java index b51ab76..d6105cd 100644 --- a/camel-core/src/main/java/org/apache/camel/DynamicRouter.java +++ b/camel-core/src/main/java/org/apache/camel/DynamicRouter.java @@ -42,7 +42,7 @@ import java.lang.annotation.Target; */ @Retention(RetentionPolicy.RUNTIME) @Documented -@Target( {ElementType.FIELD, ElementType.METHOD, ElementType.CONSTRUCTOR}) +@Target({ElementType.FIELD, ElementType.METHOD, ElementType.CONSTRUCTOR}) public @interface DynamicRouter { /** http://git-wip-us.apache.org/repos/asf/camel/blob/0366bc15/camel-core/src/main/java/org/apache/camel/Exchange.java ---------------------------------------------------------------------- diff --git a/camel-core/src/main/java/org/apache/camel/Exchange.java b/camel-core/src/main/java/org/apache/camel/Exchange.java index cc2acaa..a736174 100644 --- a/camel-core/src/main/java/org/apache/camel/Exchange.java +++ b/camel-core/src/main/java/org/apache/camel/Exchange.java @@ -233,9 +233,6 @@ public interface Exchange { String XSLT_FATAL_ERROR = "CamelXsltFatalError"; String XSLT_WARNING = "CamelXsltWarning"; - String INPUT_TYPE = "CamelInputType"; - String OUTPUT_TYPE = "CamelOutputType"; - /** * Returns the {@link ExchangePattern} (MEP) of this exchange. * http://git-wip-us.apache.org/repos/asf/camel/blob/0366bc15/camel-core/src/main/java/org/apache/camel/impl/MessageSupport.java ---------------------------------------------------------------------- diff --git a/camel-core/src/main/java/org/apache/camel/impl/MessageSupport.java b/camel-core/src/main/java/org/apache/camel/impl/MessageSupport.java index a4033d0..d02fbae 100644 --- a/camel-core/src/main/java/org/apache/camel/impl/MessageSupport.java +++ b/camel-core/src/main/java/org/apache/camel/impl/MessageSupport.java @@ -20,6 +20,8 @@ import org.apache.camel.Exchange; import org.apache.camel.InvalidPayloadException; import org.apache.camel.Message; import org.apache.camel.TypeConverter; +import org.apache.camel.spi.DataType; +import org.apache.camel.spi.DataTypeAware; /** * A base class for implementation inheritance providing the core @@ -31,10 +33,11 @@ import org.apache.camel.TypeConverter; * * @version */ -public abstract class MessageSupport implements Message { +public abstract class MessageSupport implements Message, DataTypeAware { private Exchange exchange; private Object body; private String messageId; + private DataType dataType; @Override public String toString() { @@ -112,6 +115,7 @@ public abstract class MessageSupport implements Message { public void setBody(Object body) { this.body = body; + this.dataType = body != null ? new DataType(body.getClass()) : null; } public <T> void setBody(Object value, Class<T> type) { @@ -125,6 +129,25 @@ public abstract class MessageSupport implements Message { setBody(value); } + @Override + public void setBody(Object body, DataType type) { + this.body = body; + this.dataType = type; + } + + @Override + public DataType getDataType() { + if (this.dataType == null) { + this.dataType = body != null ? new DataType(body.getClass()) : null; + } + return this.dataType; + } + + @Override + public void setDataType(DataType type) { + this.dataType = type; + } + public Message copy() { Message answer = newInstance(); answer.copyFrom(this); @@ -137,6 +160,9 @@ public abstract class MessageSupport implements Message { return; } copyFromWithNewBody(that, that.getBody()); + if (that instanceof DataTypeAware) { + setDataType(((DataTypeAware)that).getDataType()); + } } public void copyFromWithNewBody(Message that, Object newBody) { http://git-wip-us.apache.org/repos/asf/camel/blob/0366bc15/camel-core/src/main/java/org/apache/camel/model/InputTypeDefinition.java ---------------------------------------------------------------------- diff --git a/camel-core/src/main/java/org/apache/camel/model/InputTypeDefinition.java b/camel-core/src/main/java/org/apache/camel/model/InputTypeDefinition.java index 7a74f8e..3ae9686 100644 --- a/camel-core/src/main/java/org/apache/camel/model/InputTypeDefinition.java +++ b/camel-core/src/main/java/org/apache/camel/model/InputTypeDefinition.java @@ -23,14 +23,16 @@ import javax.xml.bind.annotation.XmlRootElement; import org.apache.camel.spi.Metadata; /** - * Set data type of the input message. + * Set the expected data type of the input message. If the actual message type is different at runtime, + * camel look for a required {@link Transformer} and apply if exists. If validate attribute is true + * then camel applies {@link Validator} as well. * Type name consists of two parts, 'scheme' and 'name' connected with ':'. For Java type 'name' * is a fully qualified class name. For example {@code java:java.lang.String}, {@code json:ABCOrder}. * It's also possible to specify only scheme part, so that it works like a wildcard. If only 'xml' * is specified, all the XML message matches. It's handy to add only one transformer/validator * for all the transformation from/to XML. * - * {@see OutputTypeDefinition} + * @see {@link OutputTypeDefinition} {@link Transformer} {@link Validator} */ @Metadata(label = "configuration") @XmlRootElement(name = "inputType") http://git-wip-us.apache.org/repos/asf/camel/blob/0366bc15/camel-core/src/main/java/org/apache/camel/model/OutputTypeDefinition.java ---------------------------------------------------------------------- diff --git a/camel-core/src/main/java/org/apache/camel/model/OutputTypeDefinition.java b/camel-core/src/main/java/org/apache/camel/model/OutputTypeDefinition.java index 9862f05..c658aa4 100644 --- a/camel-core/src/main/java/org/apache/camel/model/OutputTypeDefinition.java +++ b/camel-core/src/main/java/org/apache/camel/model/OutputTypeDefinition.java @@ -23,14 +23,16 @@ import javax.xml.bind.annotation.XmlRootElement; import org.apache.camel.spi.Metadata; /** - * Sets data type of the output message. + * Set the expected data type of the output message. If the actual message type is different at runtime, + * camel look for a required {@link Transformer} and apply if exists. If validate attribute is true + * then camel applies {@link Validator} as well. * Type name consists of two parts, 'scheme' and 'name' connected with ':'. For Java type 'name' * is a fully qualified class name. For example {@code java:java.lang.String}, {@code json:ABCOrder}. * It's also possible to specify only scheme part, so that it works like a wildcard. If only 'xml' * is specified, all the XML message matches. It's handy to add only one transformer/validator * for all the XML-Java transformation. * - * {@see InputTypeDefinition} + * @see {@link InputTypeDefinition} {@link Transformer} {@link Validator} */ @Metadata(label = "configuration") @XmlRootElement(name = "outputType") http://git-wip-us.apache.org/repos/asf/camel/blob/0366bc15/camel-core/src/main/java/org/apache/camel/model/RouteDefinition.java ---------------------------------------------------------------------- diff --git a/camel-core/src/main/java/org/apache/camel/model/RouteDefinition.java b/camel-core/src/main/java/org/apache/camel/model/RouteDefinition.java index 688cb64..eee2f3b 100644 --- a/camel-core/src/main/java/org/apache/camel/model/RouteDefinition.java +++ b/camel-core/src/main/java/org/apache/camel/model/RouteDefinition.java @@ -635,7 +635,10 @@ public class RouteDefinition extends ProcessorDefinition<RouteDefinition> { } /** - * Declare an input type. + * Declare the expected data type of the input message. If the actual message type is different + * at runtime, camel look for a required {@link Transformer} and apply if exists. + * The type name consists of two parts, 'scheme' and 'name' connected with ':'. For Java type 'name' + * is a fully qualified class name. For example {@code java:java.lang.String}, {@code json:ABCOrder}. * * @see {@link org.apache.camel.spi.Transformer} * @param urn input type URN @@ -649,7 +652,11 @@ public class RouteDefinition extends ProcessorDefinition<RouteDefinition> { } /** - * Declare an input type with validation enabled. + * Declare the expected data type of the input message with content validation enabled. + * If the actual message type is different at runtime, camel look for a required + * {@link Transformer} and apply if exists, and then applies {@link Validator} as well. + * The type name consists of two parts, 'scheme' and 'name' connected with ':'. For Java type 'name' + * is a fully qualified class name. For example {@code java:java.lang.String}, {@code json:ABCOrder}. * * @see {@link org.apache.camel.spi.Transformer}, {@link org.apache.camel.spi.Validator} * @param urn input type URN @@ -663,7 +670,9 @@ public class RouteDefinition extends ProcessorDefinition<RouteDefinition> { } /** - * Declare an input type with Java class. + * Declare the expected data type of the input message by Java class. + * If the actual message type is different at runtime, camel look for a required + * {@link Transformer} and apply if exists. * * @see {@link org.apache.camel.spi.Transformer} * @param clazz Class object of the input type @@ -677,7 +686,9 @@ public class RouteDefinition extends ProcessorDefinition<RouteDefinition> { } /** - * Declare an input type with Java class with validation enabled. + * Declare the expected data type of the input message by Java class with content validation enabled. + * If the actual message type is different at runtime, camel look for a required + * {@link Transformer} and apply if exists, and then applies {@link Validator} as well. * * @see {@link org.apache.camel.spi.Transformer}, {@link org.apache.camel.spi.Validator} * @param clazz Class object of the input type @@ -691,7 +702,10 @@ public class RouteDefinition extends ProcessorDefinition<RouteDefinition> { } /** - * Declare an output type. + * Declare the expected data type of the output message. If the actual message type is different + * at runtime, camel look for a required {@link Transformer} and apply if exists. + * The type name consists of two parts, 'scheme' and 'name' connected with ':'. For Java type 'name' + * is a fully qualified class name. For example {@code java:java.lang.String}, {@code json:ABCOrder}. * * @see {@link org.apache.camel.spi.Transformer} * @param urn output type URN @@ -705,7 +719,11 @@ public class RouteDefinition extends ProcessorDefinition<RouteDefinition> { } /** - * Declare an output type with validation enabled. + * Declare the expected data type of the output message with content validation enabled. + * If the actual message type is different at runtime, camel look for a required + * {@link Transformer} and apply if exists, and then applies {@link Validator} as well. + * The type name consists of two parts, 'scheme' and 'name' connected with ':'. For Java type 'name' + * is a fully qualified class name. For example {@code java:java.lang.String}, {@code json:ABCOrder}. * * @see {@link org.apache.camel.spi.Transformer}, {@link org.apache.camel.spi.Validator} * @param urn output type URN @@ -719,7 +737,9 @@ public class RouteDefinition extends ProcessorDefinition<RouteDefinition> { } /** - * Declare an output type with Java class. + * Declare the expected data type of the output message by Java class. + * If the actual message type is different at runtime, camel look for a required + * {@link Transformer} and apply if exists. * * @see {@link org.apache.camel.spi.Transformer} * @param clazz Class object of the output type @@ -733,7 +753,9 @@ public class RouteDefinition extends ProcessorDefinition<RouteDefinition> { } /** - * Declare an output type with Java class with validation enabled. + * Declare the expected data type of the ouput message by Java class with content validation enabled. + * If the actual message type is different at runtime, camel look for a required + * {@link Transformer} and apply if exists, and then applies {@link Validator} as well. * * @see {@link org.apache.camel.spi.Transformer}, {@link org.apache.camel.spi.Validator} * @param clazz Class object of the output type http://git-wip-us.apache.org/repos/asf/camel/blob/0366bc15/camel-core/src/main/java/org/apache/camel/processor/ContractAdvice.java ---------------------------------------------------------------------- diff --git a/camel-core/src/main/java/org/apache/camel/processor/ContractAdvice.java b/camel-core/src/main/java/org/apache/camel/processor/ContractAdvice.java index 6a53e7b..361b47a 100644 --- a/camel-core/src/main/java/org/apache/camel/processor/ContractAdvice.java +++ b/camel-core/src/main/java/org/apache/camel/processor/ContractAdvice.java @@ -22,16 +22,25 @@ import org.apache.camel.Message; import org.apache.camel.ValidationException; import org.apache.camel.spi.Contract; import org.apache.camel.spi.DataType; +import org.apache.camel.spi.DataTypeAware; import org.apache.camel.spi.Transformer; import org.apache.camel.spi.Validator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * A {@code CamelInternalProcessorAdvice} which performs Transformation and Validation + * A {@link CamelInternalProcessorAdvice} which applies {@link Transformer} and {@link Validator} * according to the data type Contract. + * The default camel {@link Message} implements {@link DataTypeAware} which + * holds a {@link DataType} to indicate current message type. If the input type + * declared by {@link InputTypeDefinition} is diffrent from current IN message type, + * camel internal processor look for a Transformer which transforms from the current + * message type to the expected message type before routing. + * After routing, if the output type declared by {@link OutputTypeDefinition} is different + * from current OUT message (or IN message if no OUT), camel look for a Transformer and apply. * - * @see CamelInternalProcessor, CamelInternalProcessorAdvice + * @see {@link Transformer} {@link Validator} + * {@link InputTypeDefinition} {@link OutputTypeDefinition} */ public class ContractAdvice implements CamelInternalProcessorAdvice { private static final Logger LOG = LoggerFactory.getLogger(CamelInternalProcessor.class); @@ -44,13 +53,17 @@ public class ContractAdvice implements CamelInternalProcessorAdvice { @Override public Object before(Exchange exchange) throws Exception { - DataType from = getCurrentType(exchange, Exchange.INPUT_TYPE); + if (!(exchange.getIn() instanceof DataTypeAware)) { + return null; + } + DataTypeAware target = (DataTypeAware)exchange.getIn(); + DataType from = target.getDataType(); DataType to = contract.getInputType(); if (to != null) { if (!to.equals(from)) { LOG.debug("Looking for transformer for INPUT: from='{}', to='{}'", from, to); doTransform(exchange.getIn(), from, to); - exchange.setProperty(Exchange.INPUT_TYPE, to); + target.setDataType(to); } if (contract.isValidateInput()) { doValidate(exchange.getIn(), to); @@ -65,18 +78,19 @@ public class ContractAdvice implements CamelInternalProcessorAdvice { // TODO can we add FAULT_TYPE processing? return; } - + Message target = exchange.hasOut() ? exchange.getOut() : exchange.getIn(); - if (!exchange.hasOut() && exchange.getProperty(Exchange.OUTPUT_TYPE) == null) { - exchange.setProperty(Exchange.OUTPUT_TYPE, exchange.getProperty(Exchange.INPUT_TYPE)); + if (!(target instanceof DataTypeAware)) { + return; } - DataType from = getCurrentType(exchange, Exchange.OUTPUT_TYPE); + DataTypeAware typeAwareTarget = (DataTypeAware)target; + DataType from = typeAwareTarget.getDataType(); DataType to = contract.getOutputType(); if (to != null) { if (!to.equals(from)) { LOG.debug("Looking for transformer for OUTPUT: from='{}', to='{}'", from, to); doTransform(target, from, to); - exchange.setProperty(Exchange.OUTPUT_TYPE, to); + typeAwareTarget.setDataType(to); } if (contract.isValidateOutput()) { doValidate(target, to); @@ -87,7 +101,7 @@ public class ContractAdvice implements CamelInternalProcessorAdvice { private void doTransform(Message message, DataType from, DataType to) throws Exception { if (from == null) { // If 'from' is null, only Java-Java convertion is performed. - // It means if 'to' is other than Java, it's assumed to be already in expected shape. + // It means if 'to' is other than Java, it's assumed to be already in expected type. convertIfRequired(message, to); return; } @@ -99,10 +113,11 @@ public class ContractAdvice implements CamelInternalProcessorAdvice { // Found matched transformer. Java-Java transformer is also allowed. return; } else if (from.isJavaType()) { - if (convertIfRequired(message, to)) { - // Java->Java transformation just relies on TypeConverter if no explicit transformer - return; - } + // Try TypeConverter as a fallback for Java->Java transformation + convertIfRequired(message, to); + // If Java->Other transformation required but no transformer matched, + // then assume it's already in expected type, i.e. do nothing. + return; } else if (applyTransformerChain(message, from, to)) { // Other->Other transformation - found a transformer chain return; @@ -152,22 +167,10 @@ public class ContractAdvice implements CamelInternalProcessorAdvice { } return false; } - + private Class<?> getClazz(String type, CamelContext context) throws Exception { return context.getClassResolver().resolveMandatoryClass(type); } - - private DataType getCurrentType(Exchange exchange, String name) { - Object prop = exchange.getProperty(name); - if (prop instanceof DataType) { - return (DataType)prop; - } else if (prop instanceof String) { - DataType answer = new DataType((String)prop); - exchange.setProperty(name, answer); - return answer; - } - return null; - } private void doValidate(Message message, DataType type) throws ValidationException { Validator validator = message.getExchange().getContext().resolveValidator(type); http://git-wip-us.apache.org/repos/asf/camel/blob/0366bc15/camel-core/src/main/java/org/apache/camel/processor/RestBindingAdvice.java ---------------------------------------------------------------------- diff --git a/camel-core/src/main/java/org/apache/camel/processor/RestBindingAdvice.java b/camel-core/src/main/java/org/apache/camel/processor/RestBindingAdvice.java index 7b81a19..d14c0f4 100644 --- a/camel-core/src/main/java/org/apache/camel/processor/RestBindingAdvice.java +++ b/camel-core/src/main/java/org/apache/camel/processor/RestBindingAdvice.java @@ -29,6 +29,7 @@ import org.apache.camel.processor.binding.BindingException; import org.apache.camel.spi.Contract; import org.apache.camel.spi.DataFormat; import org.apache.camel.spi.DataType; +import org.apache.camel.spi.DataTypeAware; import org.apache.camel.spi.RestConfiguration; import org.apache.camel.spi.Transformer; import org.apache.camel.util.ExchangeHelper; @@ -167,11 +168,8 @@ public class RestBindingAdvice implements CamelInternalProcessorAdvice<Map<Strin isJson = consumes != null && consumes.toLowerCase(Locale.ENGLISH).contains("json"); } - // set the INPUT_TYPE to indicate body type - if (isJson) { - exchange.setProperty(Exchange.INPUT_TYPE, new DataType("json")); - } else if (isXml) { - exchange.setProperty(Exchange.INPUT_TYPE, new DataType("xml")); + if (exchange.getIn() instanceof DataTypeAware && (isJson || isXml)) { + ((DataTypeAware)exchange.getIn()).setDataType(new DataType(isJson ? "json" : "xml")); } // only allow xml/json if the binding mode allows that @@ -196,7 +194,11 @@ public class RestBindingAdvice implements CamelInternalProcessorAdvice<Map<Strin // 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 (exchange.getIn() instanceof DataTypeAware) { + ((DataTypeAware)exchange.getIn()).setBody(body, new DataType(isJson ? "json" : "xml")); + } else { + 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 @@ -223,7 +225,6 @@ public class RestBindingAdvice implements CamelInternalProcessorAdvice<Map<Strin if (ObjectHelper.isNotEmpty(body)) { jsonUnmarshal.process(exchange); ExchangeHelper.prepareOutToIn(exchange); - exchange.setProperty(Exchange.INPUT_TYPE, new DataType(exchange.getIn().getBody().getClass())); } return; } else if (isXml && xmlUnmarshal != null) { @@ -232,7 +233,6 @@ public class RestBindingAdvice implements CamelInternalProcessorAdvice<Map<Strin if (ObjectHelper.isNotEmpty(body)) { xmlUnmarshal.process(exchange); ExchangeHelper.prepareOutToIn(exchange); - exchange.setProperty(Exchange.INPUT_TYPE, new DataType(exchange.getIn().getBody().getClass())); } return; } @@ -337,13 +337,13 @@ public class RestBindingAdvice implements CamelInternalProcessorAdvice<Map<Strin // only marshal if its json content type if (contentType.contains("json")) { jsonMarshal.process(exchange); - exchange.setProperty(Exchange.OUTPUT_TYPE, new DataType("json")); + setOutputDataType(exchange, new DataType("json")); } } else if (isXml && xmlMarshal != null) { // only marshal if its xml content type if (contentType.contains("xml")) { xmlMarshal.process(exchange); - exchange.setProperty(Exchange.OUTPUT_TYPE, new DataType("xml")); + setOutputDataType(exchange, new DataType("xml")); } } else { // we could not bind @@ -362,6 +362,13 @@ public class RestBindingAdvice implements CamelInternalProcessorAdvice<Map<Strin } } + private void setOutputDataType(Exchange exchange, DataType type) { + Message target = exchange.hasOut() ? exchange.getOut() : exchange.getIn(); + if (target instanceof DataTypeAware) { + ((DataTypeAware)target).setDataType(type); + } + } + private void ensureHeaderContentType(String contentType, boolean isXml, boolean isJson, Exchange exchange) { // favor given content type if (contentType != null) { http://git-wip-us.apache.org/repos/asf/camel/blob/0366bc15/camel-core/src/main/java/org/apache/camel/spi/DataType.java ---------------------------------------------------------------------- diff --git a/camel-core/src/main/java/org/apache/camel/spi/DataType.java b/camel-core/src/main/java/org/apache/camel/spi/DataType.java index 973e5c4..7c07188 100644 --- a/camel-core/src/main/java/org/apache/camel/spi/DataType.java +++ b/camel-core/src/main/java/org/apache/camel/spi/DataType.java @@ -20,6 +20,17 @@ import org.apache.camel.util.StringHelper; /** * Represents the data type URN which is used for message data type contract. + * Java class doesn't always explain the data type completely, for example XML and JSON + * data format is sometimes serialized as a {@code String}, {@code InputStream} or etc. + * The {@link DataTypeAware} message stores the DataType as a part of the message to carry + * those data type information even if it's marshaled, so that it could be + * leveraged to detect required {@link Transformer} and {@link Validator}. + * DataType consists of two parts, 'model' and 'name'. Its string representation is + * 'model:name' connected with colon. For example 'java:com.example.Order', 'xml:ABCOrder' + * or 'json:XYZOrder'. These type name other than java class name allows the message to + * carry the name of the message data structure even if it's marshaled. + * + * @see {@link DataTypeAware} {@link Transformer} {@link Validator} */ public class DataType { http://git-wip-us.apache.org/repos/asf/camel/blob/0366bc15/camel-core/src/main/java/org/apache/camel/spi/DataTypeAware.java ---------------------------------------------------------------------- diff --git a/camel-core/src/main/java/org/apache/camel/spi/DataTypeAware.java b/camel-core/src/main/java/org/apache/camel/spi/DataTypeAware.java new file mode 100644 index 0000000..f979f74 --- /dev/null +++ b/camel-core/src/main/java/org/apache/camel/spi/DataTypeAware.java @@ -0,0 +1,51 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.spi; + +/** + * Allows {@link org.apache.camel.Message} to store a {@link DataType} which + * represents the data type of the Message. Sometimes message content is marshaled + * into {@code String}, {@code InputStream} or etc, and the data type structure is + * not available until it's unmarshaled into Java object. The {@link DataType} stored + * in a DataTypeAware message carries that missing data type information even if it's + * marshaled, and whatever the Java class of the body is. This type information is used + * to detect required {@link Transformer} and {@link Validator}. + * + * @see {@link DataType} {@link Transformer} {@link Validator} + */ +public interface DataTypeAware { + + /** + * Set the data type of the message. + * @param type data type + */ + void setDataType(DataType type); + + /** + * Get the data type of the message. + * @return data type + */ + DataType getDataType(); + + /** + * Set the message body with data type. + * @param body message body + * @param type data type + */ + void setBody(Object body, DataType type); + +} http://git-wip-us.apache.org/repos/asf/camel/blob/0366bc15/camel-core/src/main/java/org/apache/camel/spi/Transformer.java ---------------------------------------------------------------------- diff --git a/camel-core/src/main/java/org/apache/camel/spi/Transformer.java b/camel-core/src/main/java/org/apache/camel/spi/Transformer.java index 23439f5..32fd376 100644 --- a/camel-core/src/main/java/org/apache/camel/spi/Transformer.java +++ b/camel-core/src/main/java/org/apache/camel/spi/Transformer.java @@ -27,15 +27,11 @@ import org.apache.camel.support.ServiceSupport; /** * <a href="http://camel.apache.org/transformer.html">Transformer</a> * performs message transformation according to the declared data type. - * There are two Exchange property indicates current message type, {@link Exchange#INPUT_TYPE} - * holds input message type and {@link Exchange#OUTPUT_TYPE} holds output message type. If the - * input type and/or output type declared by {@link InputTypeDefinition} - * and/or {@link OutputTypeDefinition} in the route definition is different from those property - * at runtime, camel internal processor look for a Transformer which transforms from - * the current message type to the expected message type. + * {@link ContractAdvice} looks for a required Transformer and apply if + * input/output type declared on a route is different from current message type. * - * @see InputTypeDefinition - * @see OutputTypeDefinition + * @see {@link ContractAdvice} + * {@link DataType} {@link InputTypeDefinition} {@link OutputTypeDefinition} */ public abstract class Transformer extends ServiceSupport implements CamelContextAware { http://git-wip-us.apache.org/repos/asf/camel/blob/0366bc15/camel-core/src/main/java/org/apache/camel/spi/Validator.java ---------------------------------------------------------------------- diff --git a/camel-core/src/main/java/org/apache/camel/spi/Validator.java b/camel-core/src/main/java/org/apache/camel/spi/Validator.java index 1ba2998..ebd5776 100644 --- a/camel-core/src/main/java/org/apache/camel/spi/Validator.java +++ b/camel-core/src/main/java/org/apache/camel/spi/Validator.java @@ -26,17 +26,13 @@ import org.apache.camel.model.OutputTypeDefinition; import org.apache.camel.support.ServiceSupport; /** - * <a href="http://camel.apache.org/transformer.html">Transformer</a> - * performs message transformation according to the declared data type. - * There are two Exchange property indicates current message type, {@link Exchange#INPUT_TYPE} - * holds input message type and {@link Exchange#OUTPUT_TYPE} holds output message type. If the - * input type and/or output type declared by {@link InputTypeDefinition} - * and/or {@link OutputTypeDefinition} in the route definition is different from those property - * at runtime, camel internal processor look for a Transformer which transforms from - * the current message type to the expected message type. + * <a href="http://camel.apache.org/validator.html">Validator</a> + * performs message content validation according to the declared data type. + * {@link ContractAdvice} applies Validator if input/output type is declared on + * a route with validation enabled. * - * @see InputTypeDefinition - * @see OutputTypeDefinition + * @see {@link ContractAdvice} + * {@link InputTypeDefinition} {@link OutputTypeDefinition} */ public abstract class Validator extends ServiceSupport implements CamelContextAware { http://git-wip-us.apache.org/repos/asf/camel/blob/0366bc15/camel-core/src/test/java/org/apache/camel/impl/transformer/TransformerContractTest.java ---------------------------------------------------------------------- diff --git a/camel-core/src/test/java/org/apache/camel/impl/transformer/TransformerContractTest.java b/camel-core/src/test/java/org/apache/camel/impl/transformer/TransformerContractTest.java index 56cf91d..7458e5c 100644 --- a/camel-core/src/test/java/org/apache/camel/impl/transformer/TransformerContractTest.java +++ b/camel-core/src/test/java/org/apache/camel/impl/transformer/TransformerContractTest.java @@ -30,6 +30,8 @@ import org.apache.camel.builder.RouteBuilder; import org.apache.camel.component.mock.MockEndpoint; import org.apache.camel.model.DataFormatDefinition; import org.apache.camel.spi.DataFormat; +import org.apache.camel.spi.DataType; +import org.apache.camel.spi.DataTypeAware; import org.apache.camel.spi.RouteContext; import org.junit.Test; @@ -115,7 +117,10 @@ public class TransformerContractTest extends ContextTestSupport { mocka.setExpectedCount(1); mocka2.setExpectedCount(1); mockb.setExpectedCount(1); - Object answer = template.requestBody("direct:a", "<foo/>"); + Exchange answer = template.send("direct:a", ex -> { + DataTypeAware message = (DataTypeAware)ex.getIn(); + message.setBody("<foo/>", new DataType("xml")); + }); mocka.assertIsSatisfied(); mocka2.assertIsSatisfied(); mockb.assertIsSatisfied(); @@ -125,7 +130,7 @@ public class TransformerContractTest extends ContextTestSupport { assertEquals("<foo/>", exa.getIn().getBody()); assertEquals(A.class, exb.getIn().getBody().getClass()); assertEquals(B.class, exa2.getIn().getBody().getClass()); - assertEquals("<fooResponse/>", new String((byte[])answer)); + assertEquals("<fooResponse/>", new String((byte[])answer.getIn().getBody())); } public static class MyTypeConverters implements TypeConverters { http://git-wip-us.apache.org/repos/asf/camel/blob/0366bc15/camel-core/src/test/java/org/apache/camel/impl/transformer/TransformerRouteTest.java ---------------------------------------------------------------------- diff --git a/camel-core/src/test/java/org/apache/camel/impl/transformer/TransformerRouteTest.java b/camel-core/src/test/java/org/apache/camel/impl/transformer/TransformerRouteTest.java index 4d5a0d1..8a07c4a 100644 --- a/camel-core/src/test/java/org/apache/camel/impl/transformer/TransformerRouteTest.java +++ b/camel-core/src/test/java/org/apache/camel/impl/transformer/TransformerRouteTest.java @@ -42,6 +42,7 @@ import org.apache.camel.impl.DefaultExchange; import org.apache.camel.model.DataFormatDefinition; import org.apache.camel.spi.DataFormat; import org.apache.camel.spi.DataType; +import org.apache.camel.spi.DataTypeAware; import org.apache.camel.spi.RouteContext; import org.apache.camel.spi.Transformer; import org.slf4j.Logger; @@ -98,7 +99,7 @@ public class TransformerRouteTest extends ContextTestSupport { }); Exchange exchange = new DefaultExchange(context, ExchangePattern.InOut); - exchange.getIn().setBody("{name:XOrder}"); + ((DataTypeAware)exchange.getIn()).setBody("{name:XOrder}", new DataType("json:JsonXOrder")); Exchange answerEx = template.send("direct:dataFormat", exchange); if (answerEx.getException() != null) { throw answerEx.getException(); http://git-wip-us.apache.org/repos/asf/camel/blob/0366bc15/camel-core/src/test/java/org/apache/camel/impl/validator/ValidatorRouteTest.java ---------------------------------------------------------------------- diff --git a/camel-core/src/test/java/org/apache/camel/impl/validator/ValidatorRouteTest.java b/camel-core/src/test/java/org/apache/camel/impl/validator/ValidatorRouteTest.java index 3d92741..696881d 100644 --- a/camel-core/src/test/java/org/apache/camel/impl/validator/ValidatorRouteTest.java +++ b/camel-core/src/test/java/org/apache/camel/impl/validator/ValidatorRouteTest.java @@ -64,7 +64,7 @@ public class ValidatorRouteTest extends ContextTestSupport { if (answerEx.getException() != null) { throw answerEx.getException(); } - assertEquals("{name:XOrderResponse}", answerEx.getOut().getBody(String.class)); + assertEquals("{name:XOrderResponse}", answerEx.getIn().getBody(String.class)); } public void testEndpointValidator() throws Exception { @@ -101,8 +101,7 @@ public class ValidatorRouteTest extends ContextTestSupport { from("direct:predicate") .inputTypeWithValidate("json:JsonXOrder") .outputType("json:JsonXOrderResponse") - .setBody(simple("{name:XOrderResponse}")) - .setProperty(Exchange.OUTPUT_TYPE, constant("json:JsonXOrderResponse")); + .setBody(simple("{name:XOrderResponse}")); context.addComponent("myxml", new MyXmlComponent()); validator() @@ -112,8 +111,7 @@ public class ValidatorRouteTest extends ContextTestSupport { .inputType("xml:XmlXOrder") .outputTypeWithValidate("xml:XmlXOrderResponse") .validate(exchangeProperty(VALIDATOR_INVOKED).isNull()) - .setBody(simple("<XOrderResponse/>")) - .setProperty(Exchange.OUTPUT_TYPE, constant("xml:XmlXOrderResponse")); + .setBody(simple("<XOrderResponse/>")); validator() .type("other:OtherXOrder") @@ -125,8 +123,7 @@ public class ValidatorRouteTest extends ContextTestSupport { .inputTypeWithValidate("other:OtherXOrder") .outputTypeWithValidate("other:OtherXOrderResponse") .validate(exchangeProperty(VALIDATOR_INVOKED).isEqualTo(OtherXOrderValidator.class)) - .setBody(simple("name=XOrderResponse")) - .setProperty(Exchange.OUTPUT_TYPE, constant("other:OtherXOrderResponse")); + .setBody(simple("name=XOrderResponse")); } }; } http://git-wip-us.apache.org/repos/asf/camel/blob/0366bc15/components/camel-spring/src/test/resources/org/apache/camel/spring/impl/validator/SpringValidatorRouteTest.xml ---------------------------------------------------------------------- diff --git a/components/camel-spring/src/test/resources/org/apache/camel/spring/impl/validator/SpringValidatorRouteTest.xml b/components/camel-spring/src/test/resources/org/apache/camel/spring/impl/validator/SpringValidatorRouteTest.xml index 4e6c090..fce29a6 100644 --- a/components/camel-spring/src/test/resources/org/apache/camel/spring/impl/validator/SpringValidatorRouteTest.xml +++ b/components/camel-spring/src/test/resources/org/apache/camel/spring/impl/validator/SpringValidatorRouteTest.xml @@ -38,7 +38,6 @@ <inputType urn="json:JsonXOrder" validate="true"/> <outputType urn="json:JsonXOrderResponse"/> <setBody><constant>{name:XOrderResponse}</constant></setBody> - <setProperty propertyName="CamelOutputType"><constant>json:JsonXOrderResponse</constant></setProperty> </route> <route> @@ -49,7 +48,6 @@ <simple>${exchangeProperty.validator-invoked} == null</simple> </validate> <setBody><constant><XOrderResponse/></constant></setBody> - <setProperty propertyName="CamelOutputType"><constant>xml:XmlXOrderResponse</constant></setProperty> </route> <route> @@ -60,7 +58,6 @@ <simple>${exchangeProperty.validator-invoked} == 'org.apache.camel.impl.validator.ValidatorRouteTest$OtherXOrderValidator'</simple> </validate> <setBody><constant>name=XOrderResponse</constant></setBody> - <setProperty propertyName="CamelOutputType"><constant>other:OtherXOrderResponse</constant></setProperty> </route> </camelContext> http://git-wip-us.apache.org/repos/asf/camel/blob/0366bc15/examples/camel-example-transformer-demo/src/main/java/org/apache/camel/example/transformer/demo/client/CamelClient.java ---------------------------------------------------------------------- diff --git a/examples/camel-example-transformer-demo/src/main/java/org/apache/camel/example/transformer/demo/client/CamelClient.java b/examples/camel-example-transformer-demo/src/main/java/org/apache/camel/example/transformer/demo/client/CamelClient.java index 2f42ef8..346579d 100644 --- a/examples/camel-example-transformer-demo/src/main/java/org/apache/camel/example/transformer/demo/client/CamelClient.java +++ b/examples/camel-example-transformer-demo/src/main/java/org/apache/camel/example/transformer/demo/client/CamelClient.java @@ -21,9 +21,12 @@ import java.io.File; import java.io.FileReader; import org.apache.camel.CamelContext; +import org.apache.camel.Exchange; import org.apache.camel.ProducerTemplate; import org.apache.camel.example.transformer.demo.Order; import org.apache.camel.example.transformer.demo.OrderResponse; +import org.apache.camel.spi.DataType; +import org.apache.camel.spi.DataTypeAware; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.support.ClassPathXmlApplicationContext; @@ -68,17 +71,21 @@ public final class CamelClient { String orderXml = "<order orderId=\"Order-XML-0001\" itemId=\"MIKAN\" quantity=\"365\"/>"; LOG.info("---> Sending '{}' to 'direct:xml'", orderXml); - String responseXml = producer.requestBody("direct:xml", orderXml, String.class); + Exchange answerXml = producer.send("direct:xml", ex -> { + ((DataTypeAware)ex.getIn()).setBody(orderXml, new DataType("xml:XMLOrder")); + }); Thread.sleep(1000); - LOG.info("---> Received '{}'", responseXml); + LOG.info("---> Received '{}'", answerXml.getOut().getBody(String.class)); LOG.info("---> CSV log now contains:{}", getCsvLog()); Thread.sleep(1000); String orderJson = "{\"orderId\":\"Order-JSON-0001\", \"itemId\":\"MIZUYO-KAN\", \"quantity\":\"16350\"}"; LOG.info("---> Sending '{}' to 'direct:json'", orderJson); - String responseJson = producer.requestBody("direct:json", orderJson, String.class); + Exchange answerJson = producer.send("direct:json", ex -> { + ((DataTypeAware)ex.getIn()).setBody(orderJson, new DataType("json")); + }); Thread.sleep(1000); - LOG.info("---> Received '{}'", responseJson); + LOG.info("---> Received '{}'", answerJson.getOut().getBody(String.class)); LOG.info("---> CSV log now contains:{}", getCsvLog()); Thread.sleep(1000); http://git-wip-us.apache.org/repos/asf/camel/blob/0366bc15/examples/camel-example-transformer-demo/src/test/java/org/apache/camel/example/transformer/OrderRouteSpringTest.java ---------------------------------------------------------------------- diff --git a/examples/camel-example-transformer-demo/src/test/java/org/apache/camel/example/transformer/OrderRouteSpringTest.java b/examples/camel-example-transformer-demo/src/test/java/org/apache/camel/example/transformer/OrderRouteSpringTest.java index 44d287c..8d036aa 100644 --- a/examples/camel-example-transformer-demo/src/test/java/org/apache/camel/example/transformer/OrderRouteSpringTest.java +++ b/examples/camel-example-transformer-demo/src/test/java/org/apache/camel/example/transformer/OrderRouteSpringTest.java @@ -25,6 +25,8 @@ import org.apache.camel.ProducerTemplate; import org.apache.camel.component.mock.MockEndpoint; import org.apache.camel.example.transformer.demo.Order; import org.apache.camel.example.transformer.demo.OrderResponse; +import org.apache.camel.spi.DataType; +import org.apache.camel.spi.DataTypeAware; import org.apache.camel.test.spring.CamelSpringDelegatingTestContextLoader; import org.apache.camel.test.spring.CamelSpringRunner; import org.apache.camel.test.spring.MockEndpointsAndSkip; @@ -100,8 +102,10 @@ public class OrderRouteSpringTest { String order = "<order orderId=\"Order-XML-0001\" itemId=\"MIKAN\" quantity=\"365\"/>"; String expectedAnswer = "<orderResponse orderId=\"Order-XML-0001\" accepted=\"true\" description=\"Order accepted:[item='MIKAN' quantity='365']\"/>"; - String answer = xmlProducer.requestBody("direct:xml", order, String.class); - XMLUnit.compareXML(expectedAnswer, answer); + Exchange answer = xmlProducer.send("direct:xml", ex -> { + ((DataTypeAware)ex.getIn()).setBody(order, new DataType("xml:XMLOrder")); + }); + XMLUnit.compareXML(expectedAnswer, answer.getOut().getBody(String.class)); mockCsv.assertIsSatisfied(); } @@ -126,8 +130,10 @@ public class OrderRouteSpringTest { .setDescription("Order accepted:[item='MIZUYO-KAN' quantity='16350']"); ObjectMapper jsonMapper = new ObjectMapper(); String expectedJson = jsonMapper.writeValueAsString(expected); - String answer = jsonProducer.requestBody("direct:json", order, String.class); - assertEquals(expectedJson, answer); + Exchange answer = jsonProducer.send("direct:json", ex -> { + ((DataTypeAware)ex.getIn()).setBody(order, new DataType("json")); + }); + assertEquals(expectedJson, answer.getOut().getBody(String.class)); mockCsv.assertIsSatisfied(); } }