/* * Copyright (c) 2010-2016 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.lex.dom; import com.evolveum.midpoint.prism.marshaller.XPathHolder; 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.evolveum.prism.xml.ns._public.types_3.ItemPathType; import org.apache.commons.lang.StringUtils; import org.jetbrains.annotations.NotNull; import org.w3c.dom.DOMException; import org.w3c.dom.Document; import org.w3c.dom.Element; import javax.xml.namespace.QName; import java.util.Map.Entry; /** * @author semancik * */ public class DomLexicalWriter { private Document doc; private boolean serializeCompositeObjects = false; private SchemaRegistry schemaRegistry; DomLexicalWriter(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(); } private void initializeWithExistingDocument(Document document) { doc = document; document.getDocumentElement(); } @NotNull public Element serialize(@NotNull 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); } @NotNull private Element serializeInternal(@NotNull 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.isExplicitTypeDeclaration()) { 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) { // 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); } else if (subnode.isHeterogeneousList()) { DOMUtil.setNamespaceDeclarations(topElement, getNamespacePrefixMapper().getNamespacesDeclaredByDefault()); serializeExplicitList((ListXNode) subnode, topElement); } else { throw new SchemaException("Sub-root xnode is not a map nor an explicit list, cannot serialize to XML (it is "+subnode+")"); } return topElement; } Element serializeToElement(MapXNode xmap, QName elementName) throws SchemaException { initialize(); Element element = createElement(elementName, null); 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(); 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); 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; if (xlist.isHeterogeneousList()) { Element element = createElement(elementName, parentElement); serializeExplicitList(xlist, element); parentElement.appendChild(element); } else { for (XNode xsubsubnode : xlist) { serializeSubnode(xsubsubnode, parentElement, elementName); } } } else if (xsubnode instanceof SchemaXNode) { serializeSchema((SchemaXNode)xsubnode, parentElement); } else { throw new IllegalArgumentException("Unknown subnode "+xsubnode); } } private void serializeExplicitList(ListXNode list, Element parent) throws SchemaException { for (XNode node : list) { if (node.getElementName() == null) { throw new SchemaException("In a list, there are both nodes with element names and nodes without them: " + list); } serializeSubnode(node, parent, node.getElementName()); } DOMUtil.setAttributeValue(parent, DOMUtil.IS_LIST_ATTRIBUTE_NAME, "true"); } 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.PrismContextImpl.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 (ItemPathType.COMPLEX_TYPE.equals(typeQName)) { //ItemPathType itemPathType = //ItemPathType.asItemPathType(xprim.getValue()); // TODO fix this hack ItemPathType itemPathType = (ItemPathType) xprim.getValue(); if (itemPathType != null) { if (asAttribute) { throw new UnsupportedOperationException("Serializing ItemPath as an attribute is not supported yet"); } XPathHolder holder = new XPathHolder(itemPathType.getItemPath()); 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 (DOMUtil.XSD_QNAME.equals(typeQName)) { 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 */ @NotNull 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); } }