com.evolveum.midpoint.prism.schema.SchemaToDomProcessor.java Source code

Java tutorial

Introduction

Here is the source code for com.evolveum.midpoint.prism.schema.SchemaToDomProcessor.java

Source

/*
 * Copyright (c) 2010-2013 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.schema;

import static com.evolveum.midpoint.prism.PrismConstants.*;
import static javax.xml.XMLConstants.W3C_XML_SCHEMA_NS_URI;

import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import javax.xml.namespace.QName;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.apache.commons.lang.StringUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import com.evolveum.midpoint.prism.ComplexTypeDefinition;
import com.evolveum.midpoint.prism.Definition;
import com.evolveum.midpoint.prism.ItemDefinition;
import com.evolveum.midpoint.prism.PrismContainerDefinition;
import com.evolveum.midpoint.prism.PrismContext;
import com.evolveum.midpoint.prism.PrismPropertyDefinition;
import com.evolveum.midpoint.prism.PrismReferenceDefinition;
import com.evolveum.midpoint.prism.xml.DynamicNamespacePrefixMapper;
import com.evolveum.midpoint.prism.xml.XmlTypeConverter;
import com.evolveum.midpoint.prism.xml.XsdTypeMapper;
import com.evolveum.midpoint.util.DOMUtil;
import com.evolveum.midpoint.util.DebugUtil;
import com.evolveum.midpoint.util.exception.SchemaException;
import com.evolveum.midpoint.util.logging.Trace;
import com.evolveum.midpoint.util.logging.TraceManager;
import com.sun.xml.bind.marshaller.NamespacePrefixMapper;
import com.sun.xml.xsom.XSParticle;

/**
 * Takes a midPoint Schema definition and produces a XSD schema (in a DOM form).
 * 
 * Great pains were taken to make sure that the output XML is "nice" and human readable.
 * E.g. the namespace prefixes are unified using the definitions in SchemaRegistry.
 * Please do not ruin this if you would update this class.
 * 
 * Single use class. Not thread safe. Create new instance for each run.
 * 
 * @author lazyman
 * @author Radovan Semancik
 */
public class SchemaToDomProcessor {

    private static final Trace LOGGER = TraceManager.getTrace(SchemaToDomProcessor.class);
    private static final String MAX_OCCURS_UNBOUNDED = "unbounded";
    private boolean attributeQualified = false;
    private PrismContext prismContext;
    private DynamicNamespacePrefixMapper namespacePrefixMapper;
    private PrismSchema schema;
    private Element rootXsdElement;
    private Set<String> importNamespaces;
    private Document document;

    SchemaToDomProcessor() {
        importNamespaces = new HashSet<String>();
    }

    public PrismContext getPrismContext() {
        return prismContext;
    }

    public void setPrismContext(PrismContext prismContext) {
        this.prismContext = prismContext;
    }

    void setAttributeQualified(boolean attributeQualified) {
        this.attributeQualified = attributeQualified;
    }

    public DynamicNamespacePrefixMapper getNamespacePrefixMapper() {
        return namespacePrefixMapper;
    }

    public void setNamespacePrefixMapper(DynamicNamespacePrefixMapper namespacePrefixMapper) {
        this.namespacePrefixMapper = namespacePrefixMapper;
    }

    private SchemaDefinitionFactory getDefinitionFactory() {
        return prismContext.getDefinitionFactory();
    }

    private String getNamespace() {
        return schema.getNamespace();
    }

    private boolean isMyNamespace(QName qname) {
        return getNamespace().equals(qname.getNamespaceURI());
    }

    /**
     * Main entry point.
     * 
     * @param schema midPoint schema
     * @return XSD schema in DOM form
     * @throws SchemaException error parsing the midPoint schema or converting values
     */
    Document parseSchema(PrismSchema schema) throws SchemaException {
        if (schema == null) {
            throw new IllegalArgumentException("Schema can't be null.");
        }
        this.schema = schema;

        try {

            init();

            // Process complex types first. 
            Collection<ComplexTypeDefinition> complexTypes = schema.getDefinitions(ComplexTypeDefinition.class);
            for (ComplexTypeDefinition complexTypeDefinition : complexTypes) {
                addComplexTypeDefinition(complexTypeDefinition, document.getDocumentElement());
            }

            Collection<Definition> definitions = schema.getDefinitions();
            for (Definition definition : definitions) {

                if (definition instanceof PrismContainerDefinition) {
                    // Add property container definition. This will add <complexType> and <element> definitions to XSD
                    addContainerDefinition((PrismContainerDefinition) definition, document.getDocumentElement(),
                            document.getDocumentElement());

                } else if (definition instanceof PrismPropertyDefinition) {
                    // Add top-level property definition. It will create <element> XSD definition
                    addPropertyDefinition((PrismPropertyDefinition) definition, document.getDocumentElement());

                } else if (definition instanceof ComplexTypeDefinition) {
                    // Skip this. Already processed above.

                } else {
                    throw new IllegalArgumentException(
                            "Encountered unsupported definition in schema: " + definition);
                }

                // TODO: process unprocessed ComplexTypeDefinitions
            }

            // Add import definition. These were accumulated during previous processing.
            addImports();

        } catch (Exception ex) {
            throw new SchemaException("Couldn't parse schema, reason: " + ex.getMessage(), ex);
        }
        return document;
    }

