/*
* 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.module.extension.internal.config.dsl;
import static org.mule.runtime.api.util.Preconditions.checkState;
import static org.mule.runtime.core.util.ClassUtils.withContextClassLoader;
import static org.mule.runtime.dsl.api.component.AttributeDefinition.Builder.fromChildConfiguration;
import static org.mule.runtime.dsl.api.component.AttributeDefinition.Builder.fromSimpleParameter;
import static org.mule.runtime.dsl.api.component.TypeDefinition.fromType;
import static org.mule.runtime.module.extension.internal.config.dsl.ExtensionXmlNamespaceInfo.EXTENSION_NAMESPACE;
import static org.mule.runtime.module.extension.internal.util.MuleExtensionUtils.getClassLoader;
import org.mule.metadata.api.model.ArrayType;
import org.mule.metadata.api.model.MetadataType;
import org.mule.metadata.api.model.ObjectType;
import org.mule.metadata.api.model.UnionType;
import org.mule.metadata.api.visitor.MetadataTypeVisitor;
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.api.meta.model.XmlDslModel;
import org.mule.runtime.api.meta.model.config.ConfigurationModel;
import org.mule.runtime.api.meta.model.connection.ConnectionProviderModel;
import org.mule.runtime.api.meta.model.operation.OperationModel;
import org.mule.runtime.api.meta.model.parameter.ParameterGroupModel;
import org.mule.runtime.api.meta.model.parameter.ParameterModel;
import org.mule.runtime.api.meta.model.source.SourceModel;
import org.mule.runtime.api.meta.model.util.IdempotentExtensionWalker;
import org.mule.runtime.config.spring.dsl.model.extension.xml.XmlExtensionModelProperty;
import org.mule.runtime.core.api.config.ConfigurationException;
import org.mule.runtime.core.api.extension.ExtensionManager;
import org.mule.runtime.dsl.api.component.ComponentBuildingDefinition;
import org.mule.runtime.dsl.api.component.ComponentBuildingDefinition.Builder;
import org.mule.runtime.dsl.api.component.ComponentBuildingDefinitionProvider;
import org.mule.runtime.extension.api.dsl.syntax.DslElementSyntax;
import org.mule.runtime.extension.api.dsl.syntax.resolver.DslSyntaxResolver;
import org.mule.runtime.extension.api.runtime.ExpirationPolicy;
import org.mule.runtime.module.extension.internal.config.ExtensionBuildingDefinitionProvider;
import org.mule.runtime.module.extension.internal.config.ExtensionConfig;
import org.mule.runtime.module.extension.internal.config.dsl.config.ConfigurationDefinitionParser;
import org.mule.runtime.module.extension.internal.config.dsl.connection.ConnectionProviderDefinitionParser;
import org.mule.runtime.module.extension.internal.config.dsl.infrastructure.DynamicConfigPolicyObjectFactory;
import org.mule.runtime.module.extension.internal.config.dsl.infrastructure.DynamicConfigurationExpiration;
import org.mule.runtime.module.extension.internal.config.dsl.infrastructure.DynamicConfigurationExpirationObjectFactory;
import org.mule.runtime.module.extension.internal.config.dsl.infrastructure.ExpirationPolicyObjectFactory;
import org.mule.runtime.module.extension.internal.config.dsl.infrastructure.ExtensionConfigObjectFactory;
import org.mule.runtime.module.extension.internal.config.dsl.operation.OperationDefinitionParser;
import org.mule.runtime.module.extension.internal.config.dsl.parameter.ObjectTypeParameterParser;
import org.mule.runtime.module.extension.internal.config.dsl.source.SourceDefinitionParser;
import org.mule.runtime.module.extension.internal.runtime.DynamicConfigPolicy;
import org.mule.runtime.module.extension.internal.util.IntrospectionUtils;
import com.google.common.collect.ImmutableList;
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Stream;
/**
* A generic {@link ComponentBuildingDefinitionProvider} which provides definitions capable of handling all extensions registered
* on the {@link ExtensionManager}.
* <p>
* It also provides static definitions for the config elements in the {@link ExtensionXmlNamespaceInfo#EXTENSION_NAMESPACE}
* namespace, which are used for cross extension configuration
*
* @since 4.0
*/
public class DefaultExtensionBuildingDefinitionProvider implements ExtensionBuildingDefinitionProvider {
private final List<ComponentBuildingDefinition> definitions = new LinkedList<>();
private Set<ExtensionModel> extensions;
public void setExtensions(Set<ExtensionModel> extensions) {
this.extensions = extensions;
}
/**
* Gets a hold on a {@link ExtensionManager} instance and generates the definitions.
*
* @throws java.lang.IllegalStateException if no extension manager could be found
*/
@Override
public void init() {
checkState(extensions != null, "extensions cannot be null");
extensions.stream()
.filter(this::shouldRegisterExtensionParser)
.forEach(this::registerExtensionParsers);
}
/**
* Taking an {@link ExtensionModel}, it will indicate whether it must register a {@link ComponentBuildingDefinitionProvider} or
* not.
*
* @param extensionModel to introspect
* @return true if a parser must be registered, false otherwise.
*/
private boolean shouldRegisterExtensionParser(ExtensionModel extensionModel) {
return !extensionModel.getModelProperty(XmlExtensionModelProperty.class).isPresent();
}
/**
* Returns the {@link ComponentBuildingDefinition}s for all the extensions plus for the elements in the
* {@link ExtensionXmlNamespaceInfo#EXTENSION_NAMESPACE}
*/
@Override
public List<ComponentBuildingDefinition> getComponentBuildingDefinitions() {
Builder baseDefinition =
new Builder().withNamespace(EXTENSION_NAMESPACE);
definitions.add(
baseDefinition.copy().withIdentifier("extensions-config").withTypeDefinition(fromType(ExtensionConfig.class))
.withObjectFactoryType(ExtensionConfigObjectFactory.class)
.withSetterParameterDefinition("dynamicConfigurationExpiration",
fromChildConfiguration(DynamicConfigurationExpiration.class).build())
.build());
definitions.add(baseDefinition.copy().withIdentifier("dynamic-configuration-expiration")
.withTypeDefinition(fromType(DynamicConfigurationExpiration.class))
.withObjectFactoryType(DynamicConfigurationExpirationObjectFactory.class)
.withConstructorParameterDefinition(fromSimpleParameter("frequency").build())
.withConstructorParameterDefinition(
fromSimpleParameter("timeUnit", value -> TimeUnit.valueOf((String) value)).build())
.build());
definitions.add(baseDefinition.copy().withIdentifier("dynamic-config-policy")
.withTypeDefinition(fromType(DynamicConfigPolicy.class))
.withObjectFactoryType(DynamicConfigPolicyObjectFactory.class)
.withSetterParameterDefinition("expirationPolicy", fromChildConfiguration(ExpirationPolicy.class).build())
.build());
definitions.add(baseDefinition.copy().withIdentifier("expiration-policy").withTypeDefinition(fromType(ExpirationPolicy.class))
.withObjectFactoryType(ExpirationPolicyObjectFactory.class)
.withSetterParameterDefinition("maxIdleTime", fromSimpleParameter("maxIdleTime").build())
.withSetterParameterDefinition("timeUnit",
fromSimpleParameter("timeUnit", value -> TimeUnit.valueOf((String) value))
.build())
.build());
return definitions;
}
private void registerExtensionParsers(ExtensionModel extensionModel) {
XmlDslModel xmlDslModel = extensionModel.getXmlDslModel();
final ExtensionParsingContext parsingContext = createParsingContext(extensionModel);
final Builder definitionBuilder =
new Builder().withNamespace(xmlDslModel.getPrefix());
final DslSyntaxResolver dslSyntaxResolver =
DslSyntaxResolver.getDefault(extensionModel, DslResolvingContext.getDefault(extensions));
final ClassLoader extensionClassLoader = getClassLoader(extensionModel);
withContextClassLoader(extensionClassLoader, () -> {
new IdempotentExtensionWalker() {
@Override
public void onConfiguration(ConfigurationModel model) {
parseWith(new ConfigurationDefinitionParser(definitionBuilder, extensionModel, model, dslSyntaxResolver,
parsingContext));
}
@Override
public void onOperation(OperationModel model) {
parseWith(new OperationDefinitionParser(definitionBuilder, extensionModel,
model, dslSyntaxResolver, parsingContext));
}
@Override
public void onConnectionProvider(ConnectionProviderModel model) {
parseWith(new ConnectionProviderDefinitionParser(definitionBuilder, model, extensionModel, dslSyntaxResolver,
parsingContext));
}
@Override
public void onSource(SourceModel model) {
parseWith(new SourceDefinitionParser(definitionBuilder, extensionModel, model, dslSyntaxResolver,
parsingContext));
}
@Override
protected void onParameter(ParameterGroupModel groupModel, ParameterModel model) {
registerTopLevelParameter(model.getType(), definitionBuilder, extensionClassLoader, dslSyntaxResolver, parsingContext);
}
}.walk(extensionModel);
registerExportedTypesTopLevelParsers(extensionModel, definitionBuilder, extensionClassLoader, dslSyntaxResolver,
parsingContext);
registerSubTypes(definitionBuilder, extensionClassLoader, dslSyntaxResolver, parsingContext);
});
}
private void registerSubTypes(MetadataType type, Builder definitionBuilder,
ClassLoader extensionClassLoader, DslSyntaxResolver dslSyntaxResolver,
ExtensionParsingContext parsingContext) {
type.accept(new MetadataTypeVisitor() {
@Override
public void visitUnion(UnionType unionType) {
unionType.getTypes().forEach(type -> type.accept(this));
}
@Override
public void visitArrayType(ArrayType arrayType) {
arrayType.getType().accept(this);
}
@Override
public void visitObject(ObjectType objectType) {
if (objectType.isOpen()) {
objectType.getOpenRestriction().get().accept(this);
} else {
parsingContext.getSubTypes(objectType)
.forEach(subtype -> registerTopLevelParameter(subtype, definitionBuilder, extensionClassLoader, dslSyntaxResolver,
parsingContext));
}
}
});
}
private void parseWith(ExtensionDefinitionParser parser) {
try {
definitions.addAll(parser.parse());
} catch (ConfigurationException e) {
throw new MuleRuntimeException(e);
}
}
private void registerTopLevelParameter(final MetadataType parameterType, Builder definitionBuilder,
ClassLoader extensionClassLoader, DslSyntaxResolver dslSyntaxResolver,
ExtensionParsingContext parsingContext) {
Optional<DslElementSyntax> dslElement = dslSyntaxResolver.resolve(parameterType);
if (!dslElement.isPresent() ||
parsingContext.isRegistered(dslElement.get().getElementName(), dslElement.get().getPrefix())) {
return;
}
parameterType.accept(new MetadataTypeVisitor() {
@Override
public void visitObject(ObjectType objectType) {
DslElementSyntax pojoDsl = dslElement.get();
if (pojoDsl.supportsTopLevelDeclaration() || (pojoDsl.supportsChildDeclaration() && pojoDsl.isWrapped()) ||
parsingContext.getAllSubTypes().contains(objectType)) {
parseWith(new ObjectTypeParameterParser(definitionBuilder, objectType, extensionClassLoader, dslSyntaxResolver,
parsingContext));
}
registerSubTypes(objectType, definitionBuilder, extensionClassLoader, dslSyntaxResolver, parsingContext);
}
@Override
public void visitArrayType(ArrayType arrayType) {
registerTopLevelParameter(arrayType.getType(), definitionBuilder.copy(), extensionClassLoader, dslSyntaxResolver,
parsingContext);
}
@Override
public void visitUnion(UnionType unionType) {
unionType.getTypes().forEach(type -> type.accept(this));
}
});
}
private void registerExportedTypesTopLevelParsers(ExtensionModel extensionModel,
Builder definitionBuilder,
ClassLoader extensionClassLoader, DslSyntaxResolver dslSyntaxResolver,
ExtensionParsingContext parsingContext) {
registerTopLevelParameters(extensionModel.getTypes().stream(),
definitionBuilder,
extensionClassLoader,
dslSyntaxResolver,
parsingContext);
}
private void registerSubTypes(Builder definitionBuilder,
ClassLoader extensionClassLoader, DslSyntaxResolver dslSyntaxResolver,
ExtensionParsingContext parsingContext) {
ImmutableList<MetadataType> mappedTypes = new ImmutableList.Builder<MetadataType>()
.addAll(parsingContext.getAllSubTypes())
.addAll(parsingContext.getAllBaseTypes())
.build();
registerTopLevelParameters(mappedTypes.stream(), definitionBuilder, extensionClassLoader,
dslSyntaxResolver,
parsingContext);
}
private void registerTopLevelParameters(Stream<? extends MetadataType> parameters, Builder definitionBuilder,
ClassLoader extensionClassLoader, DslSyntaxResolver dslSyntaxResolver,
ExtensionParsingContext parsingContext) {
parameters.filter(IntrospectionUtils::isInstantiable)
.forEach(subType -> registerTopLevelParameter(subType,
definitionBuilder,
extensionClassLoader,
dslSyntaxResolver,
parsingContext));
}
private ExtensionParsingContext createParsingContext(ExtensionModel extensionModel) {
return new ExtensionParsingContext(extensionModel);
}
@Override
public void setExtensionModels(Set<ExtensionModel> extensionModels) {
this.extensions = extensionModels;
}
}