Repository: camel Updated Branches: refs/heads/master a11cec91a -> e91148a05
CAMEL-11271 Support placeholders on attributes ... ...of camelContext element This adds logic to `afterPropertiesSet` to enumerate all fields of `CamelContextFactoryBean` and pass any `String` values to `CamelContext::resolvePropertyPlaceholders` for replacement. Project: http://git-wip-us.apache.org/repos/asf/camel/repo Commit: http://git-wip-us.apache.org/repos/asf/camel/commit/e91148a0 Tree: http://git-wip-us.apache.org/repos/asf/camel/tree/e91148a0 Diff: http://git-wip-us.apache.org/repos/asf/camel/diff/e91148a0 Branch: refs/heads/master Commit: e91148a05c4b0b04b374ea20bd55f5cf9d4f658e Parents: a11cec9 Author: Zoran Regvart <zregv...@apache.org> Authored: Tue May 16 08:55:57 2017 +0200 Committer: Zoran Regvart <zregv...@apache.org> Committed: Wed May 17 11:10:48 2017 +0200 ---------------------------------------------------------------------- components/camel-core-xml/pom.xml | 16 ++ .../xml/AbstractCamelContextFactoryBean.java | 30 ++-- .../AbstractCamelContextFactoryBeanTest.java | 178 +++++++++++++++++++ .../BaseSpringPropertiesComponentTest.java | 62 +++++++ .../SpringPropertiesComponent2Test.java | 5 +- .../SpringPropertiesComponent3Test.java | 5 +- ...ingPropertiesComponentCacheDisabledTest.java | 5 +- .../SpringPropertiesComponentTest.java | 47 +---- 8 files changed, 284 insertions(+), 64 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/camel/blob/e91148a0/components/camel-core-xml/pom.xml ---------------------------------------------------------------------- diff --git a/components/camel-core-xml/pom.xml b/components/camel-core-xml/pom.xml index 7c944fd..c844e558 100644 --- a/components/camel-core-xml/pom.xml +++ b/components/camel-core-xml/pom.xml @@ -41,5 +41,21 @@ <groupId>org.apache.camel</groupId> <artifactId>camel-core</artifactId> </dependency> + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.mockito</groupId> + <artifactId>mockito-core</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.assertj</groupId> + <artifactId>assertj-core</artifactId> + <version>${assertj-version}</version> + <scope>test</scope> + </dependency> </dependencies> </project> http://git-wip-us.apache.org/repos/asf/camel/blob/e91148a0/components/camel-core-xml/src/main/java/org/apache/camel/core/xml/AbstractCamelContextFactoryBean.java ---------------------------------------------------------------------- diff --git a/components/camel-core-xml/src/main/java/org/apache/camel/core/xml/AbstractCamelContextFactoryBean.java b/components/camel-core-xml/src/main/java/org/apache/camel/core/xml/AbstractCamelContextFactoryBean.java index 5ec7a1f..f7a8b74 100644 --- a/components/camel-core-xml/src/main/java/org/apache/camel/core/xml/AbstractCamelContextFactoryBean.java +++ b/components/camel-core-xml/src/main/java/org/apache/camel/core/xml/AbstractCamelContextFactoryBean.java @@ -815,50 +815,52 @@ public abstract class AbstractCamelContextFactoryBean<T extends ModelCamelContex * @throws Exception is thrown if error occurred */ protected void initCamelContext(T ctx) throws Exception { + final T context = getContext(); + if (getStreamCache() != null) { - ctx.setStreamCaching(CamelContextHelper.parseBoolean(getContext(), getStreamCache())); + ctx.setStreamCaching(CamelContextHelper.parseBoolean(context, getStreamCache())); } if (getTrace() != null) { - ctx.setTracing(CamelContextHelper.parseBoolean(getContext(), getTrace())); + ctx.setTracing(CamelContextHelper.parseBoolean(context, getTrace())); } if (getMessageHistory() != null) { - ctx.setMessageHistory(CamelContextHelper.parseBoolean(getContext(), getMessageHistory())); + ctx.setMessageHistory(CamelContextHelper.parseBoolean(context, getMessageHistory())); } if (getLogMask() != null) { - ctx.setLogMask(CamelContextHelper.parseBoolean(getContext(), getLogMask())); + ctx.setLogMask(CamelContextHelper.parseBoolean(context, getLogMask())); } if (getLogExhaustedMessageBody() != null) { - ctx.setLogExhaustedMessageBody(CamelContextHelper.parseBoolean(getContext(), getLogExhaustedMessageBody())); + ctx.setLogExhaustedMessageBody(CamelContextHelper.parseBoolean(context, getLogExhaustedMessageBody())); } if (getDelayer() != null) { - ctx.setDelayer(CamelContextHelper.parseLong(getContext(), getDelayer())); + ctx.setDelayer(CamelContextHelper.parseLong(context, getDelayer())); } if (getHandleFault() != null) { - ctx.setHandleFault(CamelContextHelper.parseBoolean(getContext(), getHandleFault())); + ctx.setHandleFault(CamelContextHelper.parseBoolean(context, getHandleFault())); } if (getErrorHandlerRef() != null) { ctx.setErrorHandlerBuilder(new ErrorHandlerBuilderRef(getErrorHandlerRef())); } if (getAutoStartup() != null) { - ctx.setAutoStartup(CamelContextHelper.parseBoolean(getContext(), getAutoStartup())); + ctx.setAutoStartup(CamelContextHelper.parseBoolean(context, getAutoStartup())); } if (getUseMDCLogging() != null) { - ctx.setUseMDCLogging(CamelContextHelper.parseBoolean(getContext(), getUseMDCLogging())); + ctx.setUseMDCLogging(CamelContextHelper.parseBoolean(context, getUseMDCLogging())); } if (getUseBreadcrumb() != null) { - ctx.setUseBreadcrumb(CamelContextHelper.parseBoolean(getContext(), getUseBreadcrumb())); + ctx.setUseBreadcrumb(CamelContextHelper.parseBoolean(context, getUseBreadcrumb())); } if (getAllowUseOriginalMessage() != null) { - ctx.setAllowUseOriginalMessage(CamelContextHelper.parseBoolean(getContext(), getAllowUseOriginalMessage())); + ctx.setAllowUseOriginalMessage(CamelContextHelper.parseBoolean(context, getAllowUseOriginalMessage())); } if (getRuntimeEndpointRegistryEnabled() != null) { - ctx.getRuntimeEndpointRegistry().setEnabled(CamelContextHelper.parseBoolean(getContext(), getRuntimeEndpointRegistryEnabled())); + ctx.getRuntimeEndpointRegistry().setEnabled(CamelContextHelper.parseBoolean(context, getRuntimeEndpointRegistryEnabled())); } if (getManagementNamePattern() != null) { - ctx.getManagementNameStrategy().setNamePattern(getManagementNamePattern()); + ctx.getManagementNameStrategy().setNamePattern(CamelContextHelper.parseText(context, getManagementNamePattern())); } if (getThreadNamePattern() != null) { - ctx.getExecutorServiceManager().setThreadNamePattern(getThreadNamePattern()); + ctx.getExecutorServiceManager().setThreadNamePattern(CamelContextHelper.parseText(context, getThreadNamePattern())); } if (getShutdownRoute() != null) { ctx.setShutdownRoute(getShutdownRoute()); http://git-wip-us.apache.org/repos/asf/camel/blob/e91148a0/components/camel-core-xml/src/test/java/org/apache/camel/core/xml/AbstractCamelContextFactoryBeanTest.java ---------------------------------------------------------------------- diff --git a/components/camel-core-xml/src/test/java/org/apache/camel/core/xml/AbstractCamelContextFactoryBeanTest.java b/components/camel-core-xml/src/test/java/org/apache/camel/core/xml/AbstractCamelContextFactoryBeanTest.java new file mode 100644 index 0000000..98fe9a1 --- /dev/null +++ b/components/camel-core-xml/src/test/java/org/apache/camel/core/xml/AbstractCamelContextFactoryBeanTest.java @@ -0,0 +1,178 @@ +/** + * 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.core.xml; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.Set; + +import static java.util.Arrays.asList; +import static java.util.Arrays.stream; + +import org.apache.camel.Service; +import org.apache.camel.TypeConverter; +import org.apache.camel.impl.DefaultClassResolver; +import org.apache.camel.impl.DefaultFactoryFinder; +import org.apache.camel.impl.DefaultPackageScanClassResolver; +import org.apache.camel.impl.converter.DefaultTypeConverter; +import org.apache.camel.model.ModelCamelContext; +import org.apache.camel.spi.ExecutorServiceManager; +import org.apache.camel.spi.ManagementNameStrategy; +import org.apache.camel.spi.RuntimeEndpointRegistry; +import org.apache.camel.util.ReflectionInjector; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.invocation.Invocation; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.doCallRealMethod; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.mockito.Mockito.withSettings; + +public class AbstractCamelContextFactoryBeanTest { + + // any properties (abstract methods in AbstractCamelContextFactoryBean that + // return String and receive no arguments) that do not support property + // placeholders + Set<String> propertiesThatAreNotPlaceholdered = Collections.singleton("{{getErrorHandlerRef}}"); + + TypeConverter typeConverter = new DefaultTypeConverter(new DefaultPackageScanClassResolver(), + new ReflectionInjector(), + new DefaultFactoryFinder(new DefaultClassResolver(), "META-INF/services/org/apache/camel/")); + + // properties that should return value that can be converted to boolean + Set<String> valuesThatReturnBoolean = new HashSet<>(asList("{{getStreamCache}}", "{{getTrace}}", + "{{getMessageHistory}}", "{{getLogMask}}", "{{getLogExhaustedMessageBody}}", "{{getHandleFault}}", + "{{getAutoStartup}}", "{{getUseMDCLogging}}", "{{getUseBreadcrumb}}", "{{getAllowUseOriginalMessage}}")); + + // properties that should return value that can be converted to long + Set<String> valuesThatReturnLong = new HashSet<>(asList("{{getDelayer}}")); + + public AbstractCamelContextFactoryBeanTest() throws Exception { + ((Service) typeConverter).start(); + } + + @Test + public void shouldSupportPropertyPlaceholdersOnAllProperties() throws Exception { + final Set<Invocation> invocations = new LinkedHashSet<>(); + + final ModelCamelContext context = mock(ModelCamelContext.class, + withSettings().invocationListeners(i -> invocations.add((Invocation) i.getInvocation()))); + + // program the property resolution in context mock + when(context.resolvePropertyPlaceholders(anyString())).thenAnswer(invocation -> { + final String placeholder = invocation.getArgumentAt(0, String.class); + + // we receive the argument and check if the method should return a + // value that can be converted to boolean + if (valuesThatReturnBoolean.contains(placeholder) || placeholder.endsWith("Enabled}}")) { + return "true"; + } + + // or long + if (valuesThatReturnLong.contains(placeholder)) { + return "1"; + } + + // else is just plain string + return "string"; + }); + when(context.getTypeConverter()).thenReturn(typeConverter); + when(context.getRuntimeEndpointRegistry()).thenReturn(mock(RuntimeEndpointRegistry.class)); + when(context.getManagementNameStrategy()).thenReturn(mock(ManagementNameStrategy.class)); + when(context.getExecutorServiceManager()).thenReturn(mock(ExecutorServiceManager.class)); + + @SuppressWarnings("unchecked") + final AbstractCamelContextFactoryBean<ModelCamelContext> factory = mock(AbstractCamelContextFactoryBean.class); + when(factory.getContext()).thenReturn(context); + doCallRealMethod().when(factory).initCamelContext(context); + + final Set<String> expectedPropertiesToBeResolved = propertiesToBeResolved(factory); + + // method under test + factory.initCamelContext(context); + + // we want to capture the arguments initCamelContext tried to resolve + // and check if it tried to resolve all placeholders we expected + final ArgumentCaptor<String> capturedPlaceholders = ArgumentCaptor.forClass(String.class); + verify(context, atLeastOnce()).resolvePropertyPlaceholders(capturedPlaceholders.capture()); + + // removes any properties that are not using property placeholders + expectedPropertiesToBeResolved.removeAll(propertiesThatAreNotPlaceholdered); + + assertThat(capturedPlaceholders.getAllValues()) + .as("The expectation is that all abstract getter methods that return Strings should support property " + + "placeholders, and that for those will delegate to CamelContext::resolvePropertyPlaceholders, " + + "we captured all placeholders that tried to resolve and found differences") + .containsAll(expectedPropertiesToBeResolved); + } + + Set<String> propertiesToBeResolved(final AbstractCamelContextFactoryBean<ModelCamelContext> factory) { + final Set<String> expectedPropertiesToBeResolved = new HashSet<>(); + + // looks at all abstract methods in AbstractCamelContextFactoryBean that + // do have no declared parameters and programs the mock to return + // "{{methodName}}" on calling that method, this happens when + // AbstractCamelContextFactoryBean::initContext invokes the programmed + // mock, so the returned collection will be empty until initContext + // invokes the mocked method + stream(AbstractCamelContextFactoryBean.class.getDeclaredMethods()) + .filter(m -> Modifier.isAbstract(m.getModifiers()) && m.getParameterCount() == 0).forEach(m -> { + try { + when(m.invoke(factory)).thenAnswer(invocation -> { + final Method method = invocation.getMethod(); + + final String name = method.getName(); + + if (String.class.equals(method.getReturnType())) { + final String placeholder = "{{" + name + "}}"; + expectedPropertiesToBeResolved.add(placeholder); + return placeholder; + } + + return null; + }); + } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ignored) { + // ignored + } + }); + + return expectedPropertiesToBeResolved; + } + + static boolean shouldProvidePropertyPlaceholderSupport(final Method method) { + // all abstract getter methods that return String are possibly returning + // strings that contain property placeholders + + final boolean isAbstract = Modifier.isAbstract(method.getModifiers()); + final boolean isGetter = method.getName().startsWith("get"); + final Class<?> returnType = method.getReturnType(); + + final boolean isCompatibleReturnType = String.class.isAssignableFrom(returnType); + + return isAbstract && isGetter && isCompatibleReturnType; + } + +} http://git-wip-us.apache.org/repos/asf/camel/blob/e91148a0/components/camel-spring/src/test/java/org/apache/camel/component/properties/BaseSpringPropertiesComponentTest.java ---------------------------------------------------------------------- diff --git a/components/camel-spring/src/test/java/org/apache/camel/component/properties/BaseSpringPropertiesComponentTest.java b/components/camel-spring/src/test/java/org/apache/camel/component/properties/BaseSpringPropertiesComponentTest.java new file mode 100644 index 0000000..c7dc26d --- /dev/null +++ b/components/camel-spring/src/test/java/org/apache/camel/component/properties/BaseSpringPropertiesComponentTest.java @@ -0,0 +1,62 @@ +/** + * 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.properties; + +import org.apache.camel.spring.SpringTestSupport; +import org.springframework.context.support.AbstractXmlApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; + +abstract class BaseSpringPropertiesComponentTest extends SpringTestSupport { + + @Override + protected AbstractXmlApplicationContext createApplicationContext() { + return new ClassPathXmlApplicationContext("org/apache/camel/component/properties/SpringPropertiesComponentTest.xml"); + } + + public void testSpringPropertiesComponentStart() throws Exception { + getMockEndpoint("mock:result").expectedMessageCount(1); + + template.sendBody("direct:start", "Hello World"); + + assertMockEndpointsSatisfied(); + } + + public void testSpringPropertiesComponentBar() throws Exception { + getMockEndpoint("mock:bar").expectedMessageCount(1); + + template.sendBody("direct:bar", "Hello World"); + + assertMockEndpointsSatisfied(); + } + + public void testSpringPropertiesComponentStart2() throws Exception { + getMockEndpoint("mock:result").expectedMessageCount(1); + + template.sendBody("direct:start2", "Hello World"); + + assertMockEndpointsSatisfied(); + } + + public void testSpringPropertiesComponentBar2() throws Exception { + getMockEndpoint("mock:bar").expectedMessageCount(1); + + template.sendBody("direct:bar2", "Hello World"); + + assertMockEndpointsSatisfied(); + } + +} http://git-wip-us.apache.org/repos/asf/camel/blob/e91148a0/components/camel-spring/src/test/java/org/apache/camel/component/properties/SpringPropertiesComponent2Test.java ---------------------------------------------------------------------- diff --git a/components/camel-spring/src/test/java/org/apache/camel/component/properties/SpringPropertiesComponent2Test.java b/components/camel-spring/src/test/java/org/apache/camel/component/properties/SpringPropertiesComponent2Test.java index 8c3ca65..307d309 100644 --- a/components/camel-spring/src/test/java/org/apache/camel/component/properties/SpringPropertiesComponent2Test.java +++ b/components/camel-spring/src/test/java/org/apache/camel/component/properties/SpringPropertiesComponent2Test.java @@ -19,10 +19,7 @@ package org.apache.camel.component.properties; import org.springframework.context.support.AbstractXmlApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; -/** - * @version - */ -public class SpringPropertiesComponent2Test extends SpringPropertiesComponentTest { +public class SpringPropertiesComponent2Test extends BaseSpringPropertiesComponentTest { @Override protected AbstractXmlApplicationContext createApplicationContext() { http://git-wip-us.apache.org/repos/asf/camel/blob/e91148a0/components/camel-spring/src/test/java/org/apache/camel/component/properties/SpringPropertiesComponent3Test.java ---------------------------------------------------------------------- diff --git a/components/camel-spring/src/test/java/org/apache/camel/component/properties/SpringPropertiesComponent3Test.java b/components/camel-spring/src/test/java/org/apache/camel/component/properties/SpringPropertiesComponent3Test.java index c1205fd..c2e2246 100644 --- a/components/camel-spring/src/test/java/org/apache/camel/component/properties/SpringPropertiesComponent3Test.java +++ b/components/camel-spring/src/test/java/org/apache/camel/component/properties/SpringPropertiesComponent3Test.java @@ -19,10 +19,7 @@ package org.apache.camel.component.properties; import org.springframework.context.support.AbstractXmlApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; -/** - * @version - */ -public class SpringPropertiesComponent3Test extends SpringPropertiesComponentTest { +public class SpringPropertiesComponent3Test extends BaseSpringPropertiesComponentTest { @Override protected AbstractXmlApplicationContext createApplicationContext() { http://git-wip-us.apache.org/repos/asf/camel/blob/e91148a0/components/camel-spring/src/test/java/org/apache/camel/component/properties/SpringPropertiesComponentCacheDisabledTest.java ---------------------------------------------------------------------- diff --git a/components/camel-spring/src/test/java/org/apache/camel/component/properties/SpringPropertiesComponentCacheDisabledTest.java b/components/camel-spring/src/test/java/org/apache/camel/component/properties/SpringPropertiesComponentCacheDisabledTest.java index 378ed53..9fbe696 100644 --- a/components/camel-spring/src/test/java/org/apache/camel/component/properties/SpringPropertiesComponentCacheDisabledTest.java +++ b/components/camel-spring/src/test/java/org/apache/camel/component/properties/SpringPropertiesComponentCacheDisabledTest.java @@ -19,10 +19,7 @@ package org.apache.camel.component.properties; import org.springframework.context.support.AbstractXmlApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; -/** - * @version - */ -public class SpringPropertiesComponentCacheDisabledTest extends SpringPropertiesComponentTest { +public class SpringPropertiesComponentCacheDisabledTest extends BaseSpringPropertiesComponentTest { @Override protected AbstractXmlApplicationContext createApplicationContext() { http://git-wip-us.apache.org/repos/asf/camel/blob/e91148a0/components/camel-spring/src/test/java/org/apache/camel/component/properties/SpringPropertiesComponentTest.java ---------------------------------------------------------------------- diff --git a/components/camel-spring/src/test/java/org/apache/camel/component/properties/SpringPropertiesComponentTest.java b/components/camel-spring/src/test/java/org/apache/camel/component/properties/SpringPropertiesComponentTest.java index b41fcac..096c051 100644 --- a/components/camel-spring/src/test/java/org/apache/camel/component/properties/SpringPropertiesComponentTest.java +++ b/components/camel-spring/src/test/java/org/apache/camel/component/properties/SpringPropertiesComponentTest.java @@ -16,49 +16,20 @@ */ package org.apache.camel.component.properties; -import org.apache.camel.spring.SpringTestSupport; -import org.springframework.context.support.AbstractXmlApplicationContext; -import org.springframework.context.support.ClassPathXmlApplicationContext; +import org.apache.camel.CamelContext; +import org.apache.camel.spring.CamelContextFactoryBean; -/** - * @version - */ -public class SpringPropertiesComponentTest extends SpringTestSupport { - - @Override - protected AbstractXmlApplicationContext createApplicationContext() { - return new ClassPathXmlApplicationContext("org/apache/camel/component/properties/SpringPropertiesComponentTest.xml"); - } - - public void testSpringPropertiesComponentStart() throws Exception { - getMockEndpoint("mock:result").expectedMessageCount(1); - - template.sendBody("direct:start", "Hello World"); - - assertMockEndpointsSatisfied(); - } - - public void testSpringPropertiesComponentBar() throws Exception { - getMockEndpoint("mock:bar").expectedMessageCount(1); +public class SpringPropertiesComponentTest extends BaseSpringPropertiesComponentTest { - template.sendBody("direct:bar", "Hello World"); - - assertMockEndpointsSatisfied(); - } + public void testResolutionOfPlaceholdersOnFactoryBean() { + final CamelContextFactoryBean factoryBean = applicationContext.getBean("&camel-1", + CamelContextFactoryBean.class); - public void testSpringPropertiesComponentStart2() throws Exception { - getMockEndpoint("mock:result").expectedMessageCount(1); + assertEquals("{{autoStartup}}", factoryBean.getAutoStartup()); - template.sendBody("direct:start2", "Hello World"); + final CamelContext context = applicationContext.getBean("camel-1", CamelContext.class); - assertMockEndpointsSatisfied(); + assertTrue(context.isAutoStartup()); } - public void testSpringPropertiesComponentBar2() throws Exception { - getMockEndpoint("mock:bar").expectedMessageCount(1); - - template.sendBody("direct:bar2", "Hello World"); - - assertMockEndpointsSatisfied(); - } }