    /**
     * Adds XSD definitions from PropertyContainerDefinition. This is complexType and element.
     * If the property container is an ResourceObjectDefinition, it will add only annotated
     * complexType definition.
     * 
     * @param definition PropertyContainerDefinition to process
     * @param parent element under which the XSD definition will be added
     */
    private void addContainerDefinition(PrismContainerDefinition definition, Element elementParent,
            Element complexTypeParent) {

        ComplexTypeDefinition complexTypeDefinition = definition.getComplexTypeDefinition();

        if (complexTypeDefinition != null &&
        // Check if the complex type is a top-level complex type. If it is then it was already processed and we can skip it
                schema.findComplexTypeDefinition(complexTypeDefinition.getTypeName()) == null &&
                // If the definition is not in this schema namespace then skip it. It is only a "ref"
                getNamespace().equals(complexTypeDefinition.getTypeName().getNamespaceURI())) {
            addComplexTypeDefinition(complexTypeDefinition, complexTypeParent);
        }

        Element elementElement = addElementDefinition(definition.getName(), definition.getTypeName(),
                definition.getMinOccurs(), definition.getMaxOccurs(), elementParent);

        if (complexTypeDefinition == null || !complexTypeDefinition.isContainerMarker()) {
            // Need to add a:container annotation to the element as the complex type does not have it
            addAnnotationToDefinition(elementElement, A_PROPERTY_CONTAINER);
        }
    }

    /**
     * Adds XSD element definition created from the midPoint PropertyDefinition.
     * @param definition midPoint PropertyDefinition
     * @param parent element under which the definition will be added
     */
    private void addPropertyDefinition(PrismPropertyDefinition definition, Element parent) {
        Element property = createElement(new QName(W3C_XML_SCHEMA_NS_URI, "element"));
        // Add to document first, so following methods will be able to resolve namespaces
        parent.appendChild(property);

        String attrNamespace = definition.getName().getNamespaceURI();
        if (attrNamespace != null && attrNamespace.equals(getNamespace())) {
            setAttribute(property, "name", definition.getName().getLocalPart());
            setQNameAttribute(property, "type", definition.getTypeName());
        } else {
            setQNameAttribute(property, "ref", definition.getName());
        }

        if (definition.getMinOccurs() != 1) {
            setAttribute(property, "minOccurs", Integer.toString(definition.getMinOccurs()));
        }

        if (definition.getMaxOccurs() != 1) {
            String maxOccurs = definition.getMaxOccurs() == XSParticle.UNBOUNDED ? MAX_OCCURS_UNBOUNDED
                    : Integer.toString(definition.getMaxOccurs());
            setAttribute(property, "maxOccurs", maxOccurs);
        }

        Element annotation = createElement(new QName(W3C_XML_SCHEMA_NS_URI, "annotation"));
        property.appendChild(annotation);
        Element appinfo = createElement(new QName(W3C_XML_SCHEMA_NS_URI, "appinfo"));
        annotation.appendChild(appinfo);

        addCommonDefinitionAnnotations(definition, appinfo);

        if (!definition.canAdd() || !definition.canRead() || !definition.canModify()) {
            // read-write-create attribute is the default. If any of this flags is missing, we must
            // add appropriate annotations.
            if (definition.canAdd()) {
                addAnnotation(A_ACCESS, A_ACCESS_CREATE, appinfo);
            }
            if (definition.canRead()) {
                addAnnotation(A_ACCESS, A_ACCESS_READ, appinfo);
            }
            if (definition.canModify()) {
                addAnnotation(A_ACCESS, A_ACCESS_UPDATE, appinfo);
            }
        }

        if (definition.isIndexed() != null) {
            addAnnotation(A_INDEXED, XmlTypeConverter.toXmlTextContent(definition.isIndexed(), A_INDEXED), appinfo);
        }

        SchemaDefinitionFactory definitionFactory = getDefinitionFactory();
        definitionFactory.addExtraPropertyAnnotations(definition, appinfo, this);

        if (!appinfo.hasChildNodes()) {
            // remove unneeded <annotation> element
            property.removeChild(annotation);
        }
    }

