/* * 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 java.lang.String.format; import static java.time.Instant.ofEpochMilli; import static java.util.Collections.emptySet; import static java.util.Optional.ofNullable; import static java.util.stream.Collectors.toList; import static org.mule.metadata.api.utils.MetadataTypeUtils.getDefaultValue; import static org.mule.metadata.java.api.utils.JavaTypeUtils.getType; import static org.mule.runtime.api.i18n.I18nMessageFactory.createStaticMessage; import static org.mule.runtime.api.meta.ExpressionSupport.NOT_SUPPORTED; import static org.mule.runtime.api.meta.ExpressionSupport.REQUIRED; import static org.mule.runtime.dsl.api.component.AttributeDefinition.Builder.fromChildCollectionConfiguration; import static org.mule.runtime.dsl.api.component.AttributeDefinition.Builder.fromChildConfiguration; import static org.mule.runtime.dsl.api.component.AttributeDefinition.Builder.fromChildMapConfiguration; import static org.mule.runtime.dsl.api.component.AttributeDefinition.Builder.fromFixedValue; import static org.mule.runtime.dsl.api.component.AttributeDefinition.Builder.fromMultipleDefinitions; import static org.mule.runtime.dsl.api.component.AttributeDefinition.Builder.fromSimpleParameter; import static org.mule.runtime.dsl.api.component.KeyAttributeDefinitionPair.newBuilder; import static org.mule.runtime.dsl.api.component.TypeDefinition.fromMapEntryType; import static org.mule.runtime.dsl.api.component.TypeDefinition.fromType; import static org.mule.runtime.extension.api.declaration.type.TypeUtils.getExpressionSupport; import static org.mule.runtime.extension.api.util.ExtensionMetadataTypeUtils.getId; import static org.mule.runtime.extension.api.util.ExtensionMetadataTypeUtils.isFlattenedParameterGroup; import static org.mule.runtime.extension.api.util.ExtensionMetadataTypeUtils.isMap; import static org.mule.runtime.extension.api.util.ExtensionModelUtils.isContent; import static org.mule.runtime.extension.api.util.NameUtils.hyphenize; import static org.mule.runtime.internal.dsl.DslConstants.VALUE_ATTRIBUTE_NAME; import static org.mule.runtime.module.extension.internal.loader.java.type.InfrastructureTypeMapping.getNameMap; import static org.mule.runtime.module.extension.internal.util.IntrospectionUtils.getContainerName; import static org.mule.runtime.module.extension.internal.util.IntrospectionUtils.getMemberName; import org.mule.metadata.api.ClassTypeLoader; import org.mule.metadata.api.model.ArrayType; import org.mule.metadata.api.model.DateTimeType; import org.mule.metadata.api.model.DateType; import org.mule.metadata.api.model.MetadataType; import org.mule.metadata.api.model.ObjectFieldType; import org.mule.metadata.api.model.ObjectType; import org.mule.metadata.api.model.StringType; import org.mule.metadata.api.visitor.BasicTypeMetadataVisitor; import org.mule.metadata.api.visitor.MetadataTypeVisitor; import org.mule.runtime.api.config.PoolingProfile; import org.mule.runtime.api.exception.MuleRuntimeException; import org.mule.runtime.api.meta.ExpressionSupport; import org.mule.runtime.api.meta.model.ExtensionModel; import org.mule.runtime.api.meta.model.ModelProperty; 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.parameter.ParameterizedModel; import org.mule.runtime.api.tls.TlsContextFactory; import org.mule.runtime.api.util.Reference; import org.mule.runtime.core.api.NestedProcessor; import org.mule.runtime.core.api.config.ConfigurationException; import org.mule.runtime.core.api.processor.Processor; import org.mule.runtime.core.api.retry.RetryPolicyTemplate; import org.mule.runtime.core.util.TemplateParser; import org.mule.runtime.dsl.api.component.AttributeDefinition; import org.mule.runtime.dsl.api.component.ComponentBuildingDefinition; import org.mule.runtime.dsl.api.component.ComponentBuildingDefinition.Builder; import org.mule.runtime.dsl.api.component.KeyAttributeDefinitionPair; import org.mule.runtime.dsl.api.component.TypeConverter; import org.mule.runtime.extension.api.declaration.type.ExtensionsTypeLoaderFactory; import org.mule.runtime.extension.api.declaration.type.TypeUtils; import org.mule.runtime.extension.api.dsl.syntax.DslElementSyntax; import org.mule.runtime.extension.api.dsl.syntax.resolver.DslSyntaxResolver; import org.mule.runtime.extension.internal.property.InfrastructureParameterModelProperty; import org.mule.runtime.module.extension.internal.config.dsl.object.CharsetValueResolverParsingDelegate; import org.mule.runtime.module.extension.internal.config.dsl.object.DefaultObjectParsingDelegate; import org.mule.runtime.module.extension.internal.config.dsl.object.DefaultValueResolverParsingDelegate; import org.mule.runtime.module.extension.internal.config.dsl.object.FixedTypeParsingDelegate; import org.mule.runtime.module.extension.internal.config.dsl.object.MediaTypeValueResolverParsingDelegate; import org.mule.runtime.module.extension.internal.config.dsl.object.ObjectParsingDelegate; import org.mule.runtime.module.extension.internal.config.dsl.object.ParsingDelegate; import org.mule.runtime.module.extension.internal.config.dsl.object.ValueResolverParsingDelegate; import org.mule.runtime.module.extension.internal.config.dsl.parameter.AnonymousInlineParameterGroupParser; import org.mule.runtime.module.extension.internal.config.dsl.parameter.ObjectTypeParameterParser; import org.mule.runtime.module.extension.internal.config.dsl.parameter.TopLevelParameterObjectFactory; import org.mule.runtime.module.extension.internal.config.dsl.parameter.TypedInlineParameterGroupParser; import org.mule.runtime.module.extension.internal.loader.ParameterGroupDescriptor; import org.mule.runtime.module.extension.internal.loader.java.property.ParameterGroupModelProperty; import org.mule.runtime.module.extension.internal.loader.java.property.QueryParameterModelProperty; import org.mule.runtime.module.extension.internal.runtime.resolver.ExpressionBasedParameterResolverValueResolver; import org.mule.runtime.module.extension.internal.runtime.resolver.ExpressionTypedValueValueResolver; import org.mule.runtime.module.extension.internal.runtime.resolver.NativeQueryParameterValueResolver; import org.mule.runtime.module.extension.internal.runtime.resolver.NestedProcessorListValueResolver; import org.mule.runtime.module.extension.internal.runtime.resolver.NestedProcessorValueResolver; import org.mule.runtime.module.extension.internal.runtime.resolver.ParameterResolverValueResolverWrapper; import org.mule.runtime.module.extension.internal.runtime.resolver.StaticLiteralValueResolver; import org.mule.runtime.module.extension.internal.runtime.resolver.StaticValueResolver; import org.mule.runtime.module.extension.internal.runtime.resolver.TypeSafeExpressionValueResolver; import org.mule.runtime.module.extension.internal.runtime.resolver.TypeSafeValueResolverWrapper; import org.mule.runtime.module.extension.internal.runtime.resolver.TypedValueValueResolverWrapper; import org.mule.runtime.module.extension.internal.runtime.resolver.ValueResolver; import org.mule.runtime.module.extension.internal.util.IntrospectionUtils; import com.google.common.collect.ImmutableList; import org.joda.time.DateTime; import org.joda.time.format.ISODateTimeFormat; import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.support.DefaultConversionService; import java.time.Instant; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.ZoneId; import java.time.format.DateTimeParseException; import java.util.ArrayList; import java.util.Calendar; import java.util.Collection; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.function.Supplier; /** * Base class for parsers delegates which generate {@link ComponentBuildingDefinition} instances for the specific components types * (configs, operations, providers, etc) which constitute an extension. * <p> * It works under the premise that this parser will be used to generate an instance of {@link AbstractExtensionObjectFactory}, * using the {@link AbstractExtensionObjectFactory#setParameters(Map)} to provide a map of values containing the parsed * attributes. * <p> * It also gives the opportunity to generate extra {@link ComponentBuildingDefinition} which can also be registered by invoking * {@link #addDefinition(ComponentBuildingDefinition)} * * @since 4.0 */ public abstract class ExtensionDefinitionParser { static final String CHILD_ELEMENT_KEY_PREFIX = "<<"; static final String CHILD_ELEMENT_KEY_SUFFIX = ">>"; protected static final String CONFIG_PROVIDER_ATTRIBUTE_NAME = "configurationProvider"; protected static final String CURSOR_PROVIDER_FACTORY_FIELD_NAME = "cursorProviderFactory"; protected final ExtensionParsingContext parsingContext; protected final List<ObjectParsingDelegate> objectParsingDelegates = ImmutableList .of(new FixedTypeParsingDelegate(PoolingProfile.class), new FixedTypeParsingDelegate(RetryPolicyTemplate.class), new FixedTypeParsingDelegate(TlsContextFactory.class), new DefaultObjectParsingDelegate()); protected final DslSyntaxResolver dslResolver; protected final Builder baseDefinitionBuilder; private final TemplateParser parser = TemplateParser.createMuleStyleParser(); private final ConversionService conversionService = new DefaultConversionService(); private final Map<String, AttributeDefinition.Builder> parameters = new HashMap<>(); private final List<ComponentBuildingDefinition> parsedDefinitions = new ArrayList<>(); private final List<ValueResolverParsingDelegate> valueResolverParsingDelegates = ImmutableList.of(new CharsetValueResolverParsingDelegate(), new MediaTypeValueResolverParsingDelegate()); private final ValueResolverParsingDelegate defaultValueResolverParsingDelegate = new DefaultValueResolverParsingDelegate(); protected final Map<String, String> infrastructureParameterMap = getNameMap(); private final ClassTypeLoader typeLoader = ExtensionsTypeLoaderFactory.getDefault().createTypeLoader(); /** * Creates a new instance * * @param baseDefinitionBuilder a {@link Builder} used as a prototype to generate new definitions * @param dslSyntaxResolver a {@link DslSyntaxResolver} instance associated with the {@link ExtensionModel} being parsed * @param parsingContext the {@link ExtensionParsingContext} in which {@code this} parser operates */ protected ExtensionDefinitionParser(Builder baseDefinitionBuilder, DslSyntaxResolver dslSyntaxResolver, ExtensionParsingContext parsingContext) { this.baseDefinitionBuilder = baseDefinitionBuilder; this.dslResolver = dslSyntaxResolver; this.parsingContext = parsingContext; } /** * Creates a list of {@link ComponentBuildingDefinition} built on copies of {@link #baseDefinitionBuilder}. It also sets the * parsed parsed parameters on the backing {@link AbstractExtensionObjectFactory} * * @return a list with the generated {@link ComponentBuildingDefinition} * @throws ConfigurationException if a parsing error occurs */ public final List<ComponentBuildingDefinition> parse() throws ConfigurationException { final Builder builder = baseDefinitionBuilder.copy(); doParse(builder); AttributeDefinition parametersDefinition = fromFixedValue(new HashMap<>()).build(); if (!parameters.isEmpty()) { KeyAttributeDefinitionPair[] attributeDefinitions = parameters.entrySet().stream() .map(entry -> newBuilder().withAttributeDefinition(entry.getValue().build()).withKey(entry.getKey()).build()) .toArray(KeyAttributeDefinitionPair[]::new); parametersDefinition = fromMultipleDefinitions(attributeDefinitions).build(); } builder.withSetterParameterDefinition("parameters", parametersDefinition); addDefinition(builder.build()); return parsedDefinitions; } /** * Implementations place their custom parsing logic here. * * @param definitionBuilder the {@link Builder} on which implementation are to define their stuff * @throws ConfigurationException if a parsing error occurs */ protected abstract void doParse(Builder definitionBuilder) throws ConfigurationException; /** * Parsers the given {@code parameters} and generates matching definitions * * @param parameters a list of {@link ParameterModel} */ protected void parseParameters(List<ParameterModel> parameters) { parameters.forEach(parameter -> { final DslElementSyntax paramDsl = dslResolver.resolve(parameter); final boolean isContent = isContent(parameter); parameter.getType().accept(new MetadataTypeVisitor() { @Override protected void defaultVisit(MetadataType metadataType) { if (!parseAsContent(metadataType)) { parseAttributeParameter(parameter); } } @Override public void visitString(StringType stringType) { if (paramDsl.supportsChildDeclaration()) { parseFromTextExpression(parameter, paramDsl, () -> { Optional<QueryParameterModelProperty> query = parameter.getModelProperty(QueryParameterModelProperty.class); if (query.isPresent()) { return value -> new NativeQueryParameterValueResolver((String) value, query.get().getQueryTranslator()); } else { return value -> resolverOf(parameter.getName(), stringType, value, parameter.getDefaultValue(), parameter.getExpressionSupport(), parameter.isRequired(), parameter.getModelProperties(), acceptsReferences(parameter)); } }); } else { defaultVisit(stringType); } } @Override public void visitObject(ObjectType objectType) { if (parseAsContent(objectType)) { return; } if (isMap(objectType)) { parseMapParameters(parameter, objectType, paramDsl); return; } if (isNestedProcessor(objectType)) { parseNestedProcessor(parameter); } else { if (!parsingContext.isRegistered(paramDsl.getElementName(), paramDsl.getPrefix())) { parsingContext.registerObjectType(paramDsl.getElementName(), paramDsl.getPrefix(), objectType); parseObjectParameter(parameter, paramDsl); } else { parseObject(getKey(parameter), parameter.getName(), objectType, parameter.getDefaultValue(), parameter.getExpressionSupport(), parameter.isRequired(), acceptsReferences(parameter), paramDsl, parameter.getModelProperties()); } } } @Override public void visitArrayType(ArrayType arrayType) { if (isNestedProcessor(arrayType.getType())) { parseNestedProcessorList(parameter); } else if (!parseAsContent(arrayType)) { parseCollectionParameter(parameter, arrayType, paramDsl); } } private boolean parseAsContent(MetadataType type) { if (isContent) { parseFromTextExpression(parameter, paramDsl, () -> value -> resolverOf(parameter.getName(), type, value, parameter.getDefaultValue(), parameter.getExpressionSupport(), parameter.isRequired(), parameter.getModelProperties(), acceptsReferences(parameter))); return true; } return false; } private boolean isNestedProcessor(MetadataType type) { return NestedProcessor.class.isAssignableFrom(getType(type)); } }); }); } /** * Registers a definition for a {@link ParameterModel} which represents an open {@link ObjectType} * * @param parameter a {@link ParameterModel} * @param objectType a {@link ObjectType} */ protected void parseMapParameters(ParameterModel parameter, ObjectType objectType, DslElementSyntax paramDsl) { parseMapParameters(getKey(parameter), parameter.getName(), objectType, parameter.getDefaultValue(), parameter.getExpressionSupport(), parameter.isRequired(), paramDsl, parameter.getModelProperties()); } /** * Registers a definition for a {@link ParameterModel} which represents an open {@link ObjectType} * * @param key the key that the parsed value should have on the parsed parameter's map * @param name the parameter's name * @param dictionaryType the parameter's open {@link ObjectType} * @param defaultValue the parameter's default value * @param expressionSupport the parameter's {@link ExpressionSupport} * @param required whether the parameter is required */ protected void parseMapParameters(String key, String name, ObjectType dictionaryType, Object defaultValue, ExpressionSupport expressionSupport, boolean required, DslElementSyntax paramDsl, Set<ModelProperty> modelProperties) { parseAttributeParameter(key, name, dictionaryType, defaultValue, expressionSupport, required, modelProperties); Class<? extends Map> mapType = getType(dictionaryType); if (ConcurrentMap.class.equals(mapType)) { mapType = ConcurrentHashMap.class; } else if (Map.class.equals(mapType)) { mapType = LinkedHashMap.class; } final MetadataType valueType = dictionaryType.getOpenRestriction().get(); final Class<?> valueClass = getType(valueType); final MetadataType keyType = typeLoader.load(String.class); final Class<?> keyClass = String.class; final String mapElementName = paramDsl.getElementName(); addParameter(getChildKey(key), fromChildMapConfiguration(String.class, valueClass).withWrapperIdentifier(mapElementName) .withDefaultValue(defaultValue)); addDefinition(baseDefinitionBuilder.copy().withIdentifier(mapElementName).withTypeDefinition(fromType(mapType)).build()); Optional<DslElementSyntax> mapValueChildDsl = paramDsl.getGeneric(valueType); if (!mapValueChildDsl.isPresent()) { return; } DslElementSyntax valueDsl = mapValueChildDsl.get(); valueType.accept(new MetadataTypeVisitor() { @Override protected void defaultVisit(MetadataType metadataType) { String parameterName = paramDsl.getAttributeName(); addDefinition(baseDefinitionBuilder.copy() .withIdentifier(valueDsl.getElementName()) .withTypeDefinition(fromMapEntryType(keyClass, valueClass)) .withKeyTypeConverter(value -> resolverOf(parameterName, keyType, value, null, expressionSupport, true, emptySet(), false)) .withTypeConverter(value -> resolverOf(parameterName, valueType, value, null, expressionSupport, true, emptySet(), false)) .build()); } @Override public void visitObject(ObjectType objectType) { defaultVisit(objectType); Optional<DslElementSyntax> containedElement = valueDsl.getContainedElement(VALUE_ATTRIBUTE_NAME); if (isMap(objectType) || !containedElement.isPresent()) { return; } DslElementSyntax valueChild = containedElement.get(); if ((valueChild.supportsTopLevelDeclaration() || (valueChild.supportsChildDeclaration() && !valueChild.isWrapped())) && !parsingContext.isRegistered(valueChild.getElementName(), valueChild.getPrefix())) { try { parsingContext.registerObjectType(valueChild.getElementName(), valueChild.getPrefix(), objectType); new ObjectTypeParameterParser(baseDefinitionBuilder.copy(), objectType, getContextClassLoader(), dslResolver, parsingContext).parse().forEach(definition -> addDefinition(definition)); } catch (ConfigurationException e) { throw new MuleRuntimeException(createStaticMessage("Could not create parser for map complex type"), e); } } } @Override public void visitArrayType(ArrayType arrayType) { defaultVisit(arrayType); Optional<DslElementSyntax> valueListGenericDsl = valueDsl.getGeneric(arrayType.getType()); if (valueDsl.supportsChildDeclaration() && valueListGenericDsl.isPresent()) { arrayType.getType().accept(new BasicTypeMetadataVisitor() { @Override protected void visitBasicType(MetadataType metadataType) { String parameterName = paramDsl.getAttributeName(); addDefinition(baseDefinitionBuilder.copy().withIdentifier(valueListGenericDsl.get().getElementName()) .withTypeDefinition(fromType(getType(metadataType))) .withTypeConverter( value -> resolverOf(parameterName, metadataType, value, getDefaultValue(metadataType), getExpressionSupport(metadataType), false, emptySet())) .build()); } @Override protected void defaultVisit(MetadataType metadataType) { addDefinition(baseDefinitionBuilder.copy().withIdentifier(valueListGenericDsl.get().getElementName()) .withTypeDefinition(fromType(ValueResolver.class)) .withObjectFactoryType(TopLevelParameterObjectFactory.class) .withConstructorParameterDefinition(fromFixedValue(arrayType.getType()).build()) .withConstructorParameterDefinition(fromFixedValue(getContextClassLoader()).build()).build()); } }); } } }); } protected void parseFields(ObjectType type, DslElementSyntax typeDsl) { type.getFields().forEach(f -> parseField(type, typeDsl, f)); } private void parseField(ObjectType type, DslElementSyntax typeDsl, ObjectFieldType objectField) { final MetadataType fieldType = objectField.getValue(); final String fieldName = objectField.getKey().getName().getLocalPart(); final boolean acceptsReferences = TypeUtils.acceptsReferences(objectField); final Object defaultValue = getDefaultValue(fieldType).orElse(null); final ExpressionSupport expressionSupport = getExpressionSupport(objectField); Optional<DslElementSyntax> fieldDsl = typeDsl.getContainedElement(fieldName); if (!fieldDsl.isPresent() && !isFlattenedParameterGroup(objectField)) { return; } Optional<String> keyName = getInfrastructureParameterName(fieldType); if (keyName.isPresent()) { parseObject(fieldName, keyName.get(), (ObjectType) fieldType, defaultValue, expressionSupport, false, acceptsReferences, fieldDsl.get(), emptySet()); return; } final boolean isContent = TypeUtils.isContent(objectField); fieldType.accept(new MetadataTypeVisitor() { @Override protected void defaultVisit(MetadataType metadataType) { parseAttributeParameter(fieldName, fieldName, metadataType, defaultValue, expressionSupport, false, emptySet()); } @Override public void visitString(StringType stringType) { if (fieldDsl.get().supportsChildDeclaration()) { String elementName = fieldDsl.get().getElementName(); addParameter(fieldName, fromChildConfiguration(String.class).withWrapperIdentifier(elementName)); addDefinition(baseDefinitionBuilder.copy() .withIdentifier(elementName) .withTypeDefinition(fromType(String.class)) .withTypeConverter(value -> resolverOf(elementName, stringType, value, defaultValue, expressionSupport, false, emptySet(), acceptsReferences)) .build()); } else { defaultVisit(stringType); } } @Override public void visitObject(ObjectType objectType) { if (objectType.isOpen()) { if (!parseAsContent(isContent, objectType)) { parseMapParameters(fieldName, fieldName, objectType, defaultValue, expressionSupport, false, fieldDsl.get(), emptySet()); } return; } if (isFlattenedParameterGroup(objectField)) { dslResolver.resolve(objectType) .ifPresent(objectDsl -> objectType.getFields().forEach(field -> parseField(objectType, objectDsl, field))); return; } if (parseAsContent(isContent, objectType)) { return; } DslElementSyntax dsl = fieldDsl.get(); if (!parsingContext.isRegistered(dsl.getElementName(), dsl.getPrefix())) { parsingContext.registerObjectType(dsl.getElementName(), dsl.getPrefix(), type); parseObjectParameter(fieldName, fieldName, objectType, defaultValue, expressionSupport, false, acceptsReferences, dsl, emptySet()); } else { parseObject(fieldName, fieldName, objectType, defaultValue, expressionSupport, false, acceptsReferences, dsl, emptySet()); } } @Override public void visitArrayType(ArrayType arrayType) { if (!parseAsContent(isContent, arrayType)) { parseCollectionParameter(fieldName, fieldName, arrayType, defaultValue, expressionSupport, false, fieldDsl.get(), emptySet()); } } private boolean parseAsContent(boolean isContent, MetadataType type) { if (isContent) { parseFromTextExpression(fieldName, fieldDsl.get(), () -> value -> resolverOf(fieldName, type, value, defaultValue, expressionSupport, false, emptySet(), false)); return true; } return false; } }); } /** * Registers a definition for a {@link ParameterModel} which represents an {@link ArrayType} * * @param parameter a {@link ParameterModel} * @param arrayType an {@link ArrayType} */ protected void parseCollectionParameter(ParameterModel parameter, ArrayType arrayType, DslElementSyntax parameterDsl) { parseCollectionParameter(getKey(parameter), parameter.getName(), arrayType, parameter.getDefaultValue(), parameter.getExpressionSupport(), parameter.isRequired(), parameterDsl, parameter.getModelProperties()); } /** * Registers a definition for a {@link ParameterModel} which represents an {@link ArrayType} * * @param key the key that the parsed value should have on the parsed parameter's map * @param name the parameter's name * @param arrayType the parameter's {@link ArrayType} * @param defaultValue the parameter's default value * @param expressionSupport the parameter's {@link ExpressionSupport} * @param required whether the parameter is required */ protected void parseCollectionParameter(String key, String name, ArrayType arrayType, Object defaultValue, ExpressionSupport expressionSupport, boolean required, DslElementSyntax parameterDsl, Set<ModelProperty> modelProperties) { parseAttributeParameter(key, name, arrayType, defaultValue, expressionSupport, required, modelProperties); Class<? extends Iterable> collectionType = getType(arrayType); if (Set.class.equals(collectionType)) { collectionType = HashSet.class; } else if (Collection.class.equals(collectionType) || Iterable.class.equals(collectionType) || collectionType == null) { collectionType = List.class; } final String collectionElementName = parameterDsl.getElementName(); addParameter(getChildKey(key), fromChildConfiguration(collectionType).withWrapperIdentifier(collectionElementName)); addDefinition(baseDefinitionBuilder.copy().withIdentifier(collectionElementName).withTypeDefinition(fromType(collectionType)) .build()); Optional<DslElementSyntax> collectionItemDsl = parameterDsl.getGeneric(arrayType.getType()); if (parameterDsl.supportsChildDeclaration() && collectionItemDsl.isPresent()) { String itemIdentifier = collectionItemDsl.get().getElementName(); String itemNamespace = collectionItemDsl.get().getPrefix(); arrayType.getType().accept(new BasicTypeMetadataVisitor() { private void addBasicTypeDefinition(MetadataType metadataType) { Builder itemDefinitionBuilder = baseDefinitionBuilder.copy().withIdentifier(itemIdentifier).withNamespace(itemNamespace) .withTypeDefinition(fromType(getType(metadataType))) .withTypeConverter(value -> resolverOf(name, metadataType, value, getDefaultValue(metadataType).orElse(null), getExpressionSupport(metadataType), false, emptySet())); addDefinition(itemDefinitionBuilder.build()); } @Override protected void visitBasicType(MetadataType metadataType) { addBasicTypeDefinition(metadataType); } @Override public void visitDate(DateType dateType) { addBasicTypeDefinition(dateType); } @Override public void visitDateTime(DateTimeType dateTimeType) { addBasicTypeDefinition(dateTimeType); } @Override public void visitObject(ObjectType objectType) { if (isMap(objectType)) { return; } DslElementSyntax itemDsl = collectionItemDsl.get(); if ((itemDsl.supportsTopLevelDeclaration() || itemDsl.supportsChildDeclaration()) && !parsingContext.isRegistered(itemDsl.getElementName(), itemDsl.getPrefix())) { try { parsingContext.registerObjectType(itemDsl.getElementName(), itemDsl.getPrefix(), objectType); new ObjectTypeParameterParser(baseDefinitionBuilder.copy(), objectType, getContextClassLoader(), dslResolver, parsingContext).parse().forEach(definition -> addDefinition(definition)); } catch (ConfigurationException e) { throw new MuleRuntimeException(createStaticMessage("Could not create parser for collection complex type"), e); } } } }); } } protected ClassLoader getContextClassLoader() { return Thread.currentThread().getContextClassLoader(); } private ValueResolver<?> resolverOf(String parameterName, MetadataType expectedType, Object value, Object defaultValue, ExpressionSupport expressionSupport, boolean required, Set<ModelProperty> modelProperties) { return resolverOf(parameterName, expectedType, value, defaultValue, expressionSupport, required, modelProperties, true); } protected ValueResolver<?> resolverOf(String parameterName, MetadataType expectedType, Object value, Object defaultValue, ExpressionSupport expressionSupport, boolean required, Set<ModelProperty> modelProperties, boolean acceptsReferences) { if (value instanceof ValueResolver) { return (ValueResolver<?>) value; } ValueResolver resolver; final Class<Object> expectedClass = getType(expectedType); boolean isExpression = isExpression(value, parser); resolver = isExpression ? getExpressionBasedValueResolver(expectedType, (String) value, modelProperties, expectedClass) : getStaticValueResolver(parameterName, expectedType, value, defaultValue, modelProperties, acceptsReferences, expectedClass); if (resolver.isDynamic() && expressionSupport == NOT_SUPPORTED) { throw new IllegalArgumentException( format("An expression value was given for parameter '%s' but it doesn't support expressions", parameterName)); } if (!resolver.isDynamic() && expressionSupport == REQUIRED && required) { throw new IllegalArgumentException(format("A fixed value was given for parameter '%s' but it only supports expressions", parameterName)); } return resolver; } /** * Generates the {@link ValueResolver} for expression based values */ private ValueResolver getExpressionBasedValueResolver(MetadataType expectedType, String value, Set<ModelProperty> modelProperties, Class<Object> expectedClass) { ValueResolver resolver; if (isParameterResolver(modelProperties, expectedType)) { resolver = new ExpressionBasedParameterResolverValueResolver<>(value, expectedType); } else if (isTypedValue(modelProperties, expectedType)) { resolver = new ExpressionTypedValueValueResolver<>(value, expectedClass); } else if (isLiteral(modelProperties, expectedType)) { resolver = new StaticLiteralValueResolver<>(value, expectedClass); } else { resolver = new TypeSafeExpressionValueResolver(value, expectedType); } return resolver; } /** * Generates the {@link ValueResolver} for non expression based values */ private ValueResolver getStaticValueResolver(String parameterName, MetadataType expectedType, Object value, Object defaultValue, Set<ModelProperty> modelProperties, boolean acceptsReferences, Class<Object> expectedClass) { if (isLiteral(modelProperties, expectedType)) { return new StaticLiteralValueResolver<>(value != null ? value.toString() : null, expectedClass); } ValueResolver resolver; resolver = value != null ? getValueResolverFromMetadataType(parameterName, expectedType, value, defaultValue, acceptsReferences, expectedClass) : new StaticValueResolver<>(defaultValue); if (isParameterResolver(modelProperties, expectedType)) { resolver = new ParameterResolverValueResolverWrapper(resolver); } else if (isTypedValue(modelProperties, expectedType)) { resolver = new TypedValueValueResolverWrapper(resolver); } return resolver; } private ValueResolver getValueResolverFromMetadataType(final String parameterName, MetadataType expectedType, final Object value, final Object defaultValue, final boolean acceptsReferences, final Class<Object> expectedClass) { final Reference<ValueResolver> resolverValueHolder = new Reference<>(); expectedType.accept(new BasicTypeMetadataVisitor() { @Override protected void visitBasicType(MetadataType metadataType) { if (conversionService.canConvert(value.getClass(), expectedClass)) { resolverValueHolder.set(new StaticValueResolver<>(convertSimpleValue(value, expectedClass, parameterName))); } else { defaultVisit(metadataType); } } @Override public void visitDateTime(DateTimeType dateTimeType) { resolverValueHolder.set(parseDate(value, dateTimeType, defaultValue)); } @Override public void visitDate(DateType dateType) { resolverValueHolder.set(parseDate(value, dateType, defaultValue)); } @Override public void visitObject(ObjectType objectType) { if (isMap(objectType)) { defaultVisit(objectType); return; } ValueResolver valueResolver; Optional<? extends ParsingDelegate> delegate = locateParsingDelegate(valueResolverParsingDelegates, objectType); Optional<DslElementSyntax> typeDsl = dslResolver.resolve(objectType); if (delegate.isPresent() && typeDsl.isPresent()) { valueResolver = (ValueResolver) delegate.get().parse(value.toString(), objectType, typeDsl.get()); } else { valueResolver = acceptsReferences ? defaultValueResolverParsingDelegate.parse(value.toString(), objectType, null) : new StaticValueResolver<>(value); } resolverValueHolder.set(valueResolver); } @Override protected void defaultVisit(MetadataType metadataType) { ValueResolver delegateResolver = locateParsingDelegate(valueResolverParsingDelegates, metadataType) .map(delegate -> delegate.parse(value.toString(), metadataType, null)) .orElseGet(() -> acceptsReferences ? defaultValueResolverParsingDelegate.parse(value.toString(), metadataType, null) : new TypeSafeValueResolverWrapper<>(new StaticValueResolver<>(value), expectedClass)); resolverValueHolder.set(delegateResolver); } }); return resolverValueHolder.get(); } protected void parseFromTextExpression(ParameterModel parameter, DslElementSyntax paramDsl, Supplier<TypeConverter> typeConverter) { parseFromTextExpression(getKey(parameter), paramDsl, typeConverter); } protected void parseFromTextExpression(String key, DslElementSyntax paramDsl, Supplier<TypeConverter> typeConverter) { addParameter(getChildKey(key), fromChildConfiguration(String.class).withWrapperIdentifier(paramDsl.getElementName())); addDefinition(baseDefinitionBuilder.copy() .withIdentifier(paramDsl.getElementName()) .withTypeDefinition(fromType(String.class)) .withTypeConverter(typeConverter.get()) .build()); } private boolean isExpression(Object value, TemplateParser parser) { return value instanceof String && parser.isContainsTemplate((String) value); } /** * Registers a definition for parsing the given {@code parameterModel} as an element attribute * * @param parameterModel a {@link ParameterModel} * @return an {@link AttributeDefinition.Builder} */ protected AttributeDefinition.Builder parseAttributeParameter(ParameterModel parameterModel) { return parseAttributeParameter(getKey(parameterModel), parameterModel.getName(), parameterModel.getType(), parameterModel.getDefaultValue(), parameterModel.getExpressionSupport(), parameterModel.isRequired(), parameterModel.getModelProperties()); } /** * Registers a definition for parsing the given {@code parameterModel} as an element attribute * * @param key the key that the parsed value should have on the parsed parameter's map * @param name the parameter's name * @param type the parameter's type * @param defaultValue the parameter's default value * @param expressionSupport the parameter's {@link ExpressionSupport} * @param required whether the parameter is required or not * @return an {@link AttributeDefinition.Builder} */ protected AttributeDefinition.Builder parseAttributeParameter(String key, String name, MetadataType type, Object defaultValue, ExpressionSupport expressionSupport, boolean required, Set<ModelProperty> modelProperties) { return parseAttributeParameter(key, name, type, defaultValue, expressionSupport, required, true, modelProperties); } private AttributeDefinition.Builder parseAttributeParameter(String key, String name, MetadataType type, Object defaultValue, ExpressionSupport expressionSupport, boolean required, boolean acceptsReferences, Set<ModelProperty> modelProperties) { AttributeDefinition.Builder definitionBuilder = fromSimpleParameter(name, value -> resolverOf(name, type, value, defaultValue, expressionSupport, required, modelProperties, acceptsReferences)) .withDefaultValue(defaultValue); addParameter(key, definitionBuilder); return definitionBuilder; } /** * Registers a definition for a {@link ParameterModel} which represents an {@link ObjectType} * * @param parameterModel a {@link ParameterModel} */ protected void parseObjectParameter(ParameterModel parameterModel, DslElementSyntax paramDsl) { if (isContent(parameterModel)) { parseFromTextExpression(parameterModel, paramDsl, () -> value -> resolverOf(parameterModel.getName(), parameterModel.getType(), value, parameterModel.getDefaultValue(), parameterModel.getExpressionSupport(), parameterModel.isRequired(), parameterModel.getModelProperties(), acceptsReferences(parameterModel))); } else { parseObjectParameter(getKey(parameterModel), parameterModel.getName(), (ObjectType) parameterModel.getType(), parameterModel.getDefaultValue(), parameterModel.getExpressionSupport(), parameterModel.isRequired(), acceptsReferences(parameterModel), paramDsl, parameterModel.getModelProperties()); } } /** * Registers a definition for a {@link ParameterModel} which represents an {@link ObjectType} * * @param key the key that the parsed value should have on the parsed parameter's map * @param name the parameter's name * @param type an {@link ObjectType} * @param defaultValue the parameter's default value * @param expressionSupport the parameter's {@link ExpressionSupport} * @param required whether the parameter is required or not * @param modelProperties parameter's {@link ModelProperty}s */ protected void parseObjectParameter(String key, String name, ObjectType type, Object defaultValue, ExpressionSupport expressionSupport, boolean required, boolean acceptsReferences, DslElementSyntax elementDsl, Set<ModelProperty> modelProperties) { parseObject(key, name, type, defaultValue, expressionSupport, required, acceptsReferences, elementDsl, modelProperties); final String elementNamespace = elementDsl.getPrefix(); final String elementName = elementDsl.getElementName(); if (elementDsl.supportsChildDeclaration() && !elementDsl.isWrapped() && modelProperties.stream().noneMatch(m -> m.getName().equals(InfrastructureParameterModelProperty.NAME))) { try { new ObjectTypeParameterParser(baseDefinitionBuilder.copy(), elementName, elementNamespace, type, getContextClassLoader(), dslResolver, parsingContext).parse() .forEach(this::addDefinition); } catch (Exception e) { throw new MuleRuntimeException(new ConfigurationException(e)); } } } protected void parseObject(String key, String name, ObjectType type, Object defaultValue, ExpressionSupport expressionSupport, boolean required, boolean acceptsReferences, DslElementSyntax elementDsl, Set<ModelProperty> modelProperties) { parseAttributeParameter(key, name, type, defaultValue, expressionSupport, required, acceptsReferences, modelProperties); ObjectParsingDelegate delegate = (ObjectParsingDelegate) locateParsingDelegate(objectParsingDelegates, type) .orElseThrow(() -> new MuleRuntimeException(createStaticMessage("Could not find a parsing delegate for type " + getType(type).getName()))); addParameter(getChildKey(key), delegate.parse(name, type, elementDsl)); } private <M extends MetadataType, T> Optional<ParsingDelegate<M, T>> locateParsingDelegate( List<? extends ParsingDelegate<M, T>> delegatesList, M metadataType) { return (Optional<ParsingDelegate<M, T>>) delegatesList.stream().filter(candidate -> candidate.accepts(metadataType)) .findFirst(); } /** * Adds the given {@code definition} to the list of definitions that the {@link #parse()} method generates by default * * @param definition a {@link ComponentBuildingDefinition} */ protected void addDefinition(ComponentBuildingDefinition definition) { parsedDefinitions.add(definition); } protected void addParameter(String key, AttributeDefinition.Builder definitionBuilder) { if (parameters.put(key, definitionBuilder) != null) { throw new IllegalArgumentException("An AttributeDefinition builder was already defined for parameter " + key); } } protected List<ParameterGroupModel> getInlineGroups(ParameterizedModel model) { return model.getParameterGroupModels().stream() .filter(ParameterGroupModel::isShowInDsl) .collect(toList()); } private void parseNestedProcessor(ParameterModel parameterModel) { final String processorElementName = hyphenize(parameterModel.getName()); addParameter(getChildKey(parameterModel.getName()), fromChildConfiguration(NestedProcessorValueResolver.class).withWrapperIdentifier(processorElementName)); addDefinition(baseDefinitionBuilder.copy().withIdentifier(processorElementName) .withTypeDefinition(fromType(NestedProcessorValueResolver.class)) .withConstructorParameterDefinition(fromChildConfiguration(Processor.class).build()).build()); } private void parseNestedProcessorList(ParameterModel parameterModel) { final String processorElementName = hyphenize(parameterModel.getName()); addParameter(getChildKey(parameterModel.getName()), fromChildCollectionConfiguration(NestedProcessorListValueResolver.class) .withWrapperIdentifier(processorElementName)); addDefinition(baseDefinitionBuilder.copy().withIdentifier(processorElementName) .withTypeDefinition(fromType(NestedProcessorListValueResolver.class)) .withConstructorParameterDefinition(fromChildCollectionConfiguration(Processor.class).build()) .build()); } private boolean isParameterResolver(Set<ModelProperty> modelProperties, MetadataType expectedType) { return IntrospectionUtils.isParameterResolver(modelProperties) || IntrospectionUtils.isParameterResolver(expectedType); } private boolean isTypedValue(Set<ModelProperty> modelProperties, MetadataType expectedType) { return IntrospectionUtils.isTypedValue(modelProperties) || IntrospectionUtils.isTypedValue(expectedType); } private boolean isLiteral(Set<ModelProperty> modelProperties, MetadataType expectedType) { return IntrospectionUtils.isLiteral(modelProperties) || IntrospectionUtils.isLiteral(expectedType); } private ValueResolver doParseDate(Object value, Class<?> type) { if (value instanceof String) { Object constructedValue = null; DateTime dateTime = getParsedDateTime((String) value); if (type.equals(LocalDate.class)) { constructedValue = LocalDate.of(dateTime.getYear(), dateTime.getMonthOfYear(), dateTime.getDayOfMonth()); } else if (type.equals(Date.class)) { constructedValue = dateTime.toDate(); } else if (type.equals(LocalDateTime.class)) { Instant instant = ofEpochMilli(dateTime.getMillis()); constructedValue = LocalDateTime.ofInstant(instant, ZoneId.of(dateTime.getZone().getID())); } else if (type.equals(Calendar.class)) { Calendar calendar = Calendar.getInstance(); calendar.setTime(dateTime.toDate()); constructedValue = calendar; } if (constructedValue == null) { throw new IllegalArgumentException(format("Could not construct value of type '%s' from String '%s'", type.getName(), value)); } else { value = constructedValue; } } if (value instanceof Date || value instanceof LocalDate || value instanceof LocalDateTime || value instanceof Calendar) { return new StaticValueResolver<>(value); } throw new IllegalArgumentException(format("Could not transform value of type '%s' to a valid date type", value != null ? value.getClass().getName() : "null")); } private DateTime getParsedDateTime(String value) { try { return ISODateTimeFormat.dateTimeParser().withOffsetParsed().parseDateTime(value); } catch (DateTimeParseException e) { throw new IllegalArgumentException(format("Could not parse value '%s' according to ISO 8601", value)); } } private ValueResolver parseDate(Object value, MetadataType dateType, Object defaultValue) { Class<?> type = getType(dateType); if (isExpression(value, parser)) { return new TypeSafeExpressionValueResolver<>((String) value, dateType); } if (value == null) { if (defaultValue == null) { return new StaticValueResolver<>(null); } value = defaultValue; } return doParseDate(value, type); } private String getKey(ParameterModel parameterModel) { return getMemberName(parameterModel, parameterModel.getName()); } protected String getChildKey(String key) { return format("%s%s%s", CHILD_ELEMENT_KEY_PREFIX, key, CHILD_ELEMENT_KEY_SUFFIX); } private Object convertSimpleValue(Object value, Class<Object> expectedClass, String parameterName) { try { return conversionService.convert(value, expectedClass); } catch (Exception e) { throw new IllegalArgumentException(format("Could not transform simple value '%s' to type '%s' in parameter '%s'", value, expectedClass.getSimpleName(), parameterName)); } } private boolean acceptsReferences(ParameterModel parameterModel) { return parameterModel.getDslConfiguration().allowsReferences(); } protected void parseParameters(ParameterizedModel parameterizedModel) throws ConfigurationException { List<ParameterGroupModel> inlineGroups = getInlineGroups(parameterizedModel); parseParameters(getFlatParameters(inlineGroups, parameterizedModel.getAllParameterModels())); for (ParameterGroupModel group : inlineGroups) { parseInlineParameterGroup(group); } } protected void parseInlineParameterGroup(ParameterGroupModel group) throws ConfigurationException { ParameterGroupDescriptor descriptor = group.getModelProperty(ParameterGroupModelProperty.class) .map(ParameterGroupModelProperty::getDescriptor) .orElse(null); DslElementSyntax dslElementSyntax = dslResolver.resolveInline(group); if (descriptor != null) { addParameter(getChildKey(getContainerName(descriptor.getContainer())), new DefaultObjectParsingDelegate().parse("", null, dslElementSyntax)); new TypedInlineParameterGroupParser(baseDefinitionBuilder.copy(), group, descriptor, getContextClassLoader(), dslElementSyntax, dslResolver, parsingContext).parse().forEach(this::addDefinition); } else { AttributeDefinition.Builder builder = fromChildConfiguration(Map.class); if (dslElementSyntax.isWrapped()) { builder.withWrapperIdentifier(dslElementSyntax.getElementName()); } else { builder.withIdentifier(dslElementSyntax.getElementName()); } addParameter(getChildKey(group.getName()), builder); new AnonymousInlineParameterGroupParser(baseDefinitionBuilder.copy(), group, getContextClassLoader(), dslElementSyntax, dslResolver, parsingContext).parse().forEach(this::addDefinition); } } protected List<ParameterModel> getFlatParameters(List<ParameterGroupModel> inlineGroups, List<ParameterModel> parameters) { List<ParameterModel> inlineParameters = inlineGroups.stream() .flatMap(g -> g.getParameterModels().stream()).collect(toList()); return inlineParameters.isEmpty() ? parameters : parameters.stream() .filter(p -> !inlineParameters.contains(p)) .collect(toList()); } protected Optional<String> getInfrastructureParameterName(MetadataType fieldType) { return ofNullable(infrastructureParameterMap.get(getId(fieldType))); } }