com.evolveum.midpoint.prism.parser.DomSerializer.java Source code

Java tutorial

Introduction

Here is the source code for com.evolveum.midpoint.prism.parser.DomSerializer.java

Source

/*
 * Copyright (c) 2010-2014 Evolveum
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.evolveum.midpoint.prism.parser;

import com.evolveum.midpoint.prism.path.ItemPath;
import com.evolveum.midpoint.prism.schema.SchemaRegistry;
import com.evolveum.midpoint.prism.xml.DynamicNamespacePrefixMapper;
import com.evolveum.midpoint.prism.xml.XsdTypeMapper;
import com.evolveum.midpoint.prism.xnode.ListXNode;
import com.evolveum.midpoint.prism.xnode.MapXNode;
import com.evolveum.midpoint.prism.xnode.PrimitiveXNode;
import com.evolveum.midpoint.prism.xnode.RootXNode;
import com.evolveum.midpoint.prism.xnode.SchemaXNode;
import com.evolveum.midpoint.prism.xnode.XNode;
import com.evolveum.midpoint.util.DOMUtil;
import com.evolveum.midpoint.util.exception.SchemaException;
import com.ibm.wsdl.extensions.schema.SchemaConstants;
import com.sun.org.apache.xml.internal.utils.XMLChar;
import org.apache.commons.lang.StringUtils;
import org.w3c.dom.Comment;
import org.w3c.dom.DOMException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

import javax.xml.namespace.QName;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

/**
 * @author semancik
 *
 */
public class DomSerializer {

    private Document doc;
    private Element topElement;
    private boolean serializeCompositeObjects = false;
    private SchemaRegistry schemaRegistry;

    DomSerializer(DomParser parser, SchemaRegistry schemaRegistry) {
        super();
        this.schemaRegistry = schemaRegistry;
    }

    public boolean isSerializeCompositeObjects() {
        return serializeCompositeObjects;
    }

    public void setSerializeCompositeObjects(boolean serializeCompositeObjects) {
        this.serializeCompositeObjects = serializeCompositeObjects;
    }

    private DynamicNamespacePrefixMapper getNamespacePrefixMapper() {
        if (schemaRegistry == null) {
            return null;
        }
        return schemaRegistry.getNamespacePrefixMapper();
    }

    private void initialize() {
        doc = DOMUtil.getDocument();
        topElement = null;
    }

    private void initializeWithExistingDocument(Document document) {
        doc = document;
        topElement = document.getDocumentElement(); // TODO is this ok?
    }

    public Element serialize(RootXNode rootxnode) throws SchemaException {
        initialize();
        return serializeInternal(rootxnode, null);
    }

    // this one is used only from within JaxbDomHack.toAny(..) - hopefully it will disappear soon
    @Deprecated
    public Element serialize(RootXNode rootxnode, Document document) throws SchemaException {
        initializeWithExistingDocument(document);
        return serializeInternal(rootxnode, null);
    }

    public Element serializeUnderElement(RootXNode rootxnode, Element parentElement) throws SchemaException {
        initializeWithExistingDocument(parentElement.getOwnerDocument());
        return serializeInternal(rootxnode, parentElement);
    }

    private Element serializeInternal(RootXNode rootxnode, Element parentElement) throws SchemaException {
        QName rootElementName = rootxnode.getRootElementName();
        Element topElement = createElement(rootElementName, parentElement);
        if (parentElement != null) {
            parentElement.appendChild(topElement);
        }
        QName typeQName = rootxnode.getTypeQName();
        if (typeQName == null && rootxnode.getSubnode().getTypeQName() != null) {
            typeQName = rootxnode.getSubnode().getTypeQName();
        }
        if (typeQName != null && !schemaRegistry.hasImplicitTypeDefinition(rootElementName, typeQName)) {
            DOMUtil.setXsiType(topElement, setQNamePrefixExplicitIfNeeded(typeQName));
        }
        XNode subnode = rootxnode.getSubnode();
        if (subnode instanceof PrimitiveXNode) {
            serializePrimitiveElementOrAttribute((PrimitiveXNode) subnode, topElement, rootElementName, false);
            return DOMUtil.getFirstChildElement(topElement);
        }
        if (!(subnode instanceof MapXNode)) {
            throw new SchemaException("Sub-root xnode is not map, cannot serialize to XML (it is " + subnode + ")");
        }
        // at this point we can put frequently used namespaces (e.g. c, t, q, ri) into the document to eliminate their use
        // on many places inside the doc (MID-2198)
        DOMUtil.setNamespaceDeclarations(topElement, getNamespacePrefixMapper().getNamespacesDeclaredByDefault());
        serializeMap((MapXNode) subnode, topElement);
        return topElement;
    }