    /**
     * Adds XSD element definition created from the PrismReferenceDefinition.
     * TODO: need to finish
     */
    private void addReferenceDefinition(PrismReferenceDefinition definition, Element parent) {
        Element property = createElement(new QName(W3C_XML_SCHEMA_NS_URI, "element"));
        // Add to document first, so following methods will be able to resolve namespaces
        parent.appendChild(property);

        String attrNamespace = definition.getName().getNamespaceURI();
        if (attrNamespace != null && attrNamespace.equals(getNamespace())) {
            setAttribute(property, "name", definition.getName().getLocalPart());
            setQNameAttribute(property, "type", definition.getTypeName());
        } else {
            setQNameAttribute(property, "ref", definition.getName());
        }

        if (definition.getCompositeObjectElementName() == null) {
            setMultiplicityAttribute(property, "minOccurs", 0);
        }
        setMultiplicityAttribute(property, "maxOccurs", definition.getMaxOccurs());

        // Add annotations
        Element annotation = createElement(new QName(W3C_XML_SCHEMA_NS_URI, "annotation"));
        property.appendChild(annotation);
        Element appinfo = createElement(new QName(W3C_XML_SCHEMA_NS_URI, "appinfo"));
        annotation.appendChild(appinfo);

        addAnnotation(A_OBJECT_REFERENCE, appinfo);

        if (definition.getTargetTypeName() != null) {
            addAnnotation(A_OBJECT_REFERENCE_TARGET_TYPE, definition.getTargetTypeName(), appinfo);
        }

        if (definition.getCompositeObjectElementName() == null) {
            return;
        }

        property = createElement(new QName(W3C_XML_SCHEMA_NS_URI, "element"));
        // Add to document first, so following methods will be able to resolve namespaces
        parent.appendChild(property);

        QName elementName = definition.getCompositeObjectElementName();
        attrNamespace = elementName.getNamespaceURI();
        if (attrNamespace != null && attrNamespace.equals(getNamespace())) {
            setAttribute(property, "name", elementName.getLocalPart());
            setQNameAttribute(property, "type", definition.getTargetTypeName());
        } else {
            setQNameAttribute(property, "ref", elementName);
        }

        setMultiplicityAttribute(property, "minOccurs", 0);
        setMultiplicityAttribute(property, "maxOccurs", definition.getMaxOccurs());

        // Add annotations
        annotation = createElement(new QName(W3C_XML_SCHEMA_NS_URI, "annotation"));
        property.appendChild(annotation);
        appinfo = createElement(new QName(W3C_XML_SCHEMA_NS_URI, "appinfo"));
        annotation.appendChild(appinfo);

        addAnnotation(A_OBJECT_REFERENCE, definition.getName(), appinfo);
        if (definition.isComposite()) {
            addAnnotation(A_COMPOSITE, definition.isComposite(), appinfo);
        }

        SchemaDefinitionFactory definitionFactory = getDefinitionFactory();
        definitionFactory.addExtraReferenceAnnotations(definition, appinfo, this);

    }

    /**
     * Adds XSD element definition.
     * @param name element QName
     * @param typeName element type QName
     * @param parent element under which the definition will be added
     */
    private Element addElementDefinition(QName name, QName typeName, int minOccurs, int maxOccurs, Element parent) {
        Element elementDef = createElement(new QName(W3C_XML_SCHEMA_NS_URI, "element"));
        parent.appendChild(elementDef);

        if (isMyNamespace(name)) {
            setAttribute(elementDef, "name", name.getLocalPart());

            if (typeName.equals(DOMUtil.XSD_ANY)) {
                addSequenceXsdAnyDefinition(elementDef);
            } else {
                setQNameAttribute(elementDef, "type", typeName);
            }
        } else {
            // Need to create "ref" instead of "name"
            setAttribute(elementDef, "ref", name);
            if (typeName != null) {
                // Type cannot be stored directly, XSD does not allow it with "ref"s.
                addAnnotationToDefinition(elementDef, A_TYPE, typeName);
            }
        }

        setMultiplicityAttribute(elementDef, "minOccurs", minOccurs);
        setMultiplicityAttribute(elementDef, "maxOccurs", maxOccurs);

        return elementDef;
    }

