blob: 375dd8931f0a151df525f004673d4990e30a0a90 [file] [log] [blame]
/*
* 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;
}
}