/*
* 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.model;
import static com.google.common.base.Joiner.on;
import static java.lang.String.format;
import static java.lang.System.getProperties;
import static java.lang.Thread.currentThread;
import static java.util.Arrays.asList;
import static java.util.Collections.emptyMap;
import static java.util.Collections.emptySet;
import static java.util.Optional.empty;
import static java.util.Optional.of;
import static java.util.stream.Collectors.toList;
import static org.apache.commons.collections.CollectionUtils.disjunction;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.mule.runtime.api.component.ComponentIdentifier.builder;
import static org.mule.runtime.api.i18n.I18nMessageFactory.createStaticMessage;
import static org.mule.runtime.api.util.Preconditions.checkState;
import static org.mule.runtime.config.spring.dsl.processor.xml.XmlCustomAttributeHandler.from;
import static org.mule.runtime.config.spring.dsl.spring.BeanDefinitionFactory.SOURCE_TYPE;
import static org.mule.runtime.core.exception.Errors.Identifiers.ANY_IDENTIFIER;
import static org.mule.runtime.extension.api.util.NameUtils.hyphenize;
import static org.mule.runtime.extension.api.util.NameUtils.pluralize;
import static org.mule.runtime.internal.dsl.DslConstants.CORE_PREFIX;
import static org.mule.runtime.internal.dsl.DslConstants.DOMAIN_PREFIX;
import static org.mule.runtime.internal.dsl.DslConstants.EE_DOMAIN_PREFIX;
import org.mule.runtime.api.app.declaration.ArtifactDeclaration;
import org.mule.runtime.api.app.declaration.ElementDeclaration;
import org.mule.runtime.api.artifact.ArtifactProperties;
import org.mule.runtime.api.component.ComponentIdentifier;
import org.mule.runtime.api.dsl.DslResolvingContext;
import org.mule.runtime.api.exception.MuleRuntimeException;
import org.mule.runtime.api.meta.model.ExtensionModel;
import org.mule.runtime.config.spring.dsl.model.extension.xml.MacroExpansionModuleModel;
import org.mule.runtime.config.spring.dsl.processor.ArtifactConfig;
import org.mule.runtime.config.spring.dsl.processor.ConfigFile;
import org.mule.runtime.config.spring.dsl.processor.ObjectTypeVisitor;
import org.mule.runtime.core.api.config.ConfigurationException;
import org.mule.runtime.core.config.artifact.DefaultArtifactProperties;
import org.mule.runtime.dsl.api.component.ComponentBuildingDefinition;
import org.mule.runtime.dsl.api.component.ComponentBuildingDefinitionProvider;
import org.mule.runtime.dsl.api.component.config.ComponentConfiguration;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Properties;
import java.util.Set;
import java.util.function.Consumer;
import org.springframework.util.PropertyPlaceholderHelper;
import org.w3c.dom.Node;
/**
* An {@code ApplicationModel} holds a representation of all the artifact configuration using an abstract model to represent any
* configuration option.
* <p/>
* This model is represented by a set of {@link org.mule.runtime.config.spring.dsl.model.ComponentModel}. Each
* {@code ComponentModel} holds a piece of configuration and may have children {@code ComponentModel}s as defined in the artifact
* configuration.
* <p/>
* Once the set of {@code ComponentModel} gets created from the application
* {@link org.mule.runtime.config.spring.dsl.processor.ConfigFile}s the {@code ApplicationModel} executes a set of common
* validations dictated by the configuration semantics.
*
* @since 4.0
*/
public class ApplicationModel {
// TODO MULE-9692 move this logic elsewhere. This are here just for the language rules and those should be processed elsewhere.
public static final String MULE_ROOT_ELEMENT = "mule";
public static final String MULE_DOMAIN_ROOT_ELEMENT = "mule-domain";
public static final String POLICY_ROOT_ELEMENT = "policy";
public static final String ANNOTATIONS = "annotations";
public static final String ERROR_HANDLER = "error-handler";
public static final String ERROR_MAPPING = "error-mapping";
public static final String MAX_REDELIVERY_ATTEMPTS_ROLLBACK_ES_ATTRIBUTE = "maxRedeliveryAttempts";
public static final String WHEN_CHOICE_ES_ATTRIBUTE = "when";
public static final String TYPE_ES_ATTRIBUTE = "type";
public static final String EXCEPTION_STRATEGY_REFERENCE_ELEMENT = "exception-strategy";
public static final String SPRING_NAMESPACE = "spring";
public static final String SPRING_CONTEXT_NAMESPACE = "context";
public static final String PROPERTY_ELEMENT = "property";
public static final String NAME_ATTRIBUTE = "name";
public static final String REFERENCE_ATTRIBUTE = "ref";
public static final String VALUE_ATTRIBUTE = "value";
public static final String PROCESSOR_REFERENCE_ELEMENT = "processor";
public static final String TRANSFORMER_REFERENCE_ELEMENT = "transformer";
public static final String FILTER_REFERENCE_ELEMENT = "filter";
public static final String MESSAGE_FILTER_ELEMENT = "message-filter";
public static final String ANNOTATION_ELEMENT = "annotations";
public static final String FILTER_ELEMENT_SUFFIX = "-filter";
public static final String PROCESSING_STRATEGY_ATTRIBUTE = "processingStrategy";
public static final String PROCESSING_STRATEGY_FACTORY_ATTRIBUTE = "processingStrategyFactory";
public static final String QUEUE_STORE = "queue-store";
public static final String CONFIGURATION_ELEMENT = "configuration";
public static final String DATA_WEAVE = "weave";
public static final String CUSTOM_TRANSFORMER = "custom-transformer";
public static final String DESCRIPTION_ELEMENT = "description";
public static final String PROPERTIES_ELEMENT = "properties";
public static final String FLOW_ELEMENT = "flow";
public static final String FLOW_REF_ELEMENT = "flow-ref";
public static final String SUBFLOW_ELEMENT = "sub-flow";
private static final String MODULE_OPERATION_CHAIN_ELEMENT = "module-operation-chain";
public static final String REDELIVERY_POLICY_ELEMENT = "redelivery-policy";
// TODO MULE-9638 Remove once all bean definitions parsers where migrated
public static final String TEST_NAMESPACE = "test";
public static final String DOC_NAMESPACE = "doc";
public static final String SPRING_SECURITY_NAMESPACE = "ss";
public static final String MULE_SECURITY_NAMESPACE = "mule-ss";
public static final String MULE_XML_NAMESPACE = "mulexml";
public static final String PGP_NAMESPACE = "pgp";
public static final String XSL_NAMESPACE = "xsl";
public static final String TRANSPORT_NAMESPACE = "transports";
public static final String JMS_NAMESPACE = "jms";
public static final String VM_NAMESPACE = "vm";
public static final String HTTP_TRANSPORT_NAMESPACE = "http-transport";
public static final String BATCH_NAMESPACE = "batch";
public static final String PARSER_TEST_NAMESPACE = "parsers-test";
public static final String PROPERTY_PLACEHOLDER_ELEMENT = "property-placeholder";
public static final String GLOBAL_PROPERTY = "global-property";
public static final String SPRING_ENTRY_ELEMENT = "entry";
public static final String SPRING_LIST_ELEMENT = "list";
public static final String SPRING_MAP_ELEMENT = "map";
public static final String SPRING_VALUE_ELEMENT = "value";
public static final String PROTOTYPE_OBJECT_ELEMENT = "prototype-object";
public static final String SINGLETON_OBJECT_ELEMENT = "singleton-object";
public static final String INTERCEPTOR_STACK_ELEMENT = "interceptor-stack";
public static final ComponentIdentifier ERROR_HANDLER_IDENTIFIER =
builder().withNamespace(CORE_PREFIX).withName(ERROR_HANDLER).build();
public static final ComponentIdentifier EXCEPTION_STRATEGY_REFERENCE_IDENTIFIER =
builder().withNamespace(CORE_PREFIX).withName(EXCEPTION_STRATEGY_REFERENCE_ELEMENT)
.build();
public static final ComponentIdentifier ERROR_MAPPING_IDENTIFIER =
builder().withNamespace(CORE_PREFIX).withName(ERROR_MAPPING).build();
public static final ComponentIdentifier MULE_IDENTIFIER =
builder().withNamespace(CORE_PREFIX).withName(MULE_ROOT_ELEMENT).build();
public static final ComponentIdentifier MULE_DOMAIN_IDENTIFIER =
builder().withNamespace(DOMAIN_PREFIX).withName(MULE_DOMAIN_ROOT_ELEMENT).build();
public static final ComponentIdentifier MULE_EE_DOMAIN_IDENTIFIER =
builder().withNamespace(EE_DOMAIN_PREFIX).withName(MULE_DOMAIN_ROOT_ELEMENT).build();
public static final ComponentIdentifier POLICY_IDENTIFIER =
builder().withNamespace(POLICY_ROOT_ELEMENT).withName(POLICY_ROOT_ELEMENT).build();
public static final ComponentIdentifier SPRING_PROPERTY_IDENTIFIER =
builder().withNamespace(SPRING_NAMESPACE).withName(PROPERTY_ELEMENT).build();
public static final ComponentIdentifier MULE_PROPERTY_IDENTIFIER =
builder().withNamespace(CORE_PREFIX).withName(PROPERTY_ELEMENT).build();
public static final ComponentIdentifier MULE_PROPERTIES_IDENTIFIER =
builder().withNamespace(CORE_PREFIX).withName(PROPERTIES_ELEMENT).build();
public static final ComponentIdentifier ANNOTATIONS_ELEMENT_IDENTIFIER =
builder().withNamespace(CORE_PREFIX).withName(ANNOTATION_ELEMENT).build();
public static final ComponentIdentifier MESSAGE_FILTER_ELEMENT_IDENTIFIER =
builder().withNamespace(CORE_PREFIX).withName(MESSAGE_FILTER_ELEMENT).build();
public static final ComponentIdentifier PROCESSOR_IDENTIFIER =
builder().withNamespace(CORE_PREFIX).withName(PROCESSOR_REFERENCE_ELEMENT).build();
public static final ComponentIdentifier TRANSFORMER_IDENTIFIER =
builder().withNamespace(CORE_PREFIX).withName(TRANSFORMER_REFERENCE_ELEMENT).build();
public static final ComponentIdentifier QUEUE_STORE_IDENTIFIER =
builder().withNamespace(CORE_PREFIX).withName(QUEUE_STORE).build();
public static final ComponentIdentifier CONFIGURATION_IDENTIFIER =
builder().withNamespace(CORE_PREFIX).withName(CONFIGURATION_ELEMENT).build();
public static final ComponentIdentifier CUSTOM_TRANSFORMER_IDENTIFIER =
builder().withNamespace(CORE_PREFIX).withName(CUSTOM_TRANSFORMER).build();
public static final ComponentIdentifier SPRING_PROPERTY_PLACEHOLDER_IDENTIFIER =
builder().withNamespace(SPRING_CONTEXT_NAMESPACE).withName(PROPERTY_PLACEHOLDER_ELEMENT)
.build();
public static final ComponentIdentifier DOC_DESCRIPTION_IDENTIFIER =
builder().withNamespace(DOC_NAMESPACE).withName(DESCRIPTION_ELEMENT).build();
public static final ComponentIdentifier DESCRIPTION_IDENTIFIER =
builder().withNamespace(CORE_PREFIX).withName(DESCRIPTION_ELEMENT).build();
public static final ComponentIdentifier ANNOTATIONS_IDENTIFIER =
builder().withNamespace(CORE_PREFIX).withName(ANNOTATIONS).build();
public static final ComponentIdentifier SPRING_ENTRY_IDENTIFIER =
builder().withNamespace(SPRING_NAMESPACE).withName(SPRING_ENTRY_ELEMENT).build();
public static final ComponentIdentifier SPRING_LIST_IDENTIFIER =
builder().withNamespace(SPRING_NAMESPACE).withName(SPRING_LIST_ELEMENT).build();
public static final ComponentIdentifier SPRING_MAP_IDENTIFIER =
builder().withNamespace(SPRING_NAMESPACE).withName(SPRING_MAP_ELEMENT).build();
public static final ComponentIdentifier SPRING_VALUE_IDENTIFIER =
builder().withNamespace(SPRING_NAMESPACE).withName(SPRING_VALUE_ELEMENT).build();
public static final ComponentIdentifier PROTOTYPE_OBJECT_IDENTIFIER =
builder().withNamespace(CORE_PREFIX).withName(PROTOTYPE_OBJECT_ELEMENT).build();
public static final ComponentIdentifier SINGLETON_OBJECT_IDENTIFIER =
builder().withNamespace(CORE_PREFIX).withName(SINGLETON_OBJECT_ELEMENT).build();
public static final ComponentIdentifier INTERCEPTOR_STACK_IDENTIFIER =
builder().withNamespace(CORE_PREFIX).withName(INTERCEPTOR_STACK_ELEMENT).build();
public static final ComponentIdentifier FLOW_IDENTIFIER =
builder().withNamespace(CORE_PREFIX).withName(FLOW_ELEMENT).build();
public static final ComponentIdentifier FLOW_REF_IDENTIFIER =
builder().withNamespace(CORE_PREFIX).withName(FLOW_REF_ELEMENT).build();
public static final ComponentIdentifier SUBFLOW_IDENTIFIER =
builder().withNamespace(CORE_PREFIX).withName(SUBFLOW_ELEMENT).build();
public static final ComponentIdentifier REDELIVERY_POLICY_IDENTIFIER =
builder().withNamespace(CORE_PREFIX).withName(REDELIVERY_POLICY_ELEMENT).build();
public static final ComponentIdentifier GLOBAL_PROPERTY_IDENTIFIER =
builder().withNamespace(CORE_PREFIX).withName(GLOBAL_PROPERTY).build();
public static final ComponentIdentifier MODULE_OPERATION_CHAIN =
builder().withNamespace(CORE_PREFIX).withName(MODULE_OPERATION_CHAIN_ELEMENT).build();
private static ImmutableSet<ComponentIdentifier> ignoredNameValidationComponentList =
ImmutableSet.<ComponentIdentifier>builder()
.add(builder().withNamespace(MULE_ROOT_ELEMENT).withName("flow-ref").build())
.add(builder().withNamespace(MULE_ROOT_ELEMENT).withName("alias").build())
.add(builder().withNamespace(MULE_ROOT_ELEMENT).withName("password-encryption-strategy")
.build())
.add(builder().withNamespace(MULE_ROOT_ELEMENT).withName("custom-security-provider")
.build())
.add(builder().withNamespace(MULE_ROOT_ELEMENT).withName("custom-encryption-strategy")
.build())
.add(builder().withNamespace(MULE_ROOT_ELEMENT)
.withName("secret-key-encryption-strategy")
.build())
.add(builder().withNamespace(MULE_ROOT_ELEMENT).withName("import").build())
.add(builder().withNamespace(MULE_ROOT_ELEMENT)
.withName("string-to-byte-array-transformer")
.build())
.add(builder().withNamespace(MULE_ROOT_ELEMENT).withName("append-string-transformer")
.build())
.add(builder().withNamespace(MULE_ROOT_ELEMENT).withName("security-manager").build())
.add(builder().withNamespace(TEST_NAMESPACE).withName("queue").build())
.add(builder().withNamespace(TEST_NAMESPACE).withName("invocation-counter").build())
.add(builder().withNamespace(SPRING_NAMESPACE).withName("property").build())
.add(builder().withNamespace(SPRING_NAMESPACE).withName("bean").build())
.add(builder().withNamespace(SPRING_SECURITY_NAMESPACE).withName("user").build())
.add(builder().withNamespace(MULE_SECURITY_NAMESPACE)
.withName("delegate-security-provider")
.build())
.add(builder().withNamespace(MULE_SECURITY_NAMESPACE).withName("security-manager")
.build())
.add(builder().withNamespace(MULE_XML_NAMESPACE).withName("xslt-transformer").build())
.add(builder().withNamespace(MULE_XML_NAMESPACE).withName("alias").build())
.add(builder().withNamespace(PGP_NAMESPACE).withName("security-provider").build())
.add(builder().withNamespace(PGP_NAMESPACE).withName("keybased-encryption-strategy")
.build())
.add(builder().withNamespace(XSL_NAMESPACE).withName("param").build())
.add(builder().withNamespace(XSL_NAMESPACE).withName("attribute").build())
.add(builder().withNamespace(XSL_NAMESPACE).withName("element").build())
.add(builder().withNamespace(TRANSPORT_NAMESPACE).withName("inbound-endpoint").build())
.add(builder().withNamespace(TRANSPORT_NAMESPACE).withName("outbound-endpoint").build())
.add(builder().withNamespace(JMS_NAMESPACE).withName("inbound-endpoint").build())
.add(builder().withNamespace(VM_NAMESPACE).withName("inbound-endpoint").build())
.add(builder().withNamespace(HTTP_TRANSPORT_NAMESPACE).withName("inbound-endpoint").build())
.add(builder().withNamespace(HTTP_TRANSPORT_NAMESPACE).withName("set-cookie").build())
.add(builder().withNamespace(HTTP_TRANSPORT_NAMESPACE).withName("header").build())
.add(builder().withNamespace(HTTP_TRANSPORT_NAMESPACE)
.withName("http-response-to-object-transformer")
.build())
.add(builder().withNamespace(HTTP_TRANSPORT_NAMESPACE)
.withName("http-response-to-string-transformer")
.build())
.add(builder().withNamespace(HTTP_TRANSPORT_NAMESPACE)
.withName("message-to-http-response-transformer")
.build())
.add(builder().withNamespace(HTTP_TRANSPORT_NAMESPACE)
.withName("object-to-http-request-transformer")
.build())
.add(builder().withNamespace(BATCH_NAMESPACE).withName("step").build())
.add(builder().withNamespace(BATCH_NAMESPACE).withName("execute").build())
.add(builder().withNamespace(PARSER_TEST_NAMESPACE).withName("child").build())
.add(builder().withNamespace(PARSER_TEST_NAMESPACE).withName("kid").build())
.add(builder().withNamespace(DATA_WEAVE).withName("reader-property").build())
.build();
private final Optional<ComponentBuildingDefinitionRegistry> componentBuildingDefinitionRegistry;
private List<ComponentModel> muleComponentModels = new LinkedList<>();
private List<ComponentModel> springComponentModels = new LinkedList<>();
private PropertyPlaceholderHelper propertyPlaceholderHelper = new PropertyPlaceholderHelper("${", "}");
private ArtifactProperties artifactProperties;
/**
* Creates an {code ApplicationModel} from a {@link ArtifactConfig}.
* <p/>
* A set of validations are applied that may make creation fail.
*
* @param artifactConfig the mule artifact configuration content.
* @param artifactDeclaration an {@link ArtifactDeclaration}
* @throws Exception when the application configuration has semantic errors.
*/
public ApplicationModel(ArtifactConfig artifactConfig, ArtifactDeclaration artifactDeclaration) throws Exception {
this(artifactConfig, artifactDeclaration, emptySet(), of(new ComponentBuildingDefinitionRegistry()));
}
/**
* Creates an {code ApplicationModel} from a {@link ArtifactConfig}.
* <p/>
* A set of validations are applied that may make creation fail.
*
* @param artifactConfig the mule artifact configuration content.
* @param artifactDeclaration an {@link ArtifactDeclaration}
* @param extensionModels Set of {@link ExtensionModel extensionModels} that will be used to type componentModels
* @param componentBuildingDefinitionRegistry an optional {@link ComponentBuildingDefinitionRegistry} used to correlate items in
* this model to their definitions
* @throws Exception when the application configuration has semantic errors.
*/
// TODO: MULE-9638 remove this optional
public ApplicationModel(ArtifactConfig artifactConfig, ArtifactDeclaration artifactDeclaration,
Set<ExtensionModel> extensionModels,
Optional<ComponentBuildingDefinitionRegistry> componentBuildingDefinitionRegistry)
throws Exception {
this.componentBuildingDefinitionRegistry = componentBuildingDefinitionRegistry;
configurePropertyPlaceholderResolver(artifactConfig);
convertConfigFileToComponentModel(artifactConfig);
convertArtifactDeclarationToComponentModel(extensionModels, artifactDeclaration);
validateModel(componentBuildingDefinitionRegistry);
createEffectiveModel();
expandModules(extensionModels);
resolveComponentTypes();
executeOnEveryMuleComponentTree(new ComponentLocationVisitor());
}
/**
* Resolves the types of each component model when possible.
*/
public void resolveComponentTypes() {
checkState(componentBuildingDefinitionRegistry.isPresent(),
"ApplicationModel was created without a " + ComponentBuildingDefinitionProvider.class.getName());
executeOnEveryComponentTree(componentModel -> {
Optional<ComponentBuildingDefinition> buildingDefinition =
componentBuildingDefinitionRegistry.get().getBuildingDefinition(componentModel.getIdentifier());
buildingDefinition.ifPresent(definition -> {
ObjectTypeVisitor typeDefinitionVisitor = new ObjectTypeVisitor(componentModel);
definition.getTypeDefinition().visit(typeDefinitionVisitor);
componentModel.setType(typeDefinitionVisitor.getType());
});
});
}
/**
* Creates the effective application model to be used to generate the runtime objects of the mule configuration.
*/
private void createEffectiveModel() {
processSourcesRedeliveryPolicy();
}
/**
* Process from any message source the redelivery-policy to make it part of the final pipeline.
*/
private void processSourcesRedeliveryPolicy() {
executeOnEveryFlow(flowComponentModel -> {
if (!flowComponentModel.getInnerComponents().isEmpty()) {
ComponentModel possibleSourceComponent = flowComponentModel.getInnerComponents().get(0);
possibleSourceComponent.getInnerComponents().stream()
.filter(childComponent -> childComponent.getIdentifier().equals(REDELIVERY_POLICY_IDENTIFIER))
.findAny()
.ifPresent(redeliveryPolicyComponentModel -> {
possibleSourceComponent.getInnerComponents().remove(redeliveryPolicyComponentModel);
flowComponentModel.getInnerComponents().add(1, redeliveryPolicyComponentModel);
});
}
});
}
private void convertArtifactDeclarationToComponentModel(Set<ExtensionModel> extensionModels,
ArtifactDeclaration artifactDeclaration) {
if (artifactDeclaration != null && !extensionModels.isEmpty()) {
DslElementModelFactory elementFactory = DslElementModelFactory
.getDefault(DslResolvingContext.getDefault(extensionModels));
ComponentModel rootComponent = new ComponentModel.Builder()
.setIdentifier(ComponentIdentifier.builder().withNamespace(CORE_PREFIX).withName(CORE_PREFIX).build()).build();
this.muleComponentModels.add(rootComponent);
artifactDeclaration.getGlobalElements().stream()
.map(e -> elementFactory.create((ElementDeclaration) e))
.filter(Optional::isPresent)
.map(e -> e.get().getConfiguration())
.forEach(config -> config
.ifPresent(c -> {
ComponentModel componentModel = convertComponentConfiguration(c, true);
componentModel.setParent(rootComponent);
rootComponent.getInnerComponents().add(componentModel);
}));
}
}
private ComponentModel convertComponentConfiguration(ComponentConfiguration componentConfiguration, boolean isRoot) {
ComponentModel.Builder builder = new ComponentModel.Builder()
.setIdentifier(componentConfiguration.getIdentifier());
if (isRoot) {
builder.markAsRootComponent();
}
for (Map.Entry<String, String> parameter : componentConfiguration.getParameters().entrySet()) {
builder.addParameter(parameter.getKey(), parameter.getValue(), false);
}
for (ComponentConfiguration childComponentConfiguration : componentConfiguration.getNestedComponents()) {
builder.addChildComponentModel(convertComponentConfiguration(childComponentConfiguration, false));
}
componentConfiguration.getValue().ifPresent(builder::setTextContent);
ComponentModel componentModel = builder.build();
for (ComponentModel childComponent : componentModel.getInnerComponents()) {
childComponent.setParent(componentModel);
}
return componentModel;
}
private void configurePropertyPlaceholderResolver(ArtifactConfig artifactConfig) {
// TODO MULE-9825: a new mechanism for property placeholders need to be defined
final List<String> locations = new ArrayList<>();
final Map<String, String> globalProperties = new HashMap<>();
artifactConfig.getConfigFiles().stream().forEach(configFile -> {
configFile.getConfigLines().get(0).getChildren().stream().forEach(configLine -> {
if (GLOBAL_PROPERTY.equals(configLine.getIdentifier())) {
globalProperties.put(configLine.getConfigAttributes().get("name").getValue(),
configLine.getConfigAttributes().get("value").getValue());
} else if (PROPERTY_PLACEHOLDER_ELEMENT.equals(configLine.getIdentifier())) {
String locationValue = configLine.getConfigAttributes().get("location").getValue();
locationValue = propertyPlaceholderHelper.replacePlaceholders(locationValue, getProperties());
locationValue = locationValue.replace("classpath:/", "");
locations.add(locationValue);
}
});
});
ImmutableMap.Builder<Object, Object> springProperties = ImmutableMap.builder();
for (String propertyFileLocation : locations) {
Properties properties = new Properties();
try (InputStream propertiesFileInputStream =
currentThread().getContextClassLoader().getResourceAsStream(propertyFileLocation)) {
properties.load(propertiesFileInputStream);
springProperties.putAll(properties);
} catch (IOException e) {
throw new MuleRuntimeException(e);
}
}
Map<String, String> applicationProperties = artifactConfig.getApplicationProperties();
artifactProperties = new DefaultArtifactProperties(ImmutableMap.builder().putAll(globalProperties).build(), springProperties
.build(), applicationProperties != null ? ImmutableMap.builder().putAll(applicationProperties).build() : emptyMap());
}
/**
* @param element element which was the source of the {@code ComponentModel}.
* @return the {@code ComponentModel} created from the element.
*/
// TODO MULE-9638: remove once the old parsing mechanism is not needed anymore
public ComponentModel findComponentDefinitionModel(Node element) {
return innerFindComponentDefinitionModel(element, muleComponentModels);
}
public Optional<ComponentModel> findComponentDefinitionModel(ComponentIdentifier componentIdentifier) {
if (muleComponentModels.isEmpty()) {
return empty();
}
return muleComponentModels.get(0).getInnerComponents().stream().filter(ComponentModel::isRoot)
.filter(componentModel -> componentModel.getIdentifier().equals(componentIdentifier)).findFirst();
}
private void convertConfigFileToComponentModel(ArtifactConfig artifactConfig) {
List<ConfigFile> configFiles = artifactConfig.getConfigFiles();
ComponentModelReader componentModelReader = new ComponentModelReader(artifactProperties);
configFiles.stream().forEach(configFile -> {
ComponentModel componentModel =
componentModelReader.extractComponentDefinitionModel(configFile.getConfigLines().get(0), configFile.getFilename());
if (isMuleConfigFile(configFile)) {
if (muleComponentModels.isEmpty()) {
muleComponentModels.add(componentModel);
} else {
// Only one componentModel as Root should be set, therefore componentModel is merged
final ComponentModel rootComponentModel = muleComponentModels.get(0);
muleComponentModels.set(0, new ComponentModel.Builder(rootComponentModel).merge(componentModel).build());
}
} else {
springComponentModels.add(componentModel);
}
});
}
private boolean isMuleConfigFile(final ConfigFile configFile) {
if (configFile.getConfigLines().isEmpty()) {
return false;
}
return !isSpringFile(configFile);
}
private boolean isSpringFile(ConfigFile configFile) {
return SPRING_NAMESPACE.equals(configFile.getConfigLines().get(0).getNamespace());
}
public boolean hasSpringConfig() {
return !springComponentModels.isEmpty();
}
private void validateModel(Optional<ComponentBuildingDefinitionRegistry> componentBuildingDefinitionRegistry)
throws ConfigurationException {
if (muleComponentModels.isEmpty() || !isMuleConfigurationFile()) {
return;
}
// TODO MULE-9692 all this validations will be moved to an entity that does the validation and allows to aggregate all
// validations instead of failing fast.
validateNameIsNotRepeated();
validateNameIsOnlyOnTopLevelElements();
validateErrorMappings();
validateExceptionStrategyWhenAttributeIsOnlyPresentInsideChoice();
validateErrorHandlerStructure();
validateParameterAndChildForSameAttributeAreNotDefinedTogether();
if (componentBuildingDefinitionRegistry.isPresent()) {
validateNamedTopLevelElementsHaveName(componentBuildingDefinitionRegistry.get());
}
}
private void validateParameterAndChildForSameAttributeAreNotDefinedTogether() {
executeOnEveryMuleComponentTree(componentModel -> {
for (String parameterName : componentModel.getParameters().keySet()) {
if (!componentModel.isParameterValueProvidedBySchema(parameterName)) {
String mapChildName = hyphenize(pluralize(parameterName));
String listOrPojoChildName = hyphenize(parameterName);
Optional<ComponentModel> childOptional =
findRelatedChildForParameter(componentModel.getInnerComponents(), mapChildName, listOrPojoChildName);
if (childOptional.isPresent() && !childOptional.get().getIdentifier().equals(SPRING_PROPERTY_IDENTIFIER)) {
throw new MuleRuntimeException(createStaticMessage(
format("Component %s has a child element %s which is used for the same purpose of the configuration parameter %s. "
+ "Only one must be used.", componentModel.getIdentifier(),
childOptional.get().getIdentifier(),
parameterName)));
}
}
}
});
}
private Optional<ComponentModel> findRelatedChildForParameter(List<ComponentModel> chilrenComponents, String... possibleNames) {
Set<String> possibleNamesSet = new HashSet<>(asList(possibleNames));
for (ComponentModel childrenComponent : chilrenComponents) {
if (possibleNamesSet.contains(childrenComponent.getIdentifier().getName())) {
return of(childrenComponent);
}
}
return empty();
}
private void validateNameIsNotRepeated() {
Map<String, ComponentModel> existingObjectsWithName = new HashMap<>();
executeOnEveryMuleComponentTree(componentModel -> {
String nameAttributeValue = componentModel.getNameAttribute();
if (nameAttributeValue != null && !ignoredNameValidationComponentList.contains(componentModel.getIdentifier())) {
if (existingObjectsWithName.containsKey(nameAttributeValue)) {
throw new MuleRuntimeException(createStaticMessage(
"Two configuration elements have been defined with the same global name. Global name [%s] must be unique. Clashing components are %s and %s",
nameAttributeValue,
existingObjectsWithName.get(nameAttributeValue).getIdentifier(),
componentModel.getIdentifier()));
}
existingObjectsWithName.put(nameAttributeValue, componentModel);
}
});
}
private boolean isMuleConfigurationFile() {
final ComponentIdentifier rootIdentifier = muleComponentModels.get(0).getIdentifier();
return rootIdentifier.equals(MULE_IDENTIFIER)
|| rootIdentifier.equals(MULE_DOMAIN_IDENTIFIER)
|| rootIdentifier.equals(MULE_EE_DOMAIN_IDENTIFIER);
}
private void validateErrorMappings() {
executeOnEveryComponentTree(componentModel -> {
List<ComponentModel> errorMappings = componentModel.getInnerComponents().stream()
.filter(c -> c.getIdentifier().equals(ERROR_MAPPING_IDENTIFIER)).collect(toList());
if (!errorMappings.isEmpty()) {
List<ComponentModel> anyMappings = errorMappings.stream().filter(this::isErrorMappingWithSourceAny).collect(toList());
if (anyMappings.size() > 1) {
throw new MuleRuntimeException(createStaticMessage("Only one mapping for ANY or an empty source type is allowed."));
} else if (anyMappings.size() == 1 && !isErrorMappingWithSourceAny(errorMappings.get(errorMappings.size() - 1))) {
throw new MuleRuntimeException(
createStaticMessage("Only the last error mapping can have ANY or an empty source type."));
}
List<String> sources = errorMappings.stream().map(model -> model.getParameters().get(SOURCE_TYPE)).collect(toList());
List<String> distinctSources = sources.stream().distinct().collect(toList());
if (sources.size() != distinctSources.size()) {
throw new MuleRuntimeException(
createStaticMessage(format("Repeated source types are not allowed. Offending types are %s.",
on(", ").join(disjunction(sources, distinctSources)))));
}
}
});
}
private boolean isErrorMappingWithSourceAny(ComponentModel model) {
String sourceType = model.getParameters().get(SOURCE_TYPE);
return sourceType == null || sourceType.equals(ANY_IDENTIFIER);
}
private void validateErrorHandlerStructure() {
executeOnEveryMuleComponentTree(component -> {
if (component.getIdentifier().equals(ERROR_HANDLER_IDENTIFIER)) {
validateExceptionStrategiesHaveWhenAttribute(component);
validateNoMoreThanOneRollbackExceptionStrategyWithRedelivery(component);
}
});
}
private void validateNoMoreThanOneRollbackExceptionStrategyWithRedelivery(ComponentModel component) {
if (component.getInnerComponents().stream().filter(exceptionStrategyComponent -> {
return exceptionStrategyComponent.getParameters().get(MAX_REDELIVERY_ATTEMPTS_ROLLBACK_ES_ATTRIBUTE) != null;
}).count() > 1) {
throw new MuleRuntimeException(createStaticMessage(
"Only one on-error-propagate within a error-handler can handle message redelivery. Remove one of the maxRedeliveryAttempts attributes"));
}
}
private void validateExceptionStrategiesHaveWhenAttribute(ComponentModel component) {
List<ComponentModel> innerComponents = component.getInnerComponents();
for (int i = 0; i < innerComponents.size() - 1; i++) {
Map<String, String> parameters = innerComponents.get(i).getParameters();
if (parameters.get(WHEN_CHOICE_ES_ATTRIBUTE) == null && parameters.get(TYPE_ES_ATTRIBUTE) == null) {
throw new MuleRuntimeException(createStaticMessage(
"Every handler (except for the last one) within an error-handler must specify the when or type attribute"));
}
}
}
private void validateExceptionStrategyWhenAttributeIsOnlyPresentInsideChoice() {
executeOnEveryMuleComponentTree(component -> {
if (component.getIdentifier().getName().endsWith(EXCEPTION_STRATEGY_REFERENCE_ELEMENT)) {
Node componentNode = from(component).getNode();
if (component.getParameters().get(WHEN_CHOICE_ES_ATTRIBUTE) != null
&& !componentNode.getParentNode().getLocalName().equals(ERROR_HANDLER)
&& !componentNode.getParentNode().getLocalName().equals(MULE_ROOT_ELEMENT)) {
throw new MuleRuntimeException(
createStaticMessage("Only handlers within an error-handler can have when attribute specified"));
}
}
});
}
private void validateNameIsOnlyOnTopLevelElements() throws ConfigurationException {
try {
List<ComponentModel> topLevelComponents = muleComponentModels.get(0).getInnerComponents();
topLevelComponents.stream().filter(this::isMuleComponent).forEach(topLevelComponent -> {
topLevelComponent.getInnerComponents().stream().filter(this::isMuleComponent).forEach((topLevelComponentChild -> {
executeOnComponentTree(topLevelComponentChild, (component) -> {
if (component.getNameAttribute() != null && !ignoredNameValidationComponentList.contains(component.getIdentifier())) {
throw new MuleRuntimeException(createStaticMessage(
"Only top level elements can have a name attribute. Component %s has attribute name with value %s",
component.getIdentifier(), component.getNameAttribute()));
}
}, true);
}));
});
} catch (Exception e) {
throw new ConfigurationException(e);
}
}
private void validateNamedTopLevelElementsHaveName(ComponentBuildingDefinitionRegistry componentBuildingDefinitionRegistry)
throws ConfigurationException {
try {
List<ComponentModel> topLevelComponents = muleComponentModels.get(0).getInnerComponents();
topLevelComponents.stream().filter(this::isMuleComponent).forEach(topLevelComponent -> {
final ComponentIdentifier identifier = topLevelComponent.getIdentifier();
componentBuildingDefinitionRegistry.getBuildingDefinition(identifier).filter(ComponentBuildingDefinition::isNamed)
.ifPresent(buildingDefinition -> {
if (isBlank(topLevelComponent.getNameAttribute())) {
throw new MuleRuntimeException(createStaticMessage(format("Global element %s:%s does not provide a name attribute.",
identifier.getNamespace(), identifier.getName())));
}
});
});
} catch (Exception e) {
throw new ConfigurationException(e);
}
}
private boolean isMuleComponent(ComponentModel componentModel) {
return !componentModel.getIdentifier().getNamespace().equals(ApplicationModel.SPRING_NAMESPACE);
}
public void executeOnEveryComponentTree(final Consumer<ComponentModel> task) {
for (ComponentModel componentModel : muleComponentModels) {
executeOnComponentTree(componentModel, task, false);
}
}
public void executeOnEveryMuleComponentTree(final Consumer<ComponentModel> task) {
for (ComponentModel componentModel : muleComponentModels) {
executeOnComponentTree(componentModel, task, true);
}
}
public void executeOnEveryFlow(final Consumer<ComponentModel> task) {
for (ComponentModel muleComponentModel : muleComponentModels) {
for (ComponentModel componentModel : muleComponentModel.getInnerComponents()) {
if (ApplicationModel.FLOW_IDENTIFIER.equals(componentModel.getIdentifier())) {
task.accept(componentModel);
}
}
}
}
private void executeOnComponentTree(final ComponentModel component, final Consumer<ComponentModel> task,
boolean avoidSpringElements)
throws MuleRuntimeException {
if (component.getIdentifier().getNamespace().equals(SPRING_NAMESPACE) && avoidSpringElements) {
// TODO MULE-9648: for now do no process beans inside spring
return;
}
task.accept(component);
component.getInnerComponents().forEach((innerComponent) -> {
executeOnComponentTree(innerComponent, task, avoidSpringElements);
});
}
private ComponentModel innerFindComponentDefinitionModel(Node element, List<ComponentModel> componentModels) {
for (ComponentModel componentModel : componentModels) {
if (from(componentModel).getNode().equals(element)) {
return componentModel;
}
ComponentModel childComponentModel = innerFindComponentDefinitionModel(element, componentModel.getInnerComponents());
if (childComponentModel != null) {
return childComponentModel;
}
}
return null;
}
/**
* TODO MULE-9688: When the model it's made immutable we will also provide the parent component for navigation and this will not
* be needed anymore.
*
* @return the root component model
*/
public ComponentModel getRootComponentModel() {
return muleComponentModels.get(0);
}
/**
* Find a named component configuration.
*
* @param name the expected value for the name attribute configuration.
* @return the component if present, if not, an empty {@link Optional}
*/
public Optional<ComponentModel> findNamedComponent(String name) {
Optional<ComponentModel> requestedComponentModelOptional = empty();
for (ComponentModel muleComponentModel : muleComponentModels) {
requestedComponentModelOptional = muleComponentModel.getInnerComponents().stream()
.filter(componentModel -> name.equals(componentModel.getNameAttribute()))
.findAny();
if (requestedComponentModelOptional.isPresent()) {
break;
}
}
return requestedComponentModelOptional;
}
/**
* Find a named component configuration.
*
* @param name the expected value for the name attribute configuration.
* @return the component if present, if not, an empty {@link Optional}
*/
// TODO MULE-11355: Make the ComponentModel haven an ComponentConfiguration internally
public Optional<ComponentConfiguration> findNamedElement(String name) {
Optional<ComponentConfiguration> requestedElement = empty();
for (ComponentModel muleComponentModel : muleComponentModels) {
requestedElement = muleComponentModel.getInnerComponents().stream()
.filter(componentModel -> name.equals(componentModel.getNameAttribute()))
.map(ComponentModel::getConfiguration)
.findAny();
if (requestedElement.isPresent()) {
break;
}
}
return requestedElement;
}
/**
* We force the current instance of {@link ApplicationModel} to be highly cohesive with {@link MacroExpansionModuleModel} as
* it's responsibility of this object to properly initialize and expand every global element/operation into the concrete set of
* message processors
*
* @param extensionModels Set of {@link ExtensionModel extensionModels} that will be used to check if the element has to be
* expanded.
*/
private void expandModules(Set<ExtensionModel> extensionModels) {
new MacroExpansionModuleModel(this, extensionModels).expand();
}
/**
* @return the configured properties for the artifact.
*/
public ArtifactProperties getArtifactProperties() {
return artifactProperties;
}
}