    private void addSequenceXsdAnyDefinition(Element elementDef) {
        Element complexContextElement = createElement(new QName(W3C_XML_SCHEMA_NS_URI, "complexType"));
        elementDef.appendChild(complexContextElement);
        Element sequenceElement = createElement(new QName(W3C_XML_SCHEMA_NS_URI, "sequence"));
        complexContextElement.appendChild(sequenceElement);
        addXsdAnyDefinition(sequenceElement);
    }

    private void addXsdAnyDefinition(Element elementDef) {
        Element anyElement = createElement(new QName(W3C_XML_SCHEMA_NS_URI, "any"));
        elementDef.appendChild(anyElement);
        setAttribute(anyElement, "namespace", "##other");
        setAttribute(anyElement, "minOccurs", "0");
        setAttribute(anyElement, "maxOccurs", "unbounded");
        setAttribute(anyElement, "processContents", "lax");
    }

    /**
     * Adds XSD complexType definition from the midPoint Schema ComplexTypeDefinion object
     * @param definition midPoint Schema ComplexTypeDefinion object
     * @param parent element under which the definition will be added
     * @return created (and added) XSD complexType definition
     */
    private Element addComplexTypeDefinition(ComplexTypeDefinition definition, Element parent) {

        if (definition == null) {
            // Nothing to do
            return null;
        }
        if (definition.getTypeName() == null) {
            throw new UnsupportedOperationException("Anonymous complex types as containers are not supported yet");
        }

        Element complexType = createElement(new QName(W3C_XML_SCHEMA_NS_URI, "complexType"));
        parent.appendChild(complexType);
        // "typeName" should be used instead of "name" when defining a XSD type
        setAttribute(complexType, "name", definition.getTypeName().getLocalPart());
        Element annotation = createElement(new QName(W3C_XML_SCHEMA_NS_URI, "annotation"));
        complexType.appendChild(annotation);

        Element containingElement = complexType;
        if (definition.getSuperType() != null) {
            Element complexContent = createElement(new QName(W3C_XML_SCHEMA_NS_URI, "complexContent"));
            complexType.appendChild(complexContent);
            Element extension = createElement(new QName(W3C_XML_SCHEMA_NS_URI, "extension"));
            complexContent.appendChild(extension);
            setQNameAttribute(extension, "base", definition.getSuperType());
            containingElement = extension;
        }

        Element sequence = createElement(new QName(W3C_XML_SCHEMA_NS_URI, "sequence"));
        containingElement.appendChild(sequence);

        Collection<? extends ItemDefinition> definitions = definition.getDefinitions();
        for (ItemDefinition def : definitions) {
            if (def instanceof PrismPropertyDefinition) {
                addPropertyDefinition((PrismPropertyDefinition) def, sequence);
            } else if (def instanceof PrismContainerDefinition) {
                PrismContainerDefinition contDef = (PrismContainerDefinition) def;
                addContainerDefinition(contDef, sequence, parent);
            } else if (def instanceof PrismReferenceDefinition) {
                addReferenceDefinition((PrismReferenceDefinition) def, sequence);
            } else {
                throw new IllegalArgumentException("Uknown definition " + def + "(" + def.getClass().getName()
                        + ") in complex type definition " + def);
            }
        }

        if (definition.isXsdAnyMarker()) {
            addXsdAnyDefinition(sequence);
        }

        Element appinfo = createElement(new QName(W3C_XML_SCHEMA_NS_URI, "appinfo"));
        annotation.appendChild(appinfo);

        if (definition.isObjectMarker()) {
            // annotation: propertyContainer
            addAnnotation(A_OBJECT, definition.getDisplayName(), appinfo);
        } else if (definition.isContainerMarker()) {
            // annotation: propertyContainer
            addAnnotation(A_PROPERTY_CONTAINER, definition.getDisplayName(), appinfo);
        }

        addCommonDefinitionAnnotations(definition, appinfo);

        SchemaDefinitionFactory definitionFactory = getDefinitionFactory();
        definitionFactory.addExtraComplexTypeAnnotations(definition, appinfo, this);

        if (!appinfo.hasChildNodes()) {
            // remove unneeded <annotation> element
            complexType.removeChild(annotation);
        }

        return complexType;
    }

