/*
* 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.internal;
import static java.lang.String.format;
import static java.util.Collections.singletonList;
import static org.apache.commons.lang.StringUtils.isBlank;
import static org.apache.commons.lang.StringUtils.join;
import static org.mule.runtime.api.i18n.I18nMessageFactory.createStaticMessage;
import static org.mule.runtime.api.util.Preconditions.checkArgument;
import static org.mule.runtime.internal.dsl.DslConstants.CORE_NAMESPACE;
import static org.mule.runtime.internal.dsl.DslConstants.CORE_PREFIX;
import static org.mule.runtime.internal.dsl.DslConstants.FLOW_ELEMENT_IDENTIFIER;
import static org.mule.runtime.internal.dsl.DslConstants.NAME_ATTRIBUTE_NAME;
import org.mule.runtime.api.app.declaration.ArtifactDeclaration;
import org.mule.runtime.api.app.declaration.ConfigurationElementDeclaration;
import org.mule.runtime.api.app.declaration.ElementDeclaration;
import org.mule.runtime.api.app.declaration.FlowElementDeclaration;
import org.mule.runtime.api.app.declaration.GlobalElementDeclarationVisitor;
import org.mule.runtime.api.app.declaration.TopLevelParameterDeclaration;
import org.mule.runtime.api.app.declaration.fluent.ParameterSimpleValue;
import org.mule.runtime.api.dsl.DslResolvingContext;
import org.mule.runtime.api.exception.MuleRuntimeException;
import org.mule.runtime.api.meta.model.XmlDslModel;
import org.mule.runtime.config.spring.dsl.api.ArtifactDeclarationXmlSerializer;
import org.mule.runtime.config.spring.dsl.model.DslElementModelFactory;
import org.mule.runtime.config.spring.dsl.model.XmlArtifactDeclarationLoader;
import org.mule.runtime.config.spring.dsl.model.XmlDslElementModelConverter;
import org.mule.runtime.core.util.xmlsecurity.XMLSecureFactories;
import java.io.InputStream;
import java.io.StringWriter;
import java.util.LinkedList;
import java.util.List;
import java.util.stream.IntStream;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
/**
* Default implementation of {@link ArtifactDeclarationXmlSerializer}
*
* @since 4.0
*/
public class DefaultArtifactDeclarationXmlSerializer implements ArtifactDeclarationXmlSerializer {
private static final String XMLNS_W3_URL = "http://www.w3.org/2000/xmlns/";
private static final String XSI_W3_URL = "http://www.w3.org/2001/XMLSchema-instance";
private static final String XSI_SCHEMA_LOCATION = "xsi:schemaLocation";
private static final String XMLNS = "xmlns";
private final DslResolvingContext context;
public DefaultArtifactDeclarationXmlSerializer(DslResolvingContext context) {
this.context = context;
}
@Override
public String serialize(ArtifactDeclaration declaration) {
return serializeArtifact(declaration);
}
@Override
public ArtifactDeclaration deserialize(InputStream configResource) {
checkArgument(configResource != null, "The artifact to deserialize cannot be null");
return XmlArtifactDeclarationLoader.getDefault(context).load(configResource);
}
@Override
public ArtifactDeclaration deserialize(String name, InputStream configResource) {
checkArgument(configResource != null, "The artifact to deserialize cannot be null");
return XmlArtifactDeclarationLoader.getDefault(context).load(name, configResource);
}
private String serializeArtifact(ArtifactDeclaration artifact) {
checkArgument(artifact != null, "The artifact to serialize cannot be null");
try {
Document doc = createAppDocument(artifact);
XmlDslElementModelConverter toXmlConverter = XmlDslElementModelConverter.getDefault(doc);
DslElementModelFactory modelResolver = DslElementModelFactory.getDefault(context);
final GlobalElementDeclarationVisitor declarationVisitor = new GlobalElementDeclarationVisitor() {
@Override
public void visit(ConfigurationElementDeclaration declaration) {
appendChildElement(toXmlConverter, doc.getDocumentElement(), modelResolver, declaration);
}
@Override
public void visit(TopLevelParameterDeclaration declaration) {
appendChildElement(toXmlConverter, doc.getDocumentElement(), modelResolver, declaration);
}
@Override
public void visit(FlowElementDeclaration flowDeclaration) {
Element flow = doc.createElement(FLOW_ELEMENT_IDENTIFIER);
flow.setAttribute(NAME_ATTRIBUTE_NAME, flowDeclaration.getRefName());
flowDeclaration.getParameters()
.stream().filter(p -> p.getValue() instanceof ParameterSimpleValue)
.forEach(p -> flow.setAttribute(p.getName(), ((ParameterSimpleValue) p.getValue()).getValue()));
flowDeclaration.getComponents()
.forEach(declaration -> appendChildElement(toXmlConverter, flow, modelResolver, declaration));
doc.getDocumentElement().appendChild(flow);
}
};
artifact.getGlobalElements().forEach(declaration -> declaration.accept(declarationVisitor));
List<String> cDataElements = getCDataElements(doc.getDocumentElement());
// write the content into xml file
TransformerFactory transformerFactory = XMLSecureFactories.createDefault().getTransformerFactory();
Transformer transformer = transformerFactory.newTransformer();
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
transformer.setOutputProperty(OutputKeys.CDATA_SECTION_ELEMENTS, join(cDataElements, " "));
transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");
DOMSource source = new DOMSource(doc);
StringWriter writer = new StringWriter();
transformer.transform(source, new StreamResult(writer));
return writer.getBuffer().toString();
} catch (Exception e) {
throw new MuleRuntimeException(createStaticMessage("Failed to serialize the declaration for the artifact ["
+ artifact.getName() + "]: " + e.getMessage()), e);
}
}
private Document createAppDocument(ArtifactDeclaration artifact) throws ParserConfigurationException {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(true);
DocumentBuilder docBuilder = factory.newDocumentBuilder();
Document doc = docBuilder.newDocument();
Element mule = doc.createElement(CORE_PREFIX);
doc.appendChild(mule);
artifact.getCustomConfigurationParameters().forEach(p -> mule.setAttribute(p.getName(), p.getValue().toString()));
if (isBlank(mule.getAttribute(XSI_SCHEMA_LOCATION))) {
StringBuilder schemaLocation = new StringBuilder();
context.getExtensions().forEach(extension -> {
XmlDslModel xml = extension.getXmlDslModel();
schemaLocation.append(xml.getNamespace())
.append(" ")
.append(xml.getSchemaLocation())
.append(" ");
String prefix = xml.getNamespace().equals(CORE_NAMESPACE) ? "" : ":" + xml.getPrefix();
mule.setAttributeNS(XMLNS_W3_URL, XMLNS + prefix, xml.getNamespace());
});
mule.setAttributeNS(XSI_W3_URL,
XSI_SCHEMA_LOCATION, schemaLocation.toString().trim());
}
return doc;
}
private void appendChildElement(XmlDslElementModelConverter converter, Element parent, DslElementModelFactory modelResolver,
ElementDeclaration declaration) {
modelResolver.create(declaration)
.ifPresent(e -> parent.appendChild(converter.asXml(e)));
}
private List<String> getCDataElements(Node element) {
if (element.getChildNodes().getLength() == 1 && element.getFirstChild().getNodeType() == Node.CDATA_SECTION_NODE) {
return singletonList(format("{%s}%s", element.getNamespaceURI(), element.getLocalName()));
} else {
List<String> identifiers = new LinkedList<>();
NodeList childs = element.getChildNodes();
IntStream.range(0, childs.getLength()).mapToObj(childs::item)
.forEach(c -> identifiers.addAll(getCDataElements(c)));
return identifiers;
}
}
}