/** * 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; } }