    public Element serializeToElement(MapXNode xmap, QName elementName) throws SchemaException {
        initialize();
        Element element = createElement(elementName, null);
        topElement = element;
        serializeMap(xmap, element);
        return element;
    }

    private void serializeMap(MapXNode xmap, Element topElement) throws SchemaException {
        for (Entry<QName, XNode> entry : xmap.entrySet()) {
            QName elementQName = entry.getKey();
            XNode xsubnode = entry.getValue();
            if (xsubnode instanceof ListXNode) {
                ListXNode xlist = (ListXNode) xsubnode;
                for (XNode xsubsubnode : xlist) {
                    serializeSubnode(xsubsubnode, topElement, elementQName);
                }
            } else {
                serializeSubnode(xsubnode, topElement, elementQName);
            }
        }
    }

    private void serializeSubnode(XNode xsubnode, Element parentElement, QName elementName) throws SchemaException {
        if (xsubnode == null) {
            return;
        }
        if (xsubnode instanceof RootXNode) {
            Element element = createElement(elementName, parentElement);
            appendCommentIfPresent(element, xsubnode);
            parentElement.appendChild(element);
            serializeSubnode(((RootXNode) xsubnode).getSubnode(), element,
                    ((RootXNode) xsubnode).getRootElementName());
        } else if (xsubnode instanceof MapXNode) {
            Element element = createElement(elementName, parentElement);
            appendCommentIfPresent(element, xsubnode);
            if (xsubnode.isExplicitTypeDeclaration() && xsubnode.getTypeQName() != null) {
                DOMUtil.setXsiType(element, setQNamePrefixExplicitIfNeeded(xsubnode.getTypeQName()));
            }
            parentElement.appendChild(element);
            //         System.out.println("subnode " + xsubnode.debugDump());
            serializeMap((MapXNode) xsubnode, element);
        } else if (xsubnode instanceof PrimitiveXNode<?>) {
            PrimitiveXNode<?> xprim = (PrimitiveXNode<?>) xsubnode;
            if (xprim.isAttribute()) {
                serializePrimitiveElementOrAttribute(xprim, parentElement, elementName, true);
            } else {
                serializePrimitiveElementOrAttribute(xprim, parentElement, elementName, false);
            }
        } else if (xsubnode instanceof ListXNode) {
            ListXNode xlist = (ListXNode) xsubnode;
            for (XNode xsubsubnode : xlist) {
                serializeSubnode(xsubsubnode, parentElement, elementName);
            }
        } else if (xsubnode instanceof SchemaXNode) {
            serializeSchema((SchemaXNode) xsubnode, parentElement);
        } else {
            throw new IllegalArgumentException("Unknown subnode " + xsubnode);
        }
    }

    public Element serializeXPrimitiveToElement(PrimitiveXNode<?> xprim, QName elementName) throws SchemaException {
        initialize();
        Element parent = DOMUtil.createElement(doc, new QName("fake", "fake"));
        serializePrimitiveElementOrAttribute(xprim, parent, elementName, false);
        return DOMUtil.getFirstChildElement(parent);
    }

