/*
* Copyright (c) MuleSoft, Inc. All rights reserved. http://www.mulesoft.com
* The software in this package is published under the terms of the CPAL v1.0
* license, a copy of which has been included with this distribution in the
* LICENSE.txt file.
*/
package org.mule.runtime.config.spring.dsl.spring;
import static java.util.Optional.of;
import static java.util.Optional.ofNullable;
import static java.util.stream.Collectors.toList;
import static org.mule.runtime.config.spring.dsl.model.ApplicationModel.PROCESSING_STRATEGY_ATTRIBUTE;
import static org.mule.runtime.config.spring.dsl.spring.CommonBeanDefinitionCreator.areMatchingTypes;
import static org.mule.runtime.core.util.ProcessingStrategyUtils.parseProcessingStrategy;
import org.mule.runtime.config.spring.dsl.model.ComponentModel;
import org.mule.runtime.core.api.processor.strategy.ProcessingStrategyFactory;
import org.mule.runtime.core.util.ClassUtils;
import org.mule.runtime.dsl.api.component.AttributeDefinition;
import org.mule.runtime.dsl.api.component.AttributeDefinitionVisitor;
import org.mule.runtime.dsl.api.component.ComponentBuildingDefinition;
import org.mule.runtime.dsl.api.component.KeyAttributeDefinitionPair;
import org.mule.runtime.dsl.api.component.SetterAttributeDefinition;
import org.mule.runtime.dsl.api.component.TypeConverter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.function.Predicate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.config.RuntimeBeanNameReference;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.ManagedList;
import org.springframework.beans.factory.support.ManagedMap;
/**
* Based on the object building definition provided by {@link org.mule.runtime.dsl.api.component.ComponentBuildingDefinition}
* and the user configuration defined in {@link org.mule.runtime.config.spring.dsl.model.ComponentModel} it populates all the
* spring {@link org.springframework.beans.factory.config.BeanDefinition} attributes using the helper class
* {@link org.mule.runtime.config.spring.dsl.spring.BeanDefinitionBuilderHelper}.
*
* @since 4.0
*/
class ComponentConfigurationBuilder {
private static final Logger logger = LoggerFactory.getLogger(ComponentConfigurationBuilder.class);
private final BeanDefinitionBuilderHelper beanDefinitionBuilderHelper;
private final ObjectReferencePopulator objectReferencePopulator = new ObjectReferencePopulator();
private final List<ComponentValue> complexParameters;
private final Map<String, String> simpleParameters;
private final ComponentModel componentModel;
private final ComponentBuildingDefinition componentBuildingDefinition;
public ComponentConfigurationBuilder(ComponentModel componentModel, ComponentBuildingDefinition componentBuildingDefinition,
BeanDefinitionBuilderHelper beanDefinitionBuilderHelper) {
this.componentModel = componentModel;
this.componentBuildingDefinition = componentBuildingDefinition;
this.beanDefinitionBuilderHelper = beanDefinitionBuilderHelper;
this.simpleParameters = new HashMap(componentModel.getParameters());
this.complexParameters = collectComplexParametersWithTypes(componentModel);
}
public void processConfiguration() {
componentBuildingDefinition.getIgnoredConfigurationParameters().stream().forEach(simpleParameters::remove);
for (SetterAttributeDefinition setterAttributeDefinition : componentBuildingDefinition.getSetterParameterDefinitions()) {
AttributeDefinition attributeDefinition = setterAttributeDefinition.getAttributeDefinition();
attributeDefinition.accept(setterVisitor(setterAttributeDefinition.getAttributeName(), attributeDefinition));
}
for (AttributeDefinition attributeDefinition : componentBuildingDefinition.getConstructorAttributeDefinition()) {
attributeDefinition.accept(constructorVisitor());
}
}
private List<ComponentValue> collectComplexParametersWithTypes(ComponentModel componentModel) {
/*
* TODO: MULE-9638 This ugly code is required since we need to get the object type from the bean definition. This code will go
* away one we remove the old parsing method.
*/
return componentModel.getInnerComponents().stream().filter(child -> child.isEnabled()).map(cdm -> {
// When it comes from old model it does not have the type set
Class<?> beanDefinitionType = cdm.getType();
if (beanDefinitionType == null) {
if (cdm.getBeanDefinition() == null) {
// Some component do not have a bean definition since the element parsing is ignored. i.e: annotations
return null;
} else {
try {
String beanClassName = cdm.getBeanDefinition().getBeanClassName();
if (beanClassName != null) {
beanDefinitionType = ClassUtils.getClass(beanClassName);
} else {
// Happens in case of spring:property
beanDefinitionType = Object.class;
}
} catch (ClassNotFoundException e) {
logger.debug("Exception trying to determine ComponentModel type: ", e);
beanDefinitionType = Object.class;
}
}
}
Object bean = cdm.getBeanDefinition() != null ? cdm.getBeanDefinition() : cdm.getBeanReference();
return new ComponentValue(cdm, beanDefinitionType, bean);
}).filter(beanDefinitionTypePair -> beanDefinitionTypePair != null).collect(toList());
}
private ConfigurableAttributeDefinitionVisitor constructorVisitor() {
return new ConfigurableAttributeDefinitionVisitor(beanDefinitionBuilderHelper::addConstructorValue);
}
private ConfigurableAttributeDefinitionVisitor setterVisitor(String propertyName, AttributeDefinition attributeDefinition) {
DefaultValueVisitor defaultValueVisitor = new DefaultValueVisitor();
attributeDefinition.accept(defaultValueVisitor);
Optional<Object> defaultValue = defaultValueVisitor.getDefaultValue();
return new ConfigurableAttributeDefinitionVisitor(value -> {
if (isPropertySetWithUserConfigValue(propertyName, defaultValue, value)) {
return;
}
beanDefinitionBuilderHelper.forProperty(propertyName).addValue(value);
});
}
private boolean isPropertySetWithUserConfigValue(String propertyName, Optional<Object> defaultValue, Object value) {
return defaultValue.isPresent() && defaultValue.get().equals(value)
&& beanDefinitionBuilderHelper.hasValueForProperty(propertyName);
}
/**
* Process a single {@link AttributeDefinition} from a {@link ComponentBuildingDefinition} and uses an invokes a
* {@code Consumer} when the value is a bean definition or a different {@code Consumer} if the value is a bean reference.
*/
private class ConfigurableAttributeDefinitionVisitor implements AttributeDefinitionVisitor {
private final Consumer<Object> valueConsumer;
/**
* @param valueConsumer consumer for handling a bean definition
*/
ConfigurableAttributeDefinitionVisitor(Consumer<Object> valueConsumer) {
this.valueConsumer = valueConsumer;
}
@Override
public void onReferenceObject(Class<?> objectType) {
ValueExtractorAttributeDefinitionVisitor valueExtractor = new ValueExtractorAttributeDefinitionVisitor();
valueExtractor.onReferenceObject(objectType);
valueConsumer.accept(valueExtractor.getValue());
}
@Override
public void onReferenceSimpleParameter(final String configAttributeName) {
ValueExtractorAttributeDefinitionVisitor valueExtractor = new ValueExtractorAttributeDefinitionVisitor();
valueExtractor.onReferenceSimpleParameter(configAttributeName);
Object value = valueExtractor.getValue();
if (value != null) {
valueConsumer.accept(value);
} else {
valueConsumer.accept(null);
}
}
@Override
public void onReferenceFixedParameter(String reference) {
valueConsumer.accept(new RuntimeBeanReference(reference));
}
@Override
public void onFixedValue(Object value) {
ValueExtractorAttributeDefinitionVisitor valueExtractor = new ValueExtractorAttributeDefinitionVisitor();
valueExtractor.onFixedValue(value);
valueConsumer.accept(value);
}
@Override
public void onConfigurationParameter(String parameterName, Object defaultValue, Optional<TypeConverter> typeConverter) {
ValueExtractorAttributeDefinitionVisitor valueExtractor = new ValueExtractorAttributeDefinitionVisitor();
valueExtractor.onConfigurationParameter(parameterName, defaultValue, typeConverter);
valueConsumer.accept(valueExtractor.getValue());
}
@Override
public void onUndefinedSimpleParameters() {
ValueExtractorAttributeDefinitionVisitor valueExtractor = new ValueExtractorAttributeDefinitionVisitor();
valueExtractor.onUndefinedSimpleParameters();
valueConsumer.accept(valueExtractor.getValue());
}
@Override
public void onUndefinedComplexParameters() {
ValueExtractorAttributeDefinitionVisitor valueExtractor = new ValueExtractorAttributeDefinitionVisitor();
valueExtractor.onUndefinedComplexParameters();
valueConsumer.accept(valueExtractor.getValue());
}
@Override
public void onComplexChildCollection(Class<?> type, Optional<String> wrapperIdentifier) {
ValueExtractorAttributeDefinitionVisitor valueExtractor = new ValueExtractorAttributeDefinitionVisitor();
valueExtractor.onComplexChildCollection(type, wrapperIdentifier);
valueConsumer.accept(valueExtractor.getValue());
}
@Override
public void onComplexChildMap(Class<?> keyType, Class<?> valueType, String wrapperIdentifier) {
ValueExtractorAttributeDefinitionVisitor valueExtractor = new ValueExtractorAttributeDefinitionVisitor();
valueExtractor.onComplexChildMap(keyType, valueType, wrapperIdentifier);
valueConsumer.accept(valueExtractor.getValue());
}
@Override
public void onComplexChild(Class<?> type, Optional<String> wrapperIdentifier, Optional<String> childIdentifier) {
ValueExtractorAttributeDefinitionVisitor valueExtractor = new ValueExtractorAttributeDefinitionVisitor();
valueExtractor.onComplexChild(type, wrapperIdentifier, childIdentifier);
Object value = valueExtractor.getValue();
if (value != null) {
valueConsumer.accept(value);
}
}
@Override
public void onValueFromTextContent() {
ValueExtractorAttributeDefinitionVisitor valueExtractor = new ValueExtractorAttributeDefinitionVisitor();
valueExtractor.onValueFromTextContent();
valueConsumer.accept(valueExtractor.getValue());
}
@Override
public void onMultipleValues(KeyAttributeDefinitionPair[] definitions) {
ManagedMap managedMap = new ManagedMap();
for (KeyAttributeDefinitionPair definition : definitions) {
ValueExtractorAttributeDefinitionVisitor valueExtractor = new ValueExtractorAttributeDefinitionVisitor();
definition.getAttributeDefinition().accept(valueExtractor);
Object value = valueExtractor.getValue();
if (value != null) {
managedMap.put(definition.getKey(), value);
}
}
valueConsumer.accept(managedMap);
}
}
/**
* {code AttributeDefinitionVisitor} that extracts the value from the {@code ComponentModel}
*/
private class ValueExtractorAttributeDefinitionVisitor implements AttributeDefinitionVisitor {
private Object value;
private String getStringValue() {
return (String) getValue();
}
public Object getValue() {
return value;
}
@Override
public void onReferenceObject(Class<?> objectType) {
objectReferencePopulator.populate(objectType, referenceId -> this.value = new RuntimeBeanReference(referenceId));
}
@Override
public void onReferenceSimpleParameter(final String configAttributeName) {
String reference = simpleParameters.get(configAttributeName);
if (reference != null) {
this.value = new RuntimeBeanReference(reference);
}
simpleParameters.remove(configAttributeName);
if (configAttributeName.equals(PROCESSING_STRATEGY_ATTRIBUTE) || configAttributeName.equals("defaultProcessingStrategy")) {
ProcessingStrategyFactory processingStrategyFactory = parseProcessingStrategy(reference);
if (processingStrategyFactory != null) {
this.value = processingStrategyFactory;
return;
}
}
}
@Override
public void onReferenceFixedParameter(String reference) {
this.value = new RuntimeBeanReference(reference);
}
@Override
public void onFixedValue(Object value) {
this.value = value;
}
@Override
public void onConfigurationParameter(String parameterName, Object defaultValue, Optional<TypeConverter> typeConverter) {
Object parameterValue = simpleParameters.get(parameterName);
simpleParameters.remove(parameterName);
parameterValue = parameterValue != null ? parameterValue : defaultValue;
if (parameterValue != null) {
parameterValue = typeConverter.isPresent() ? typeConverter.get().convert(parameterValue) : parameterValue;
}
this.value = Optional.ofNullable(parameterValue).orElse(defaultValue);
}
@Override
public void onUndefinedSimpleParameters() {
this.value = simpleParameters;
}
@Override
public void onUndefinedComplexParameters() {
this.value = constructManagedList(fromBeanDefinitionTypePairToBeanDefinition(complexParameters));
}
@Override
public void onComplexChildCollection(Class<?> type, Optional<String> wrapperIdentifier) {
Predicate<ComponentValue> matchesTypeAndIdentifierPredicate = getTypeAndIdentifierPredicate(type, wrapperIdentifier);
List<ComponentValue> matchingComponentValues = complexParameters.stream()
.filter(matchesTypeAndIdentifierPredicate)
.collect(toList());
matchingComponentValues.stream().forEach(complexParameters::remove);
if (wrapperIdentifier.isPresent() && !matchingComponentValues.isEmpty()) {
this.value = matchingComponentValues.get(0).getBean();
} else {
if (!matchingComponentValues.isEmpty()) {
this.value = constructManagedList(fromBeanDefinitionTypePairToBeanDefinition(matchingComponentValues));
}
}
}
@Override
public void onComplexChildMap(Class<?> keyType, Class<?> valueType, String wrapperIdentifier) {
complexParameters.stream()
.filter(getTypeAndIdentifierPredicate(MapFactoryBean.class, ofNullable(wrapperIdentifier)))
.findFirst()
.ifPresent(componentValue -> {
complexParameters.remove(componentValue);
value = componentValue.getBean();
});
}
@Override
public void onComplexChild(Class<?> type, Optional<String> wrapperIdentifier, Optional<String> childIdentifier) {
Optional<String> identifier = wrapperIdentifier.isPresent() ? wrapperIdentifier : childIdentifier;
Predicate<ComponentValue> matchesTypeAndIdentifierPredicate = getTypeAndIdentifierPredicate(type, identifier);
Optional<ComponentValue> value = complexParameters.stream().filter(matchesTypeAndIdentifierPredicate).findFirst();
value.ifPresent(beanDefinitionTypePair -> {
complexParameters.remove(beanDefinitionTypePair);
Object bean = beanDefinitionTypePair.getBean();
this.value = bean;
});
}
private Predicate<ComponentValue> getTypeAndIdentifierPredicate(Class<?> type, Optional<String> identifierOptional) {
return componentValue -> {
AtomicReference<Boolean> matchesIdentifier = new AtomicReference<>(true);
identifierOptional.ifPresent(wrapperIdentifier -> matchesIdentifier
.set(wrapperIdentifier.equals(componentValue.getComponentModel().getIdentifier().getName())));
return matchesIdentifier.get() && (areMatchingTypes(type, componentValue.getType())
|| ((areMatchingTypes(Map.class, componentValue.getType()) && areMatchingTypes(MapFactoryBean.class, type))));
};
}
@Override
public void onValueFromTextContent() {
this.value = componentModel.getTextContent();
}
@Override
public void onMultipleValues(KeyAttributeDefinitionPair[] definitions) {
for (KeyAttributeDefinitionPair definition : definitions) {
definition.getAttributeDefinition().accept(this);
}
}
}
private ManagedList constructManagedList(List<Object> beans) {
ManagedList managedList = new ManagedList();
managedList.addAll(beans);
return managedList;
}
private List<Object> fromBeanDefinitionTypePairToBeanDefinition(List<ComponentValue> undefinedComplexParameters) {
return undefinedComplexParameters.stream().map(ComponentValue::getBean).collect(toList());
}
}