    private void addCommonDefinitionAnnotations(Definition definition, Element appinfoElement) {
        if (definition.isIgnored()) {
            addAnnotation(A_IGNORE, "true", appinfoElement);
        }

        if ((definition instanceof ItemDefinition) && ((ItemDefinition) definition).isOperational()) {
            addAnnotation(A_OPERATIONAL, "true", appinfoElement);
        }

        if (definition.getDisplayName() != null) {
            addAnnotation(A_DISPLAY_NAME, definition.getDisplayName(), appinfoElement);
        }

        if (definition.getDisplayOrder() != null) {
            addAnnotation(A_DISPLAY_ORDER, definition.getDisplayOrder().toString(), appinfoElement);
        }

        if (definition.getHelp() != null) {
            addAnnotation(A_HELP, definition.getHelp(), appinfoElement);
        }
    }

    /**
     * Add generic annotation element.
     * @param qname QName of the element
     * @param value string value of the element
     * @param parent element under which the definition will be added
     * @return created XSD element
     */
    public Element addAnnotation(QName qname, String value, Element parent) {
        Element annotation = createElement(qname);
        parent.appendChild(annotation);
        if (value != null) {
            annotation.setTextContent(value);
        }
        return annotation;
    }

    public Element addAnnotation(QName qname, boolean value, Element parent) {
        Element annotation = createElement(qname);
        parent.appendChild(annotation);
        annotation.setTextContent(Boolean.toString(value));
        return annotation;
    }

    public Element addAnnotation(QName qname, Element parent) {
        Element annotation = createElement(qname);
        parent.appendChild(annotation);
        return annotation;
    }

    public Element addAnnotation(QName qname, QName value, Element parent) {
        Element annotation = createElement(qname);
        parent.appendChild(annotation);
        if (value != null) {
            DOMUtil.setQNameValue(annotation, value);
        }
        return annotation;
    }

    private void addAnnotationToDefinition(Element definitionElement, QName qname) {
        addAnnotationToDefinition(definitionElement, qname, null);
    }

    private void addAnnotationToDefinition(Element definitionElement, QName qname, QName value) {
        Element annotationElement = getOrCreateElement(new QName(W3C_XML_SCHEMA_NS_URI, "annotation"),
                definitionElement);
        Element appinfoElement = getOrCreateElement(new QName(W3C_XML_SCHEMA_NS_URI, "appinfo"), annotationElement);
        if (value == null) {
            addAnnotation(qname, appinfoElement);
        } else {
            addAnnotation(qname, value, appinfoElement);
        }
    }

    private Element getOrCreateElement(QName qName, Element parentElement) {
        NodeList elements = parentElement.getElementsByTagNameNS(qName.getNamespaceURI(), qName.getLocalPart());
        if (elements.getLength() == 0) {
            Element element = createElement(qName);
            Element refChild = DOMUtil.getFirstChildElement(parentElement);
            parentElement.insertBefore(element, refChild);
            return element;
        }
        return (Element) elements.item(0);
    }

    /**
     * Adds annotation that points to another element (ususaly a property).
     * @param qname QName of the element
     * @param value Qname of the target element (property QName)
     * @param parent parent element under which the definition will be added
     * @return created XSD element
     */
    public Element addRefAnnotation(QName qname, QName value, Element parent) {
        Element element = createElement(qname);
        parent.appendChild(element);
        //old way: setQNameAttribute(access, "ref", value);
        DOMUtil.setQNameValue(element, value);
        return element;
    }

    /**
     * Create schema XSD DOM document.
     */
    private void init() throws ParserConfigurationException {

        if (namespacePrefixMapper == null) {
            // TODO: clone?
            namespacePrefixMapper = prismContext.getSchemaRegistry().getNamespacePrefixMapper();
        }

        // We don't want the "tns" prefix to be kept in the mapper
        namespacePrefixMapper = namespacePrefixMapper.clone();
        namespacePrefixMapper.registerPrefixLocal(getNamespace(), "tns");

        if (LOGGER.isTraceEnabled()) {
            LOGGER.trace("Using namespace prefix mapper to serialize schema:\n{}",
                    DebugUtil.dump(namespacePrefixMapper));
        }

        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        dbf.setNamespaceAware(true);
        dbf.setValidating(false);
        DocumentBuilder db = dbf.newDocumentBuilder();

        document = db.newDocument();
        Element root = createElement(new QName(W3C_XML_SCHEMA_NS_URI, "schema"));
        document.appendChild(root);

        rootXsdElement = document.getDocumentElement();
        setAttribute(rootXsdElement, "targetNamespace", getNamespace());
        setAttribute(rootXsdElement, "elementFormDefault", "qualified");

        DOMUtil.setNamespaceDeclaration(rootXsdElement, "tns", getNamespace());

        if (attributeQualified) {
            setAttribute(rootXsdElement, "attributeFormDefault", "qualified");
        }
    }

