CAMEL-7548: Property palceholders now supported in <dataFormats> in XML DSL.
Project: http://git-wip-us.apache.org/repos/asf/camel/repo Commit: http://git-wip-us.apache.org/repos/asf/camel/commit/5e151086 Tree: http://git-wip-us.apache.org/repos/asf/camel/tree/5e151086 Diff: http://git-wip-us.apache.org/repos/asf/camel/diff/5e151086 Branch: refs/heads/master Commit: 5e151086390ac1d36c6cb4f5398aabc8e60d0ca0 Parents: e86aa1e Author: Claus Ibsen <davscl...@apache.org> Authored: Mon Aug 25 14:02:56 2014 +0200 Committer: Claus Ibsen <davscl...@apache.org> Committed: Mon Aug 25 14:56:35 2014 +0200 ---------------------------------------------------------------------- .../camel/model/DataFormatDefinition.java | 8 + .../apache/camel/model/ProcessorDefinition.java | 147 ++----------------- .../camel/model/ProcessorDefinitionHelper.java | 133 +++++++++++++++++ ...StringDataFormatPropertyPlaceholderTest.java | 31 ++++ .../camel/spring/issues/myprop.properties | 4 +- .../stringDataFormatPropertyPlaceholderTest.xml | 50 +++++++ 6 files changed, 237 insertions(+), 136 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/camel/blob/5e151086/camel-core/src/main/java/org/apache/camel/model/DataFormatDefinition.java ---------------------------------------------------------------------- diff --git a/camel-core/src/main/java/org/apache/camel/model/DataFormatDefinition.java b/camel-core/src/main/java/org/apache/camel/model/DataFormatDefinition.java index 6125068..48e49fb 100644 --- a/camel-core/src/main/java/org/apache/camel/model/DataFormatDefinition.java +++ b/camel-core/src/main/java/org/apache/camel/model/DataFormatDefinition.java @@ -83,6 +83,14 @@ public class DataFormatDefinition extends IdentifiedType { public DataFormat getDataFormat(RouteContext routeContext) { if (dataFormat == null) { + + // resolve properties before we create the data format + try { + ProcessorDefinitionHelper.resolvePropertyPlaceholders(routeContext, this); + } catch (Exception e) { + throw new IllegalArgumentException("Error resolving property placeholders on data format: " + this, e); + } + dataFormat = createDataFormat(routeContext); if (dataFormat != null) { configureDataFormat(dataFormat, routeContext.getCamelContext()); http://git-wip-us.apache.org/repos/asf/camel/blob/5e151086/camel-core/src/main/java/org/apache/camel/model/ProcessorDefinition.java ---------------------------------------------------------------------- diff --git a/camel-core/src/main/java/org/apache/camel/model/ProcessorDefinition.java b/camel-core/src/main/java/org/apache/camel/model/ProcessorDefinition.java index 3d2714e..6af6ca6 100644 --- a/camel-core/src/main/java/org/apache/camel/model/ProcessorDefinition.java +++ b/camel-core/src/main/java/org/apache/camel/model/ProcessorDefinition.java @@ -407,10 +407,10 @@ public abstract class ProcessorDefinition<Type extends ProcessorDefinition<Type> output.preCreateProcessor(); // resolve properties before we create the processor - resolvePropertyPlaceholders(routeContext, output); + ProcessorDefinitionHelper.resolvePropertyPlaceholders(routeContext, output); // resolve constant fields (eg Exchange.FILE_NAME) - resolveKnownConstantFields(output); + ProcessorDefinitionHelper.resolveKnownConstantFields(output); // also resolve properties and constant fields on embedded expressions ProcessorDefinition<?> me = (ProcessorDefinition<?>) output; @@ -419,10 +419,10 @@ public abstract class ProcessorDefinition<Type extends ProcessorDefinition<Type> ExpressionDefinition expressionDefinition = exp.getExpression(); if (expressionDefinition != null) { // resolve properties before we create the processor - resolvePropertyPlaceholders(routeContext, expressionDefinition); + ProcessorDefinitionHelper.resolvePropertyPlaceholders(routeContext, expressionDefinition); // resolve constant fields (eg Exchange.FILE_NAME) - resolveKnownConstantFields(expressionDefinition); + ProcessorDefinitionHelper.resolveKnownConstantFields(expressionDefinition); } } @@ -472,10 +472,10 @@ public abstract class ProcessorDefinition<Type extends ProcessorDefinition<Type> preCreateProcessor(); // resolve properties before we create the processor - resolvePropertyPlaceholders(routeContext, this); + ProcessorDefinitionHelper.resolvePropertyPlaceholders(routeContext, this); // resolve constant fields (eg Exchange.FILE_NAME) - resolveKnownConstantFields(this); + ProcessorDefinitionHelper.resolveKnownConstantFields(this); // also resolve properties and constant fields on embedded expressions ProcessorDefinition<?> me = (ProcessorDefinition<?>) this; @@ -484,10 +484,10 @@ public abstract class ProcessorDefinition<Type extends ProcessorDefinition<Type> ExpressionDefinition expressionDefinition = exp.getExpression(); if (expressionDefinition != null) { // resolve properties before we create the processor - resolvePropertyPlaceholders(routeContext, expressionDefinition); + ProcessorDefinitionHelper.resolvePropertyPlaceholders(routeContext, expressionDefinition); // resolve constant fields (eg Exchange.FILE_NAME) - resolveKnownConstantFields(expressionDefinition); + ProcessorDefinitionHelper.resolveKnownConstantFields(expressionDefinition); } } @@ -508,129 +508,6 @@ public abstract class ProcessorDefinition<Type extends ProcessorDefinition<Type> } /** - * Inspects the given definition and resolves any property placeholders from its properties. - * <p/> - * This implementation will check all the getter/setter pairs on this instance and for all the values - * (which is a String type) will be property placeholder resolved. - * - * @param routeContext the route context - * @param definition the definition - * @throws Exception is thrown if property placeholders was used and there was an error resolving them - * @see org.apache.camel.CamelContext#resolvePropertyPlaceholders(String) - * @see org.apache.camel.component.properties.PropertiesComponent - */ - protected void resolvePropertyPlaceholders(RouteContext routeContext, Object definition) throws Exception { - log.trace("Resolving property placeholders for: {}", definition); - - // find all getter/setter which we can use for property placeholders - Map<String, Object> properties = new HashMap<String, Object>(); - IntrospectionSupport.getProperties(definition, properties, null); - - ProcessorDefinition<?> processorDefinition = null; - if (definition instanceof ProcessorDefinition) { - processorDefinition = (ProcessorDefinition<?>) definition; - } - // include additional properties which have the Camel placeholder QName - // and when the definition parameter is this (otherAttributes belong to this) - if (processorDefinition != null && processorDefinition.getOtherAttributes() != null) { - for (QName key : processorDefinition.getOtherAttributes().keySet()) { - if (Constants.PLACEHOLDER_QNAME.equals(key.getNamespaceURI())) { - String local = key.getLocalPart(); - Object value = processorDefinition.getOtherAttributes().get(key); - if (value != null && value instanceof String) { - // value must be enclosed with placeholder tokens - String s = (String) value; - String prefixToken = routeContext.getCamelContext().getPropertyPrefixToken(); - String suffixToken = routeContext.getCamelContext().getPropertySuffixToken(); - if (prefixToken == null) { - throw new IllegalArgumentException("Property with name [" + local + "] uses property placeholders; however, no properties component is configured."); - } - - if (!s.startsWith(prefixToken)) { - s = prefixToken + s; - } - if (!s.endsWith(suffixToken)) { - s = s + suffixToken; - } - value = s; - } - properties.put(local, value); - } - } - } - - if (!properties.isEmpty()) { - log.trace("There are {} properties on: {}", properties.size(), definition); - // lookup and resolve properties for String based properties - for (Map.Entry<String, Object> entry : properties.entrySet()) { - // the name is always a String - String name = entry.getKey(); - Object value = entry.getValue(); - if (value instanceof String) { - // value must be a String, as a String is the key for a property placeholder - String text = (String) value; - text = routeContext.getCamelContext().resolvePropertyPlaceholders(text); - if (text != value) { - // invoke setter as the text has changed - boolean changed = IntrospectionSupport.setProperty(routeContext.getCamelContext().getTypeConverter(), definition, name, text); - if (!changed) { - throw new IllegalArgumentException("No setter to set property: " + name + " to: " + text + " on: " + definition); - } - if (log.isDebugEnabled()) { - log.debug("Changed property [{}] from: {} to: {}", new Object[]{name, value, text}); - } - } - } - } - } - } - - /** - * Inspects the given definition and resolves known fields - * <p/> - * This implementation will check all the getter/setter pairs on this instance and for all the values - * (which is a String type) will check if it refers to a known field (such as on Exchange). - * - * @param definition the definition - */ - protected void resolveKnownConstantFields(Object definition) throws Exception { - log.trace("Resolving known fields for: {}", definition); - - // find all String getter/setter - Map<String, Object> properties = new HashMap<String, Object>(); - IntrospectionSupport.getProperties(definition, properties, null); - - if (!properties.isEmpty()) { - log.trace("There are {} properties on: {}", properties.size(), definition); - - // lookup and resolve known constant fields for String based properties - for (Map.Entry<String, Object> entry : properties.entrySet()) { - String name = entry.getKey(); - Object value = entry.getValue(); - if (value instanceof String) { - // we can only resolve String typed values - String text = (String) value; - - // is the value a known field (currently we only support constants from Exchange.class) - if (text.startsWith("Exchange.")) { - String field = ObjectHelper.after(text, "Exchange."); - String constant = ObjectHelper.lookupConstantFieldValue(Exchange.class, field); - if (constant != null) { - // invoke setter as the text has changed - IntrospectionSupport.setProperty(definition, name, constant); - if (log.isDebugEnabled()) { - log.debug("Changed property [{}] from: {} to: {}", new Object[]{name, value, constant}); - } - } else { - throw new IllegalArgumentException("Constant field with name: " + field + " not found on Exchange.class"); - } - } - } - } - } - } - - /** * Strategy to execute any custom logic before the {@link Processor} is created. */ protected void preCreateProcessor() { @@ -2587,7 +2464,7 @@ public abstract class ProcessorDefinition<Type extends ProcessorDefinition<Type> * * @param bean the bean to invoke * @param method the method name to invoke on the bean (can be used to avoid ambiguity) - * @param multiparameterArray if it is ture, camel will treat the message body as an object array which holds + * @param multiParameterArray if it is true, camel will treat the message body as an object array which holds * the multi parameter * @return the builder */ @@ -2637,9 +2514,9 @@ public abstract class ProcessorDefinition<Type extends ProcessorDefinition<Type> * <a href="http://camel.apache.org/message-translator.html">Message Translator EIP:</a> * Adds a bean which is invoked which could be a final destination, or could be a transformation in a pipeline * - * @param beanType the bean class, Camel will instantiate an object at runtime + * @param beanType the bean class, Camel will instantiate an object at runtime * @param method the method name to invoke on the bean (can be used to avoid ambiguity) - * @param multiparameterArray if it is ture, camel will treat the message body as an object array which holds + * @param multiParameterArray if it is true, camel will treat the message body as an object array which holds * the multi parameter * @return the builder */ @@ -2725,7 +2602,7 @@ public abstract class ProcessorDefinition<Type extends ProcessorDefinition<Type> * @param method the method name to invoke on the bean (can be used to avoid ambiguity) * @param cache if enabled, Camel will cache the result of the first Registry look-up. * Cache can be enabled if the bean in the Registry is defined as a singleton scope. - * @param multiparameterArray if it is ture, camel will treat the message body as an object array which holds + * @param multiParameterArray if it is true, camel will treat the message body as an object array which holds * the multi parameter * @return the builder */ http://git-wip-us.apache.org/repos/asf/camel/blob/5e151086/camel-core/src/main/java/org/apache/camel/model/ProcessorDefinitionHelper.java ---------------------------------------------------------------------- diff --git a/camel-core/src/main/java/org/apache/camel/model/ProcessorDefinitionHelper.java b/camel-core/src/main/java/org/apache/camel/model/ProcessorDefinitionHelper.java index 2ae283b..c94599c 100644 --- a/camel-core/src/main/java/org/apache/camel/model/ProcessorDefinitionHelper.java +++ b/camel-core/src/main/java/org/apache/camel/model/ProcessorDefinitionHelper.java @@ -17,22 +17,32 @@ package org.apache.camel.model; import java.util.ArrayList; +import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.ScheduledExecutorService; +import javax.xml.namespace.QName; + +import org.apache.camel.Exchange; import org.apache.camel.spi.ExecutorServiceManager; import org.apache.camel.spi.RouteContext; +import org.apache.camel.util.IntrospectionSupport; import org.apache.camel.util.ObjectHelper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Helper class for ProcessorDefinition and the other model classes. */ public final class ProcessorDefinitionHelper { + private static final Logger LOG = LoggerFactory.getLogger(ProcessorDefinitionHelper.class); + private ProcessorDefinitionHelper() { } @@ -480,4 +490,127 @@ public final class ProcessorDefinitionHelper { return null; } + /** + * Inspects the given definition and resolves any property placeholders from its properties. + * <p/> + * This implementation will check all the getter/setter pairs on this instance and for all the values + * (which is a String type) will be property placeholder resolved. + * + * @param routeContext the route context + * @param definition the definition + * @throws Exception is thrown if property placeholders was used and there was an error resolving them + * @see org.apache.camel.CamelContext#resolvePropertyPlaceholders(String) + * @see org.apache.camel.component.properties.PropertiesComponent + */ + public static void resolvePropertyPlaceholders(RouteContext routeContext, Object definition) throws Exception { + LOG.trace("Resolving property placeholders for: {}", definition); + + // find all getter/setter which we can use for property placeholders + Map<String, Object> properties = new HashMap<String, Object>(); + IntrospectionSupport.getProperties(definition, properties, null); + + ProcessorDefinition<?> processorDefinition = null; + if (definition instanceof ProcessorDefinition) { + processorDefinition = (ProcessorDefinition<?>) definition; + } + // include additional properties which have the Camel placeholder QName + // and when the definition parameter is this (otherAttributes belong to this) + if (processorDefinition != null && processorDefinition.getOtherAttributes() != null) { + for (QName key : processorDefinition.getOtherAttributes().keySet()) { + if (Constants.PLACEHOLDER_QNAME.equals(key.getNamespaceURI())) { + String local = key.getLocalPart(); + Object value = processorDefinition.getOtherAttributes().get(key); + if (value != null && value instanceof String) { + // value must be enclosed with placeholder tokens + String s = (String) value; + String prefixToken = routeContext.getCamelContext().getPropertyPrefixToken(); + String suffixToken = routeContext.getCamelContext().getPropertySuffixToken(); + if (prefixToken == null) { + throw new IllegalArgumentException("Property with name [" + local + "] uses property placeholders; however, no properties component is configured."); + } + + if (!s.startsWith(prefixToken)) { + s = prefixToken + s; + } + if (!s.endsWith(suffixToken)) { + s = s + suffixToken; + } + value = s; + } + properties.put(local, value); + } + } + } + + if (!properties.isEmpty()) { + LOG.trace("There are {} properties on: {}", properties.size(), definition); + // lookup and resolve properties for String based properties + for (Map.Entry<String, Object> entry : properties.entrySet()) { + // the name is always a String + String name = entry.getKey(); + Object value = entry.getValue(); + if (value instanceof String) { + // value must be a String, as a String is the key for a property placeholder + String text = (String) value; + text = routeContext.getCamelContext().resolvePropertyPlaceholders(text); + if (text != value) { + // invoke setter as the text has changed + boolean changed = IntrospectionSupport.setProperty(routeContext.getCamelContext().getTypeConverter(), definition, name, text); + if (!changed) { + throw new IllegalArgumentException("No setter to set property: " + name + " to: " + text + " on: " + definition); + } + if (LOG.isDebugEnabled()) { + LOG.debug("Changed property [{}] from: {} to: {}", new Object[]{name, value, text}); + } + } + } + } + } + } + + /** + * Inspects the given definition and resolves known fields + * <p/> + * This implementation will check all the getter/setter pairs on this instance and for all the values + * (which is a String type) will check if it refers to a known field (such as on Exchange). + * + * @param definition the definition + */ + public static void resolveKnownConstantFields(Object definition) throws Exception { + LOG.trace("Resolving known fields for: {}", definition); + + // find all String getter/setter + Map<String, Object> properties = new HashMap<String, Object>(); + IntrospectionSupport.getProperties(definition, properties, null); + + if (!properties.isEmpty()) { + LOG.trace("There are {} properties on: {}", properties.size(), definition); + + // lookup and resolve known constant fields for String based properties + for (Map.Entry<String, Object> entry : properties.entrySet()) { + String name = entry.getKey(); + Object value = entry.getValue(); + if (value instanceof String) { + // we can only resolve String typed values + String text = (String) value; + + // is the value a known field (currently we only support constants from Exchange.class) + if (text.startsWith("Exchange.")) { + String field = ObjectHelper.after(text, "Exchange."); + String constant = ObjectHelper.lookupConstantFieldValue(Exchange.class, field); + if (constant != null) { + // invoke setter as the text has changed + IntrospectionSupport.setProperty(definition, name, constant); + if (LOG.isDebugEnabled()) { + LOG.debug("Changed property [{}] from: {} to: {}", new Object[]{name, value, constant}); + } + } else { + throw new IllegalArgumentException("Constant field with name: " + field + " not found on Exchange.class"); + } + } + } + } + } + } + } http://git-wip-us.apache.org/repos/asf/camel/blob/5e151086/components/camel-spring/src/test/java/org/apache/camel/spring/issues/StringDataFormatPropertyPlaceholderTest.java ---------------------------------------------------------------------- diff --git a/components/camel-spring/src/test/java/org/apache/camel/spring/issues/StringDataFormatPropertyPlaceholderTest.java b/components/camel-spring/src/test/java/org/apache/camel/spring/issues/StringDataFormatPropertyPlaceholderTest.java new file mode 100644 index 0000000..ef34614 --- /dev/null +++ b/components/camel-spring/src/test/java/org/apache/camel/spring/issues/StringDataFormatPropertyPlaceholderTest.java @@ -0,0 +1,31 @@ +/** + * 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.spring.issues; + +import org.springframework.context.support.AbstractXmlApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; + +/** + * Spring bases data format unit test. + */ +public class StringDataFormatPropertyPlaceholderTest extends StringDataFormatTest { + + protected AbstractXmlApplicationContext createApplicationContext() { + return new ClassPathXmlApplicationContext("org/apache/camel/spring/issues/stringDataFormatPropertyPlaceholderTest.xml"); + } + +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/camel/blob/5e151086/components/camel-spring/src/test/resources/org/apache/camel/spring/issues/myprop.properties ---------------------------------------------------------------------- diff --git a/components/camel-spring/src/test/resources/org/apache/camel/spring/issues/myprop.properties b/components/camel-spring/src/test/resources/org/apache/camel/spring/issues/myprop.properties index 0eff885..95ab3f3 100644 --- a/components/camel-spring/src/test/resources/org/apache/camel/spring/issues/myprop.properties +++ b/components/camel-spring/src/test/resources/org/apache/camel/spring/issues/myprop.properties @@ -17,4 +17,6 @@ inputQueue=direct:start -outputFolder=target/issue \ No newline at end of file +outputFolder=target/issue + +myCharset=utf-8 \ No newline at end of file http://git-wip-us.apache.org/repos/asf/camel/blob/5e151086/components/camel-spring/src/test/resources/org/apache/camel/spring/issues/stringDataFormatPropertyPlaceholderTest.xml ---------------------------------------------------------------------- diff --git a/components/camel-spring/src/test/resources/org/apache/camel/spring/issues/stringDataFormatPropertyPlaceholderTest.xml b/components/camel-spring/src/test/resources/org/apache/camel/spring/issues/stringDataFormatPropertyPlaceholderTest.xml new file mode 100644 index 0000000..9d01b6b --- /dev/null +++ b/components/camel-spring/src/test/resources/org/apache/camel/spring/issues/stringDataFormatPropertyPlaceholderTest.xml @@ -0,0 +1,50 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + 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. +--> +<beans xmlns="http://www.springframework.org/schema/beans" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation=" + http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd + http://camel.apache.org/schema/spring http://camel.apache.org/schema/spring/camel-spring.xsd + "> + + <camelContext xmlns="http://camel.apache.org/schema/spring"> + + <propertyPlaceholder id="properties" location="classpath:org/apache/camel/spring/issues/myprop.properties"/> + + <dataFormats> + <string id="xs" charset="{{myCharset}}"/> + </dataFormats> + + <route> + <from uri="direct:marshal"/> + <!-- using a bean id --> + <marshal ref="xs"/> + <to uri="mock:marshal"/> + </route> + + <route> + <from uri="direct:unmarshal"/> + <!-- using a child node --> + <unmarshal> + <string charset="UTF-8"/> + </unmarshal> + <to uri="mock:unmarshal"/> + </route> + </camelContext> + +</beans>