    private void serializePrimitiveElementOrAttribute(PrimitiveXNode<?> xprim, Element parentElement,
            QName elementOrAttributeName, boolean asAttribute) throws SchemaException {
        QName typeQName = xprim.getTypeQName();

        // if typeQName is not explicitly specified, we try to determine it from parsed value
        // TODO we should probably set typeQName when parsing the value...
        if (typeQName == null && xprim.isParsed()) {
            Object v = xprim.getValue();
            if (v != null) {
                typeQName = XsdTypeMapper.toXsdType(v.getClass());
            }
        }

        if (typeQName == null) { // this means that either xprim is unparsed or it is empty
            if (com.evolveum.midpoint.prism.PrismContext.isAllowSchemalessSerialization()) {
                // We cannot correctly serialize without a type. But this is needed
                // sometimes. So just default to string
                String stringValue = xprim.getStringValue();
                if (stringValue != null) {
                    if (asAttribute) {
                        DOMUtil.setAttributeValue(parentElement, elementOrAttributeName.getLocalPart(),
                                stringValue);
                        DOMUtil.setNamespaceDeclarations(parentElement, xprim.getRelevantNamespaceDeclarations());
                    } else {
                        Element element;
                        try {
                            element = createElement(elementOrAttributeName, parentElement);
                            appendCommentIfPresent(element, xprim);
                        } catch (DOMException e) {
                            throw new DOMException(e.code, e.getMessage() + "; creating element "
                                    + elementOrAttributeName + " in element " + DOMUtil.getQName(parentElement));
                        }
                        parentElement.appendChild(element);
                        DOMUtil.setElementTextContent(element, stringValue);
                        DOMUtil.setNamespaceDeclarations(element, xprim.getRelevantNamespaceDeclarations());
                    }
                }
                return;
            } else {
                throw new IllegalStateException("No type for primitive element " + elementOrAttributeName
                        + ", cannot serialize (schemaless serialization is disabled)");
            }
        }

        // typeName != null after this point

        if (StringUtils.isBlank(typeQName.getNamespaceURI())) {
            typeQName = XsdTypeMapper.determineQNameWithNs(typeQName);
        }

        Element element = null;

        if (typeQName.equals(ItemPath.XSD_TYPE)) {
            ItemPath itemPath = (ItemPath) xprim.getValue();
            if (itemPath != null) {
                if (asAttribute) {
                    throw new UnsupportedOperationException(
                            "Serializing ItemPath as an attribute is not supported yet");
                }
                XPathHolder holder = new XPathHolder(itemPath);
                element = holder.toElement(elementOrAttributeName, parentElement.getOwnerDocument());
                parentElement.appendChild(element);
            }

        } else {
            // not an ItemPathType

            if (!asAttribute) {
                try {
                    element = createElement(elementOrAttributeName, parentElement);
                } catch (DOMException e) {
                    throw new DOMException(e.code, e.getMessage() + "; creating element " + elementOrAttributeName
                            + " in element " + DOMUtil.getQName(parentElement));
                }
                appendCommentIfPresent(element, xprim);
                parentElement.appendChild(element);
            }

            if (typeQName.equals(DOMUtil.XSD_QNAME)) {
                QName value = (QName) xprim.getParsedValueWithoutRecording(DOMUtil.XSD_QNAME);
                value = setQNamePrefixExplicitIfNeeded(value);
                if (asAttribute) {
                    try {
                        DOMUtil.setQNameAttribute(parentElement, elementOrAttributeName.getLocalPart(), value);
                    } catch (DOMException e) {
                        throw new DOMException(e.code,
                                e.getMessage() + "; setting attribute " + elementOrAttributeName.getLocalPart()
                                        + " in element " + DOMUtil.getQName(parentElement) + " to QName value "
                                        + value);
                    }
                } else {
                    DOMUtil.setQNameValue(element, value);
                }
            } else {
                // not ItemType nor QName
                String value = xprim.getGuessedFormattedValue();

                if (asAttribute) {
                    DOMUtil.setAttributeValue(parentElement, elementOrAttributeName.getLocalPart(), value);
                } else {
                    DOMUtil.setElementTextContent(element, value);
                }
            }

        }
        if (!asAttribute && xprim.isExplicitTypeDeclaration()) {
            DOMUtil.setXsiType(element, setQNamePrefixExplicitIfNeeded(typeQName));
        }
    }

    private void appendCommentIfPresent(Element element, XNode xnode) {
        String text = xnode.getComment();
        if (StringUtils.isNotEmpty(text)) {
            DOMUtil.createComment(element, text);
        }
    }

    private void serializeSchema(SchemaXNode xschema, Element parentElement) {
        Element schemaElement = xschema.getSchemaElement();
        if (schemaElement == null) {
            return;
        }
        Element clonedSchema = (Element) schemaElement.cloneNode(true);
        doc.adoptNode(clonedSchema);
        parentElement.appendChild(clonedSchema);
    }

    /**
     * Create XML element with the correct namespace prefix and namespace definition.
     * @param qname element QName
     * @return created DOM element
     */
    private Element createElement(QName qname, Element parentElement) {
        String namespaceURI = qname.getNamespaceURI();
        if (!StringUtils.isBlank(namespaceURI)) {
            qname = setQNamePrefix(qname);
        }
        if (parentElement != null) {
            return DOMUtil.createElement(doc, qname, parentElement, parentElement);
        } else {
            // This is needed otherwise the root element itself could not be created
            // Caller of this method is responsible for setting the topElement
            return DOMUtil.createElement(doc, qname);
        }
    }

    private QName setQNamePrefix(QName qname) {
        DynamicNamespacePrefixMapper namespacePrefixMapper = getNamespacePrefixMapper();
        if (namespacePrefixMapper == null) {
            return qname;
        }
        return namespacePrefixMapper.setQNamePrefix(qname);
    }

    private QName setQNamePrefixExplicitIfNeeded(QName name) {
        if (name != null && StringUtils.isNotBlank(name.getNamespaceURI())
                && StringUtils.isBlank(name.getPrefix())) {
            return setQNamePrefixExplicit(name);
        } else {
            return name;
        }
    }

    private QName setQNamePrefixExplicit(QName qname) {
        DynamicNamespacePrefixMapper namespacePrefixMapper = getNamespacePrefixMapper();
        if (namespacePrefixMapper == null) {
            return qname;
        }
        return namespacePrefixMapper.setQNamePrefixExplicit(qname);
    }

}