    /**
     * Create DOM document with a root element.
     */
    private Document createDocument(QName name) throws ParserConfigurationException {

        return document;
    }

    /**
     * Create XML element with the correct namespace prefix and namespace definition.
     * @param qname element QName
     * @return created DOM element
     */
    public Element createElement(QName qname) {
        QName qnameWithPrefix = namespacePrefixMapper.setQNamePrefix(qname);
        addToImport(qname.getNamespaceURI());
        if (rootXsdElement != null) {
            return DOMUtil.createElement(document, qnameWithPrefix, rootXsdElement, rootXsdElement);
        } else {
            // This is needed otherwise the root element itself could not be created
            return DOMUtil.createElement(document, qnameWithPrefix);
        }
    }

    /**
     * Set attribute in the DOM element to a string value.
     * @param element element where to set attribute
     * @param attrName attribute name (String)
     * @param attrValue attribute value (String)
     */
    private void setAttribute(Element element, String attrName, String attrValue) {
        setAttribute(element, new QName(W3C_XML_SCHEMA_NS_URI, attrName), attrValue);
    }

    private void setAttribute(Element element, String attrName, QName attrValue) {
        setAttribute(element, new QName(W3C_XML_SCHEMA_NS_URI, attrName), attrValue);
    }

    private void setMultiplicityAttribute(Element element, String attrName, int attrValue) {
        if (attrValue == 1) {
            return;
        }
        setAttribute(element, attrName, XsdTypeMapper.multiplicityToString(attrValue));
    }

    /**
     * Set attribute in the DOM element to a string value.
     * @param element element element where to set attribute
     * @param attr attribute name (QName)
     * @param attrValue attribute value (String)
     */
    private void setAttribute(Element element, QName attr, String attrValue) {
        if (attributeQualified) {
            element.setAttributeNS(attr.getNamespaceURI(), attr.getLocalPart(), attrValue);
            addToImport(attr.getNamespaceURI());
        } else {
            element.setAttribute(attr.getLocalPart(), attrValue);
        }
    }

    private void setAttribute(Element element, QName attr, QName attrValue) {
        if (attributeQualified) {
            DOMUtil.setQNameAttribute(element, attr, attrValue, rootXsdElement);
            addToImport(attr.getNamespaceURI());
        } else {
            DOMUtil.setQNameAttribute(element, attr.getLocalPart(), attrValue, rootXsdElement);
        }
    }

    /**
     * Set attribute in the DOM element to a QName value. This will make sure that the
     * appropriate namespace definition for the QName exists.
     * 
     * @param element element element element where to set attribute
     * @param attrName attribute name (String)
     * @param value attribute value (Qname)
     */
    private void setQNameAttribute(Element element, String attrName, QName value) {
        QName valueWithPrefix = namespacePrefixMapper.setQNamePrefix(value);
        DOMUtil.setQNameAttribute(element, attrName, valueWithPrefix, rootXsdElement);
        addToImport(value.getNamespaceURI());
    }

    /**
     * Make sure that the namespace will be added to import definitions.
     * @param namespace namespace to import
     */
    private void addToImport(String namespace) {
        if (!importNamespaces.contains(namespace)) {
            importNamespaces.add(namespace);
        }
    }

    /**
     * Adds import definition to XSD.
     * It adds imports of namespaces that accumulated during schema processing in the importNamespaces list.
     * @param schema
     */
    private void addImports() {
        for (String namespace : importNamespaces) {
            if (W3C_XML_SCHEMA_NS_URI.equals(namespace)) {
                continue;
            }

            if (getNamespace().equals(namespace)) {
                //we don't want to import target namespace
                continue;
            }

            rootXsdElement.insertBefore(createImport(namespace), rootXsdElement.getFirstChild());
        }
    }

    /**
     * Create single import XSD element.
     */
    private Element createImport(String namespace) {
        Element element = createElement(new QName(W3C_XML_SCHEMA_NS_URI, "import"));
        setAttribute(element, "namespace", namespace);
        return element;
    }

}