| /* |
| * 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.ExtendedCamelContext; |
| import org.apache.camel.Service; |
| import org.apache.camel.TypeConverter; |
| import org.apache.camel.impl.DefaultCamelContext; |
| import org.apache.camel.impl.converter.DefaultTypeConverter; |
| import org.apache.camel.impl.engine.DefaultClassResolver; |
| import org.apache.camel.impl.engine.DefaultFactoryFinder; |
| import org.apache.camel.impl.engine.DefaultPackageScanClassResolver; |
| import org.apache.camel.model.ModelCamelContext; |
| import org.apache.camel.spi.ExecutorServiceManager; |
| import org.apache.camel.spi.Injector; |
| import org.apache.camel.spi.ManagementNameStrategy; |
| import org.apache.camel.spi.RuntimeEndpointRegistry; |
| import org.apache.camel.support.ObjectHelper; |
| 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.ArgumentMatchers.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 Injector() { |
| @Override |
| public <T> T newInstance(Class<T> type) { |
| return newInstance(type, false); |
| } |
| |
| @Override |
| public <T> T newInstance(Class<T> type, String factoryMethod) { |
| return null; |
| } |
| |
| @Override |
| public <T> T newInstance(Class<T> type, boolean postProcessBean) { |
| return ObjectHelper.newInstance(type); |
| } |
| |
| @Override |
| public boolean supportsAutoWiring() { |
| return false; |
| } |
| }, |
| new DefaultFactoryFinder(new DefaultClassResolver(), "META-INF/services/org/apache/camel/"), false); |
| |
| // 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}}", "{{getUseDataType}}", "{{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 DefaultCamelContext context = mock(DefaultCamelContext.class, |
| withSettings().invocationListeners(i -> invocations.add((Invocation) i.getInvocation()))); |
| |
| when(context.adapt(ExtendedCamelContext.class)).thenReturn(context); |
| |
| // program the property resolution in context mock |
| when(context.resolvePropertyPlaceholders(anyString())).thenAnswer(invocation -> { |
| final String placeholder = invocation.getArgument(0); |
| |
| // 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; |
| } |
| |
| } |