/* * 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.capability.xml.schema.builder; import static java.lang.String.format; import static java.math.BigInteger.ONE; import static java.math.BigInteger.ZERO; import static org.apache.commons.lang.StringUtils.EMPTY; import static org.mule.metadata.api.model.MetadataFormat.JAVA; import static org.mule.metadata.java.api.utils.JavaTypeUtils.getType; import static org.mule.runtime.internal.dsl.DslConstants.CONFIG_ATTRIBUTE_NAME; import static org.mule.runtime.api.util.Preconditions.checkArgument; import static org.mule.runtime.config.spring.dsl.api.xml.SchemaConstants.CONFIG_ATTRIBUTE_DESCRIPTION; import static org.mule.runtime.config.spring.dsl.api.xml.SchemaConstants.GROUP_SUFFIX; import static org.mule.runtime.config.spring.dsl.api.xml.SchemaConstants.MAX_ONE; import static org.mule.runtime.config.spring.dsl.api.xml.SchemaConstants.MULE_ABSTRACT_OPERATOR; import static org.mule.runtime.config.spring.dsl.api.xml.SchemaConstants.MULE_MESSAGE_PROCESSOR_TYPE; import static org.mule.runtime.config.spring.dsl.api.xml.SchemaConstants.OPERATION_SUBSTITUTION_GROUP_SUFFIX; import static org.mule.runtime.config.spring.dsl.api.xml.SchemaConstants.SUBSTITUTABLE_NAME; import static org.mule.runtime.config.spring.dsl.api.xml.SchemaConstants.UNBOUNDED; 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.visitor.MetadataTypeVisitor; import org.mule.runtime.api.meta.model.ComponentModel; import org.mule.runtime.api.meta.model.parameter.ParameterModel; import org.mule.runtime.api.util.Reference; import org.mule.runtime.core.api.NestedProcessor; import org.mule.runtime.core.util.StringUtils; import org.mule.runtime.extension.api.annotation.Extensible; 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.QNameModelProperty; import org.mule.runtime.module.extension.internal.capability.xml.schema.model.Attribute; import org.mule.runtime.module.extension.internal.capability.xml.schema.model.ComplexContent; import org.mule.runtime.module.extension.internal.capability.xml.schema.model.ExplicitGroup; import org.mule.runtime.module.extension.internal.capability.xml.schema.model.ExtensionType; import org.mule.runtime.module.extension.internal.capability.xml.schema.model.GroupRef; import org.mule.runtime.module.extension.internal.capability.xml.schema.model.LocalComplexType; import org.mule.runtime.module.extension.internal.capability.xml.schema.model.NamedGroup; import org.mule.runtime.module.extension.internal.capability.xml.schema.model.ObjectFactory; import org.mule.runtime.module.extension.internal.capability.xml.schema.model.TopLevelComplexType; import org.mule.runtime.module.extension.internal.capability.xml.schema.model.TopLevelElement; import org.mule.runtime.module.extension.internal.loader.java.property.TypeRestrictionModelProperty; import javax.xml.namespace.QName; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; /** * Base builder delegation class to generate an XSD schema that describes an executable {@link ComponentModel} * * @since 4.0.0 */ abstract class ExecutableTypeSchemaDelegate { protected final SchemaBuilder builder; protected final ObjectFactory objectFactory = new ObjectFactory(); private final Map<String, NamedGroup> substitutionGroups = new LinkedHashMap<>(); private final DslSyntaxResolver dsl; ExecutableTypeSchemaDelegate(SchemaBuilder builder) { this.builder = builder; this.dsl = builder.getDslResolver(); } protected ExtensionType createExecutableType(String name, QName base, DslElementSyntax dslSyntax) { TopLevelComplexType complexType = new TopLevelComplexType(); complexType.setName(name); ComplexContent complexContent = new ComplexContent(); complexType.setComplexContent(complexContent); final ExtensionType complexContentExtension = new ExtensionType(); complexContentExtension.setBase(base); complexContent.setExtension(complexContentExtension); if (dslSyntax.requiresConfig()) { Attribute configAttr = builder.createAttribute(CONFIG_ATTRIBUTE_NAME, CONFIG_ATTRIBUTE_DESCRIPTION, true, SUBSTITUTABLE_NAME); complexContentExtension.getAttributeOrAttributeGroup().add(configAttr); } this.builder.getSchema().getSimpleTypeOrComplexTypeOrGroup().add(complexType); return complexContentExtension; } protected ExtensionType registerParameters(ExtensionType type, List<ParameterModel> parameterModels) { List<TopLevelElement> childElements = new LinkedList<>(); parameterModels.forEach(parameter -> { DslElementSyntax paramDsl = dsl.resolve(parameter); MetadataType parameterType = parameter.getType(); if (isOperation(parameterType)) { String maxOccurs = parameterType instanceof ArrayType ? UNBOUNDED : MAX_ONE; childElements.add(generateNestedProcessorElement(paramDsl, parameter, maxOccurs)); } else { boolean shouldDeclare = true; if (parameter.getModelProperty(QNameModelProperty.class).isPresent() && !parameter.getDslConfiguration().allowsReferences()) { shouldDeclare = false; } if (shouldDeclare) { this.builder.declareAsParameter(parameterType, type, parameter, paramDsl, childElements); } } }); if (!childElements.isEmpty()) { if (type.getSequence() == null) { final ExplicitGroup all = new ExplicitGroup(); all.setMinOccurs(ZERO); all.setMaxOccurs(MAX_ONE); builder.addParameterToSequence(childElements, all); type.setSequence(all); } else { builder.addParameterToSequence(childElements, type.getSequence()); } } return type; } protected void initialiseSequence(ExtensionType operationType) { if (operationType.getSequence() == null) { ExplicitGroup sequence = new ExplicitGroup(); sequence.setMinOccurs(ZERO); sequence.setMaxOccurs(MAX_ONE); operationType.setSequence(sequence); } } private TopLevelElement generateNestedProcessorElement(DslElementSyntax paramDsl, ParameterModel parameterModel, String maxOccurs) { LocalComplexType collectionComplexType = new LocalComplexType(); GroupRef group = generateNestedProcessorGroup(parameterModel, maxOccurs); collectionComplexType.setGroup(group); collectionComplexType.setAnnotation(builder.createDocAnnotation(parameterModel.getDescription())); TopLevelElement collectionElement = new TopLevelElement(); collectionElement.setName(paramDsl.getElementName()); collectionElement.setMinOccurs(parameterModel.isRequired() ? ONE : ZERO); collectionElement.setMaxOccurs(maxOccurs); collectionElement.setComplexType(collectionComplexType); collectionElement.setAnnotation(builder.createDocAnnotation(EMPTY)); return collectionElement; } private GroupRef generateNestedProcessorGroup(ParameterModel parameterModel, String maxOccurs) { QName ref = MULE_MESSAGE_PROCESSOR_TYPE; TypeRestrictionModelProperty restrictionCapability = parameterModel.getModelProperty(TypeRestrictionModelProperty.class).orElse(null); if (restrictionCapability != null) { ref = getSubstitutionGroup(restrictionCapability.getType()); ref = new QName(ref.getNamespaceURI(), getGroupName(ref.getLocalPart()), ref.getPrefix()); } GroupRef group = new GroupRef(); group.setRef(ref); group.setMinOccurs(parameterModel.isRequired() ? ONE : ZERO); group.setMaxOccurs(maxOccurs); return group; } private boolean isOperation(MetadataType type) { if (!type.getMetadataFormat().equals(JAVA)) { return false; } Reference<Boolean> isOperation = new Reference<>(false); type.accept(new MetadataTypeVisitor() { @Override public void visitObject(ObjectType objectType) { if (NestedProcessor.class.isAssignableFrom(getType(objectType))) { isOperation.set(true); } } @Override public void visitArrayType(ArrayType arrayType) { arrayType.getType().accept(this); } }); return isOperation.get(); } protected QName getSubstitutionGroup(Class<?> type) { return new QName(builder.getSchema().getTargetNamespace(), registerExtensibleElement(type)); } private String registerExtensibleElement(Class<?> type) { Extensible extensible = type.getAnnotation(Extensible.class); checkArgument(extensible != null, format("Type %s is not extensible", type.getName())); String name = extensible.alias(); if (StringUtils.isBlank(name)) { name = type.getName() + OPERATION_SUBSTITUTION_GROUP_SUFFIX; } NamedGroup group = substitutionGroups.get(name); if (group == null) { // register abstract element to serve as substitution TopLevelElement element = new TopLevelElement(); element.setName(name); element.setAbstract(true); element.setSubstitutionGroup(MULE_ABSTRACT_OPERATOR); builder.getSchema().getSimpleTypeOrComplexTypeOrGroup().add(element); group = new NamedGroup(); group.setName(getGroupName(name)); builder.getSchema().getSimpleTypeOrComplexTypeOrGroup().add(group); substitutionGroups.put(name, group); element = new TopLevelElement(); element.setRef(new QName(builder.getSchema().getTargetNamespace(), name)); group.getChoice().getParticle().add(objectFactory.createElement(element)); } return name; } private String getGroupName(String name) { return name + GROUP_SUFFIX; } }