/*
* 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 java.util.Collections.emptySet;
import static java.util.stream.Collectors.toList;
import static org.apache.commons.lang3.StringUtils.EMPTY;
import static org.mule.metadata.api.utils.MetadataTypeUtils.getDefaultValue;
import static org.mule.runtime.extension.api.declaration.type.TypeUtils.getExpressionSupport;
import static org.mule.runtime.extension.api.declaration.type.TypeUtils.getLayoutModel;
import static org.mule.runtime.extension.api.declaration.type.TypeUtils.getParameterRole;
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.NameUtils.sanitizeName;
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_EXTENSION;
import static org.mule.runtime.config.spring.dsl.api.xml.SchemaConstants.MULE_ABSTRACT_EXTENSION_TYPE;
import static org.mule.runtime.config.spring.dsl.api.xml.SchemaConstants.UNBOUNDED;
import org.mule.metadata.api.model.MetadataType;
import org.mule.metadata.api.model.ObjectFieldType;
import org.mule.metadata.api.model.ObjectType;
import org.mule.runtime.api.meta.model.ParameterDslConfiguration;
import org.mule.runtime.api.meta.model.SubTypesModel;
import org.mule.runtime.api.meta.model.parameter.ParameterModel;
import org.mule.runtime.api.tls.TlsContextFactory;
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.model.parameter.ImmutableParameterModel;
import org.mule.runtime.module.extension.internal.capability.xml.schema.model.ComplexContent;
import org.mule.runtime.module.extension.internal.capability.xml.schema.model.ComplexType;
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.LocalComplexType;
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 java.util.Collection;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import javax.xml.namespace.QName;
/**
* Builder delegation class to generate an XSD schema that describes an {@link ObjectType}
*
* @since 4.0.0
*/
final class ObjectTypeSchemaDelegate {
private final Map<String, ComplexTypeHolder> registeredComplexTypesHolders = new LinkedHashMap<>();
private final Map<String, TopLevelElement> registeredGlobalElementTypes = new LinkedHashMap<>();
private final ObjectFactory objectFactory = new ObjectFactory();
private final SchemaBuilder builder;
private final DslSyntaxResolver dsl;
ObjectTypeSchemaDelegate(SchemaBuilder builder) {
this.builder = builder;
this.dsl = builder.getDslResolver();
}
/**
* For any given {@code parameter} with an {@link ObjectType} as {@link MetadataType}, the element generated in the schema will
* vary depending on the properties of the type itself along with the properties associated to the parameter.
* <p>
* This method serves as a resolver for all that logic, creating the required element for the parameter with complex type.
*
* @param type the {@link ObjectType} of the parameter for which the element is being created
* @param paramSyntax the {@link DslElementSyntax} of the parameter for which the element is being created
* @param paramDsl the {@link ParameterDslConfiguration} associated to the parameter, if any is present.
* @param description the documentation associated to the parameter
* @param all the {@link ExplicitGroup group} the generated element should belong to
*/
void generatePojoElement(ObjectType type, DslElementSyntax paramSyntax, ParameterDslConfiguration paramDsl,
String description, List<TopLevelElement> all) {
if (paramSyntax.supportsChildDeclaration()) {
if (builder.isImported(type)) {
addImportedTypeElement(paramSyntax, description, type, all);
} else {
if (paramSyntax.isWrapped()) {
declareRefToType(type, paramSyntax, description, all);
} else {
declareTypeInline(type, paramSyntax, description, all);
}
}
}
Optional<DslElementSyntax> typeDsl = builder.getDslResolver().resolve(type);
if (paramDsl.allowsReferences() && typeDsl.isPresent() && typeDsl.get().supportsTopLevelDeclaration()
&& !builder.isImported(type)) {
// We need to register the type, just in case people want to use it as global elements
registerPojoType(type, description);
}
}
private void declareTypeInline(ObjectType objectType, DslElementSyntax paramDsl, String description,
List<TopLevelElement> all) {
registerPojoComplexType(objectType, null, description);
String typeName = getBaseTypeName(objectType);
QName localQName = new QName(paramDsl.getNamespace(), typeName, paramDsl.getPrefix());
addChildElementTypeExtension(localQName, description, paramDsl.getElementName(), !paramDsl.supportsAttributeDeclaration(),
all);
}
private void declareRefToType(ObjectType objectType, DslElementSyntax paramDsl, String description,
List<TopLevelElement> all) {
registerPojoSubtypes(objectType, builder.getTypesMapping().getSubTypes(objectType));
addAbstractTypeRef(paramDsl, description, objectType, all);
}
/**
* Adds a new {@link TopLevelElement element} to the {@link ExplicitGroup group} {@code all}
*/
private void addChildElementTypeExtension(QName base, String description, String name, boolean required,
List<TopLevelElement> all) {
TopLevelElement objectElement = builder.createTopLevelElement(name, required ? ONE : ZERO, MAX_ONE);
objectElement.setAnnotation(builder.createDocAnnotation(description));
objectElement.setComplexType(createTypeExtension(base));
all.add(objectElement);
}
private void addImportedTypeElement(DslElementSyntax paramDsl, String description, MetadataType metadataType,
List<TopLevelElement> all) {
DslElementSyntax typeDsl = builder.getDslResolver().resolve(metadataType)
.orElseThrow(() -> new IllegalArgumentException(format("The given type [%s] is not eligible for Import",
getId(metadataType))));
if (paramDsl.isWrapped()) {
TopLevelElement objectElement = builder.createTopLevelElement(paramDsl.getElementName(), ZERO, MAX_ONE);
objectElement.setComplexType(new LocalComplexType());
objectElement.setAnnotation(builder.createDocAnnotation(description));
if (typeDsl.isWrapped()) {
objectElement.getComplexType()
.setChoice(builder.createTypeRefChoiceLocalOrGlobal(typeDsl, metadataType, ZERO, UNBOUNDED));
} else {
ExplicitGroup sequence = new ExplicitGroup();
sequence.setMinOccurs(ONE);
sequence.setMaxOccurs(MAX_ONE);
QName refQName = new QName(paramDsl.getNamespace(), getAbstractElementName(typeDsl), paramDsl.getPrefix());
sequence.getParticle().add(objectFactory.createElement(builder.createRefElement(refQName, false)));
objectElement.getComplexType().setSequence(sequence);
}
all.add(objectElement);
} else {
QName extensionBase = new QName(typeDsl.getNamespace(), sanitizeName(getId(metadataType)), typeDsl.getPrefix());
addChildElementTypeExtension(extensionBase, description, paramDsl.getElementName(),
!paramDsl.supportsAttributeDeclaration(), all);
}
}
private void addAbstractTypeRef(DslElementSyntax paramDsl, String description, MetadataType metadataType,
List<TopLevelElement> all) {
TopLevelElement objectElement = builder.createTopLevelElement(paramDsl.getElementName(),
paramDsl.supportsAttributeDeclaration() ? ZERO : ONE,
MAX_ONE);
objectElement.setAnnotation(builder.createDocAnnotation(description));
objectElement.setComplexType(createComplexTypeWithAbstractElementRef(metadataType));
all.add(objectElement);
}
private LocalComplexType createComplexTypeWithAbstractElementRef(MetadataType type) {
DslElementSyntax typeDsl = builder.getDslResolver().resolve(type).orElseThrow(
() -> new IllegalArgumentException(format("No element ref can be created for the given type [%s]",
getId(type))));
LocalComplexType complexType = new LocalComplexType();
if (typeDsl.isWrapped()) {
complexType.setChoice(builder.createTypeRefChoiceLocalOrGlobal(typeDsl, type, ONE, MAX_ONE));
} else {
ExplicitGroup sequence = new ExplicitGroup();
sequence.setMinOccurs(ONE);
sequence.setMaxOccurs(MAX_ONE);
sequence.getParticle().add(objectFactory.createElement(createRefToLocalElement(typeDsl, type)));
complexType.setSequence(sequence);
}
return complexType;
}
private TopLevelElement createRefToLocalElement(DslElementSyntax typeDsl, MetadataType metadataType) {
registerPojoType(metadataType, EMPTY);
QName qName = new QName(typeDsl.getNamespace(), getAbstractElementName(typeDsl), typeDsl.getPrefix());
return builder.createRefElement(qName, false);
}
String registerPojoType(MetadataType metadataType, String description) {
return registerPojoType(metadataType, null, description);
}
/**
* The given {@code type} type will be registered as a {@link TopLevelComplexType} in the current namespace if it was not
* imported.
* <p/>
* If an abstract or concrete {@link TopLevelElement} declaration are required for this type, then they will also be registered.
* This method is idempotent for any given {@code type}
*
* @param type a {@link MetadataType} describing a pojo type
* @param baseType a {@link MetadataType} describing a pojo's base type
* @param description the type's description
* @return the reference name of the complexType
*/
private String registerPojoType(MetadataType type, MetadataType baseType, String description) {
if (!builder.isImported(type)) {
registerPojoComplexType((ObjectType) type, (ObjectType) baseType, description);
Optional<DslElementSyntax> typeDsl = builder.getDslResolver().resolve(type);
if (typeDsl.isPresent() && shouldRegisterTypeAsElement(type, typeDsl.get())) {
registerPojoGlobalElements(typeDsl.get(), (ObjectType) type, (ObjectType) baseType, description);
}
}
return getBaseTypeName(type);
}
/**
* @return whether or not the {@code type} requires the declaration of an abstract or concrete {@link TopLevelElement}
*/
private boolean shouldRegisterTypeAsElement(MetadataType type, DslElementSyntax typeDsl) {
return typeDsl.supportsTopLevelDeclaration() || typeDsl.isWrapped() ||
(type instanceof ObjectType && !builder.getTypesMapping().getSuperTypes((ObjectType) type).isEmpty());
}
/**
* Registers the {@link TopLevelComplexType} associated to the given {@link ObjectType} in the current namespace
*
* @param type the {@link ObjectType} that will be represented by the registered {@link ComplexType}
* @param baseType the {@code base} for the {@link ComplexType} {@code extension} declaration
* @param description
* @return a new {@link ComplexType} declaration for the given {@link ObjectType}
*/
private ComplexType registerPojoComplexType(ObjectType type, ObjectType baseType, String description) {
String typeId = getId(type);
if (registeredComplexTypesHolders.get(typeId) != null) {
return registeredComplexTypesHolders.get(typeId).getComplexType();
}
QName base = getComplexTypeBase(baseType);
Collection<ObjectFieldType> fields;
if (baseType == null) {
fields = type.getFields();
} else {
fields = type.getFields().stream()
.filter(field -> !baseType.getFields().stream()
.anyMatch(other -> other.getKey().getName().getLocalPart().equals(field.getKey().getName().getLocalPart())))
.collect(toList());
}
ComplexType complexType = declarePojoAsType(type, base, description, fields);
builder.getSchema().getSimpleTypeOrComplexTypeOrGroup().add(complexType);
return complexType;
}
/**
* @return the {@link QName} of the {@code base} type for which the new {@link ComplexType} declares an {@code extension}
*/
private QName getComplexTypeBase(ObjectType baseType) {
Optional<DslElementSyntax> baseDsl = builder.getDslResolver().resolve(baseType);
if (!baseDsl.isPresent()) {
return MULE_ABSTRACT_EXTENSION_TYPE;
}
return new QName(baseDsl.get().getNamespace(), getBaseTypeName(baseType), baseDsl.get().getPrefix());
}
private ComplexType declarePojoAsType(ObjectType metadataType, QName base, String description,
Collection<ObjectFieldType> fields) {
final TopLevelComplexType complexType = new TopLevelComplexType();
registeredComplexTypesHolders.put(getId(metadataType), new ComplexTypeHolder(complexType, metadataType));
complexType.setName(sanitizeName(getId(metadataType)));
complexType.setAnnotation(builder.createDocAnnotation(description));
ComplexContent complexContent = new ComplexContent();
complexType.setComplexContent(complexContent);
final ExtensionType extension = new ExtensionType();
extension.setBase(base);
complexContent.setExtension(extension);
DslElementSyntax typeDsl = dsl.resolve(metadataType).get();
List<TopLevelElement> childElements = new LinkedList<>();
fields.forEach(field -> {
String fieldName = field.getKey().getName().getLocalPart();
DslElementSyntax fieldDsl = typeDsl.getContainedElement(fieldName).orElse(null);
if (isFlattenedParameterGroup(field)) {
declareGroupedFields(extension, childElements, field);
} else {
declareObjectField(fieldDsl, field, extension, childElements);
}
});
if (!childElements.isEmpty()) {
final ExplicitGroup all = new ExplicitGroup();
all.setMaxOccurs(MAX_ONE);
boolean requiredChilds = childElements.stream().anyMatch(builder::isRequired);
all.setMinOccurs(requiredChilds ? ONE : ZERO);
childElements.forEach(p -> all.getParticle().add(objectFactory.createElement(p)));
extension.setSequence(all);
}
return complexType;
}
private void declareGroupedFields(ExtensionType extension, List<TopLevelElement> childElements, ObjectFieldType field) {
DslElementSyntax groupDsl = dsl.resolve(field.getValue()).get();
((ObjectType) field.getValue()).getFields().forEach(
subField -> {
DslElementSyntax subFieldDsl = groupDsl
.getContainedElement(subField.getKey().getName().getLocalPart())
.orElse(null);
declareObjectField(subFieldDsl, subField, extension, childElements);
});
}
private void declareObjectField(DslElementSyntax fieldDsl, ObjectFieldType field, ExtensionType extension,
List<TopLevelElement> all) {
ParameterModel parameter = asParameter(field);
if (fieldDsl == null) {
fieldDsl = dsl.resolve(parameter);
}
final String id = getId(field.getValue());
if (id.equals(TlsContextFactory.class.getName())) {
builder.addTlsSupport(extension, all);
return;
}
builder.declareAsParameter(field.getValue(), extension, parameter, fieldDsl, all);
}
private void registerPojoGlobalElements(DslElementSyntax typeDsl, ObjectType type, ObjectType baseType, String description) {
if (registeredGlobalElementTypes.containsKey(globalTypeKey(typeDsl))) {
return;
}
QName typeQName = getTypeQName(typeDsl, type);
TopLevelElement abstractElement = registerAbstractElement(typeQName, typeDsl, baseType);
if (typeDsl.supportsTopLevelDeclaration() || (typeDsl.supportsChildDeclaration() && typeDsl.isWrapped()) ||
!builder.getTypesMapping().getSuperTypes(type).isEmpty()) {
registerConcreteGlobalElement(typeDsl, description, abstractElement.getName(), typeQName);
}
}
QName getTypeQName(DslElementSyntax typeDsl, MetadataType type) {
return new QName(builder.getSchema().getTargetNamespace(), getBaseTypeName(type), typeDsl.getPrefix());
}
TopLevelElement registerAbstractElement(MetadataType type, DslElementSyntax typeDsl) {
return registerAbstractElement(getTypeQName(typeDsl, type), typeDsl, null);
}
private TopLevelElement registerAbstractElement(QName typeQName, DslElementSyntax typeDsl, ObjectType baseType) {
TopLevelElement element = registeredGlobalElementTypes.get(typeDsl.getPrefix() + getAbstractElementName(typeDsl));
if (element != null) {
return element;
}
Optional<DslElementSyntax> baseDsl = builder.getDslResolver().resolve(baseType);
if (typeDsl.isWrapped()) {
createGlobalMuleExtensionAbstractElement(typeQName, typeDsl, baseDsl);
}
TopLevelElement abstractElement = new TopLevelElement();
abstractElement.setName(getAbstractElementName(typeDsl));
abstractElement.setAbstract(true);
if (!typeDsl.supportsTopLevelDeclaration()) {
abstractElement.setType(typeQName);
}
if (baseDsl.isPresent() || typeDsl.supportsTopLevelDeclaration()) {
QName substitutionGroup = getAbstractElementSubstitutionGroup(typeDsl, baseDsl);
abstractElement.setSubstitutionGroup(substitutionGroup);
}
builder.getSchema().getSimpleTypeOrComplexTypeOrGroup().add(abstractElement);
registeredGlobalElementTypes.put(typeDsl.getPrefix() + getAbstractElementName(typeDsl), abstractElement);
return abstractElement;
}
private QName getAbstractElementSubstitutionGroup(DslElementSyntax typeDsl, Optional<DslElementSyntax> baseDsl) {
QName substitutionGroup;
if (baseDsl.isPresent()) {
DslElementSyntax base = baseDsl.get();
String abstractElementName = typeDsl.supportsTopLevelDeclaration() ? getGlobalAbstractName(base)
: getAbstractElementName(base);
substitutionGroup = new QName(base.getNamespace(), abstractElementName, base.getPrefix());
} else {
if (typeDsl.isWrapped()) {
substitutionGroup = new QName(typeDsl.getNamespace(), getGlobalAbstractName(typeDsl), typeDsl.getPrefix());
} else {
substitutionGroup = MULE_ABSTRACT_EXTENSION;
}
}
return substitutionGroup;
}
private void createGlobalMuleExtensionAbstractElement(QName typeQName, DslElementSyntax typeDsl,
Optional<DslElementSyntax> baseDsl) {
QName globalSubGroup;
if (baseDsl.isPresent()) {
DslElementSyntax base = baseDsl.get();
globalSubGroup = new QName(base.getNamespace(), getGlobalAbstractName(base), base.getPrefix());
} else {
globalSubGroup = MULE_ABSTRACT_EXTENSION;
}
TopLevelElement abstractElement = new TopLevelElement();
abstractElement.setName(getGlobalAbstractName(typeDsl));
abstractElement.setSubstitutionGroup(globalSubGroup);
abstractElement.setAbstract(true);
if (!typeDsl.supportsTopLevelDeclaration()) {
abstractElement.setType(typeQName);
}
builder.getSchema().getSimpleTypeOrComplexTypeOrGroup().add(abstractElement);
}
void registerConcreteGlobalElement(DslElementSyntax typeDsl, String description,
String abstractElementName, QName typeQName) {
if (registeredGlobalElementTypes.containsKey(globalTypeKey(typeDsl))) {
return;
}
TopLevelElement objectElement = new TopLevelElement();
objectElement.setName(typeDsl.getElementName());
objectElement.setSubstitutionGroup(new QName(typeDsl.getNamespace(), abstractElementName, typeDsl.getPrefix()));
objectElement.setAnnotation(builder.createDocAnnotation(description));
objectElement.setComplexType(createTypeExtension(typeQName));
if (typeDsl.supportsTopLevelDeclaration()) {
objectElement.getComplexType().getComplexContent().getExtension().getAttributeOrAttributeGroup()
.add(builder.createNameAttribute(false));
}
builder.getSchema().getSimpleTypeOrComplexTypeOrGroup().add(objectElement);
registeredGlobalElementTypes.put(globalTypeKey(typeDsl), objectElement);
}
private String globalTypeKey(DslElementSyntax typeDsl) {
return typeDsl.getPrefix() + typeDsl.getElementName();
}
private ImmutableParameterModel asParameter(ObjectFieldType field) {
return new ImmutableParameterModel(field.getKey().getName().getLocalPart(),
"",
field.getValue(),
false,
field.isRequired(),
false,
getExpressionSupport(field), getDefaultValue(field).orElse(null),
getParameterRole(field), ParameterDslConfiguration.getDefaultInstance(),
null, getLayoutModel(field).orElse(null), emptySet());
}
void registerPojoSubtypes(SubTypesModel subTypesModel) {
registerPojoSubtypes(subTypesModel.getBaseType(), subTypesModel.getSubTypes());
}
void registerPojoSubtypes(MetadataType baseType, Collection<ObjectType> subTypes) {
if (!builder.isImported(baseType)) {
registerPojoType(baseType, EMPTY);
}
subTypes.forEach(subtype -> registerPojoType(subtype, baseType, EMPTY));
}
LocalComplexType createTypeExtension(QName base) {
final LocalComplexType complexType = new LocalComplexType();
ComplexContent complexContent = new ComplexContent();
complexType.setComplexContent(complexContent);
final ExtensionType extension = new ExtensionType();
extension.setBase(base);
complexContent.setExtension(extension);
return complexType;
}
private String getBaseTypeName(MetadataType type) {
return sanitizeName(getId(type));
}
static String getGlobalAbstractName(DslElementSyntax dsl) {
return "global-" + getAbstractElementName(dsl);
}
static String getAbstractElementName(DslElementSyntax dsl) {
return "abstract-" + dsl.getElementName();
}
}