/*
* 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.Collections.emptyMap;
import static java.util.Optional.empty;
import static org.apache.commons.beanutils.BeanUtils.copyProperty;
import static org.mule.runtime.api.meta.AnnotatedObject.PROPERTY_NAME;
import static org.mule.runtime.config.spring.dsl.model.ApplicationModel.ANNOTATIONS_ELEMENT_IDENTIFIER;
import static org.mule.runtime.config.spring.dsl.model.ApplicationModel.CUSTOM_TRANSFORMER_IDENTIFIER;
import static org.mule.runtime.config.spring.dsl.model.ApplicationModel.FILTER_ELEMENT_SUFFIX;
import static org.mule.runtime.config.spring.dsl.model.ApplicationModel.MESSAGE_FILTER_ELEMENT_IDENTIFIER;
import static org.mule.runtime.config.spring.dsl.model.ApplicationModel.MULE_IDENTIFIER;
import static org.mule.runtime.config.spring.dsl.model.ApplicationModel.MULE_PROPERTIES_IDENTIFIER;
import static org.mule.runtime.config.spring.dsl.model.ApplicationModel.MULE_PROPERTY_IDENTIFIER;
import static org.mule.runtime.config.spring.dsl.model.ApplicationModel.PROTOTYPE_OBJECT_IDENTIFIER;
import static org.mule.runtime.config.spring.dsl.model.ApplicationModel.SINGLETON_OBJECT_IDENTIFIER;
import static org.mule.runtime.config.spring.dsl.model.ApplicationModel.SPRING_PROPERTY_IDENTIFIER;
import static org.mule.runtime.config.spring.dsl.processor.xml.XmlCustomAttributeHandler.from;
import static org.mule.runtime.config.spring.dsl.spring.BeanDefinitionFactory.SPRING_PROTOTYPE_OBJECT;
import static org.mule.runtime.config.spring.dsl.spring.PropertyComponentUtils.getPropertyValueFromPropertyComponent;
import static org.mule.runtime.config.spring.parsers.AbstractMuleBeanDefinitionParser.processMetadataAnnotationsHelper;
import static org.mule.runtime.deployment.model.internal.application.MuleApplicationClassLoader.resolveContextArtifactPluginClassLoaders;
import static org.springframework.beans.factory.support.BeanDefinitionBuilder.genericBeanDefinition;
import static org.springframework.beans.factory.support.BeanDefinitionBuilder.rootBeanDefinition;
import org.mule.runtime.api.component.ComponentIdentifier;
import org.mule.runtime.api.exception.MuleRuntimeException;
import org.mule.runtime.api.meta.AnnotatedObject;
import org.mule.runtime.config.spring.dsl.model.ComponentModel;
import org.mule.runtime.config.spring.dsl.processor.ObjectTypeVisitor;
import org.mule.runtime.config.spring.dsl.processor.xml.XmlCustomAttributeHandler;
import org.mule.runtime.core.api.routing.filter.Filter;
import org.mule.runtime.core.api.security.SecurityFilter;
import org.mule.runtime.core.processor.SecurityFilterMessageProcessor;
import org.mule.runtime.core.routing.MessageFilter;
import org.mule.runtime.core.util.ClassUtils;
import org.mule.runtime.dsl.api.component.ComponentBuildingDefinition;
import com.google.common.collect.ImmutableSet;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import javax.xml.namespace.QName;
import org.springframework.beans.PropertyValue;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
/**
* Processor in the chain of responsibility that knows how to handle a generic {@code ComponentBuildingDefinition}.
*
* @since 4.0
* <p/>
* TODO MULE-9638 set visibility to package
*/
public class CommonBeanDefinitionCreator extends BeanDefinitionCreator {
private static final String TRANSPORT_BEAN_DEFINITION_POST_PROCESSOR_CLASS =
"org.mule.compatibility.config.spring.parsers.specific.TransportElementBeanDefinitionPostProcessor";
private static final ImmutableSet<ComponentIdentifier> MESSAGE_FILTER_WRAPPERS =
new ImmutableSet.Builder<ComponentIdentifier>()
.add(MESSAGE_FILTER_ELEMENT_IDENTIFIER)
.add(MULE_IDENTIFIER)
.build();
private Set<ComponentIdentifier> genericPropertiesCustomProcessingIdentifiers =
ImmutableSet.<ComponentIdentifier>builder()
.add(SINGLETON_OBJECT_IDENTIFIER)
.add(PROTOTYPE_OBJECT_IDENTIFIER)
.add(CUSTOM_TRANSFORMER_IDENTIFIER)
.build();
private final ObjectFactoryClassRepository objectFactoryClassRepository;
private BeanDefinitionPostProcessor beanDefinitionPostProcessor;
public CommonBeanDefinitionCreator(ObjectFactoryClassRepository objectFactoryClassRepository) {
this.objectFactoryClassRepository = objectFactoryClassRepository;
this.beanDefinitionPostProcessor = resolvePostProcessor();
}
// TODO MULE-9728 - Provide a mechanism to hook per transport in the endpoint address parsing
private BeanDefinitionPostProcessor resolvePostProcessor() {
for (ClassLoader classLoader : resolveContextArtifactPluginClassLoaders()) {
try {
return (BeanDefinitionPostProcessor) ClassUtils.getClass(classLoader, TRANSPORT_BEAN_DEFINITION_POST_PROCESSOR_CLASS)
.newInstance();
} catch (Exception e) {
// Nothing to do, we just don't have compatibility plugin in the app
}
}
return (componentModel, beanDefinition) -> {
};
}
@Override
public boolean handleRequest(CreateBeanDefinitionRequest request) {
ComponentModel componentModel = request.getComponentModel();
ComponentBuildingDefinition buildingDefinition = request.getComponentBuildingDefinition();
componentModel.setType(retrieveComponentType(componentModel, buildingDefinition));
BeanDefinitionBuilder beanDefinitionBuilder = createBeanDefinitionBuilder(componentModel, buildingDefinition);
processAnnotations(componentModel, beanDefinitionBuilder);
processComponentDefinitionModel(request.getParentComponentModel(), componentModel, buildingDefinition, beanDefinitionBuilder);
return true;
}
private BeanDefinitionBuilder createBeanDefinitionBuilder(ComponentModel componentModel,
ComponentBuildingDefinition buildingDefinition) {
BeanDefinitionBuilder beanDefinitionBuilder;
if (buildingDefinition.getObjectFactoryType() != null) {
beanDefinitionBuilder = createBeanDefinitionBuilderFromObjectFactory(componentModel, buildingDefinition);
} else {
beanDefinitionBuilder = genericBeanDefinition(componentModel.getType());
}
return beanDefinitionBuilder;
}
private void processAnnotationParameters(ComponentModel componentModel, Map<QName, Object> annotations) {
componentModel.getParameters().entrySet().stream()
.filter(entry -> entry.getKey().contains(":"))
.forEach(annotationKey -> {
Node attribute = from(componentModel).getNode().getAttributes().getNamedItem(annotationKey.getKey());
if (attribute != null) {
annotations.put(new QName(attribute.getNamespaceURI(), attribute.getLocalName()), annotationKey.getValue());
}
});
}
private void processNestedAnnotations(ComponentModel componentModel, Map<QName, Object> previousAnnotations) {
componentModel.getInnerComponents().stream()
.filter(cdm -> cdm.getIdentifier().equals(ANNOTATIONS_ELEMENT_IDENTIFIER))
.findFirst()
.ifPresent(annotationsCdm -> annotationsCdm.getInnerComponents().forEach(
annotationCdm -> previousAnnotations
.put(new QName(from(annotationCdm)
.getNamespaceUri(), annotationCdm
.getIdentifier()
.getName()),
annotationCdm.getTextContent())));
}
private void processAnnotations(ComponentModel componentModel, BeanDefinitionBuilder beanDefinitionBuilder) {
if (AnnotatedObject.class.isAssignableFrom(componentModel.getType())) {
XmlCustomAttributeHandler.ComponentCustomAttributeRetrieve customAttributeRetrieve = from(componentModel);
Map<QName, Object> annotations =
processMetadataAnnotationsHelper((Element) customAttributeRetrieve.getNode(), null, beanDefinitionBuilder);
processAnnotationParameters(componentModel, annotations);
processNestedAnnotations(componentModel, annotations);
if (!annotations.isEmpty()) {
beanDefinitionBuilder.addPropertyValue(PROPERTY_NAME, annotations);
}
}
}
private Class<?> retrieveComponentType(final ComponentModel componentModel,
ComponentBuildingDefinition componentBuildingDefinition) {
ObjectTypeVisitor objectTypeVisitor = new ObjectTypeVisitor(componentModel);
componentBuildingDefinition.getTypeDefinition().visit(objectTypeVisitor);
return objectTypeVisitor.getType();
}
private BeanDefinitionBuilder createBeanDefinitionBuilderFromObjectFactory(final ComponentModel componentModel,
final ComponentBuildingDefinition componentBuildingDefinition) {
ObjectTypeVisitor objectTypeVisitor = new ObjectTypeVisitor(componentModel);
componentBuildingDefinition.getTypeDefinition().visit(objectTypeVisitor);
BeanDefinitionBuilder beanDefinitionBuilder;
Class<?> objectFactoryType = componentBuildingDefinition.getObjectFactoryType();
Map<String, Object> customProperties = getTransformerCustomProperties(componentModel);
Optional<Consumer<Object>> instanceCustomizationFunction = empty();
if (!customProperties.isEmpty()) {
instanceCustomizationFunction = Optional.of(object -> {
injectSpringProperties(customProperties, object);
});
}
Class factoryBeanClass = objectFactoryClassRepository.getObjectFactoryClass(componentBuildingDefinition,
objectFactoryType,
objectTypeVisitor.getType(),
() -> componentModel.getBeanDefinition()
.isLazyInit(),
instanceCustomizationFunction);
beanDefinitionBuilder = rootBeanDefinition(factoryBeanClass);
return beanDefinitionBuilder;
}
private void injectSpringProperties(Map<String, Object> customProperties, Object createdInstance) {
try {
for (String propertyName : customProperties.keySet()) {
copyProperty(createdInstance, propertyName, customProperties.get(propertyName));
}
} catch (Exception e) {
throw new MuleRuntimeException(e);
}
}
private Map<String, Object> getTransformerCustomProperties(ComponentModel componentModel) {
ComponentIdentifier identifier = componentModel.getIdentifier();
if (!identifier.equals(CUSTOM_TRANSFORMER_IDENTIFIER)) {
return emptyMap();
}
return componentModel.getInnerComponents()
.stream()
.filter(innerComponent -> {
ComponentIdentifier childIdentifier = innerComponent.getIdentifier();
return childIdentifier.equals(SPRING_PROPERTY_IDENTIFIER) || childIdentifier.equals(MULE_PROPERTY_IDENTIFIER);
})
.collect(Collectors.toMap(springComponent -> getPropertyValueFromPropertyComponent(springComponent).getName(),
springComponent -> getPropertyValueFromPropertyComponent(springComponent).getValue()));
}
private void processComponentDefinitionModel(final ComponentModel parentComponentModel, final ComponentModel componentModel,
ComponentBuildingDefinition componentBuildingDefinition,
final BeanDefinitionBuilder beanDefinitionBuilder) {
processObjectConstructionParameters(componentModel, componentBuildingDefinition,
new BeanDefinitionBuilderHelper(beanDefinitionBuilder));
processSpringOrMuleProperties(componentModel, beanDefinitionBuilder);
if (componentBuildingDefinition.isPrototype()) {
beanDefinitionBuilder.setScope(SPRING_PROTOTYPE_OBJECT);
}
AbstractBeanDefinition originalBeanDefinition = beanDefinitionBuilder.getBeanDefinition();
AbstractBeanDefinition wrappedBeanDefinition = adaptFilterBeanDefinitions(parentComponentModel, originalBeanDefinition);
if (originalBeanDefinition != wrappedBeanDefinition) {
componentModel.setType(wrappedBeanDefinition.getBeanClass());
}
beanDefinitionPostProcessor.postProcess(componentModel, wrappedBeanDefinition);
componentModel.setBeanDefinition(wrappedBeanDefinition);
}
// TODO MULE-9638 Remove once we don't mix spring beans with mule beans.
private void processSpringOrMuleProperties(ComponentModel componentModel, BeanDefinitionBuilder beanDefinitionBuilder) {
// for now we skip custom-transformer since requires injection by the object factory.
if (genericPropertiesCustomProcessingIdentifiers.contains(componentModel.getIdentifier())) {
return;
}
componentModel.getInnerComponents()
.stream()
.filter(innerComponent -> {
ComponentIdentifier identifier = innerComponent.getIdentifier();
return identifier.equals(SPRING_PROPERTY_IDENTIFIER) || identifier.equals(MULE_PROPERTY_IDENTIFIER)
|| identifier.equals(MULE_PROPERTIES_IDENTIFIER);
})
.forEach(propertyComponentModel -> {
PropertyValue propertyValue = getPropertyValueFromPropertyComponent(propertyComponentModel);
beanDefinitionBuilder.addPropertyValue(propertyValue.getName(), propertyValue.getValue());
});
}
public static List<PropertyValue> getPropertyValueFromPropertiesComponent(ComponentModel propertyComponentModel) {
List<PropertyValue> propertyValues = new ArrayList<>();
propertyComponentModel.getInnerComponents().stream().forEach(entryComponentModel -> {
propertyValues.add(new PropertyValue(entryComponentModel.getParameters().get("key"),
entryComponentModel.getParameters().get("value")));
});
return propertyValues;
}
private void processObjectConstructionParameters(final ComponentModel componentModel,
final ComponentBuildingDefinition componentBuildingDefinition,
final BeanDefinitionBuilderHelper beanDefinitionBuilderHelper) {
new ComponentConfigurationBuilder(componentModel, componentBuildingDefinition, beanDefinitionBuilderHelper)
.processConfiguration();
}
public static AbstractBeanDefinition adaptFilterBeanDefinitions(ComponentModel parentComponentModel,
AbstractBeanDefinition originalBeanDefinition) {
// TODO this condition may be removed
if (originalBeanDefinition == null) {
return null;
}
if (!filterWrapperRequired(parentComponentModel)) {
return originalBeanDefinition;
}
Class beanClass;
if (originalBeanDefinition instanceof RootBeanDefinition) {
beanClass = ((RootBeanDefinition) originalBeanDefinition).getBeanClass();
} else {
// TODO see if this condition can be removed.
if (originalBeanDefinition.getBeanClassName() == null) {
return originalBeanDefinition;
}
try {
beanClass = ClassUtils.getClass(originalBeanDefinition.getBeanClassName());
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
BeanDefinition newBeanDefinition;
if (areMatchingTypes(Filter.class, beanClass)) {
boolean failOnUnaccepted = false;
Object processorWhenUnaccepted = null;
newBeanDefinition =
BeanDefinitionBuilder.rootBeanDefinition(MessageFilter.class).addConstructorArgValue(originalBeanDefinition)
.addConstructorArgValue(failOnUnaccepted).addConstructorArgValue(processorWhenUnaccepted).getBeanDefinition();
return (AbstractBeanDefinition) newBeanDefinition;
} else if (areMatchingTypes(SecurityFilter.class, beanClass)) {
newBeanDefinition = BeanDefinitionBuilder.rootBeanDefinition(SecurityFilterMessageProcessor.class)
.addPropertyValue("filter", originalBeanDefinition).getBeanDefinition();
return (AbstractBeanDefinition) newBeanDefinition;
}
return originalBeanDefinition;
}
private static boolean filterWrapperRequired(ComponentModel parentComponentModel) {
return !MESSAGE_FILTER_WRAPPERS.contains(parentComponentModel.getIdentifier())
&& !parentComponentModel.getIdentifier().getName().endsWith(FILTER_ELEMENT_SUFFIX);
}
public static boolean areMatchingTypes(Class<?> superType, Class<?> childType) {
return superType.isAssignableFrom(childType);
}
public interface BeanDefinitionPostProcessor {
void postProcess(ComponentModel componentModel, AbstractBeanDefinition beanDefinition);
}
}