All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.nakedobjects.runtime.snapshot.XmlSchema Maven / Gradle / Ivy

package org.nakedobjects.runtime.snapshot;

import java.util.Enumeration;
import java.util.Hashtable;

import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.NodeList;


/**
 * Represents the schema for the derived snapshot.
 */
public final class XmlSchema {

    private final String prefix;
    private final String uriBase;
    private String uri;

    private final NofMetaModel nofMeta;
    private final XsMetaModel xsMeta;
    private final Helper helper;

    /**
     * The base part of the namespace prefix to use if none explicitly supplied in the constructor.
     */
    public final static String DEFAULT_PREFIX = "app";

    public XmlSchema() {
        this(NofMetaModel.DEFAULT_URI_BASE, XmlSchema.DEFAULT_PREFIX);
    }

    /**
     * @param uriBase
     *            the prefix for the application namespace's URIs
     * @param prefix
     *            the prefix for the application namespace's prefix
     */
    public XmlSchema(final String uriBase, final String prefix) {
        this.nofMeta = new NofMetaModel();
        this.xsMeta = new XsMetaModel();
        this.helper = new Helper();

        final String base = new Helper().trailingSlash(uriBase);
        if (XsMetaModel.W3_ORG_XMLNS_URI.equals(base)) {
            throw new IllegalArgumentException("Namespace URI reserved for w3.org XMLNS namespace");
        }
        if (XsMetaModel.W3_ORG_XMLNS_PREFIX.equals(prefix)) {
            throw new IllegalArgumentException("Namespace prefix reserved for w3.org XMLNS namespace.");
        }
        if (XsMetaModel.W3_ORG_XS_URI.equals(base)) {
            throw new IllegalArgumentException("Namespace URI reserved for w3.org XML schema namespace.");
        }
        if (XsMetaModel.W3_ORG_XS_PREFIX.equals(prefix)) {
            throw new IllegalArgumentException("Namespace prefix reserved for w3.org XML schema namespace.");
        }
        if (XsMetaModel.W3_ORG_XSI_URI.equals(base)) {
            throw new IllegalArgumentException("Namespace URI reserved for w3.org XML schema-instance namespace.");
        }
        if (XsMetaModel.W3_ORG_XSI_PREFIX.equals(prefix)) {
            throw new IllegalArgumentException("Namespace prefix reserved for w3.org XML schema-instance namespace.");
        }
        if (NofMetaModel.NOF_METAMODEL_NS_URI.equals(base)) {
            throw new IllegalArgumentException("Namespace URI reserved for NOF metamodel namespace.");
        }
        if (NofMetaModel.NOF_METAMODEL_NS_PREFIX.equals(prefix)) {
            throw new IllegalArgumentException("Namespace prefix reserved for NOF metamodel namespace.");
        }
        this.uriBase = base;
        this.prefix = prefix;
    }

    /**
     * The base of the Uri in use. All namespaces are concatenated with this.
     * 
     * The namespace string will be the concatenation of the plus the package name of the class of the object
     * being referenced.
     * 
     * If not specified in the constructor, then {@link #DEFAULT_URI_PREFIX} is used.
     */
    public String getUriBase() {
        return uriBase;
    }

    /**
     * Returns the namespace URI for the class.
     */
    void setUri(final String fullyQualifiedClassName) {
        if (uri != null) {
            throw new IllegalStateException("URI has already been specified.");
        }
        this.uri = getUriBase() + helper.packageNameFor(fullyQualifiedClassName) + "/"
                + helper.classNameFor(fullyQualifiedClassName);
    }

    /**
     * The URI of the application namespace.
     * 
     * The value returned will be null until a {@link Snapshot} is created.
     */
    public String getUri() {
        if (uri == null) {
            throw new IllegalStateException("URI has not been specified.");
        }
        return uri;
    }

    /**
     * The prefix to the namespace for the application.
     */
    public String getPrefix() {
        return this.prefix;
    }

    /**
     * Creates an element with the specified localName, in the appropriate namespace for the NOS.
     * 
     * If necessary the namespace definition is added to the root element of the doc used to create the
     * element. The element is not parented but to avoid an error can only be added as a child of another
     * element in the same doc.
     */
    Element createElement(
            final Document doc,
            final String localName,
            final String fullyQualifiedClassName,
            final String singularName,
            final String pluralName) {
        final Element element = doc.createElementNS(getUri(), getPrefix() + ":" + localName);
        element.setAttributeNS(NofMetaModel.NOF_METAMODEL_NS_URI, "nof:fqn", fullyQualifiedClassName);
        element.setAttributeNS(NofMetaModel.NOF_METAMODEL_NS_URI, "nof:singular", singularName);
        element.setAttributeNS(NofMetaModel.NOF_METAMODEL_NS_URI, "nof:plural", pluralName);
        nofMeta.addNamespace(element); // good a place as any

        addNamespace(element, getPrefix(), getUri());
        return element;
    }

    /**
     * Sets the target namespace for the XSD document to a URI derived from the fully qualified class name of
     * the supplied object
     */
    void setTargetNamespace(final Document xsdDoc, final String fullyQualifiedClassName) {

        final Element xsSchemaElement = xsdDoc.getDocumentElement();
        if (xsSchemaElement == null) {
            throw new IllegalArgumentException("XSD Document must have  element attached");
        }

        // targetNamespace="http://www.nakedobjects.org/ns/app/
        xsSchemaElement.setAttribute("targetNamespace", getUri());

        addNamespace(xsSchemaElement, getPrefix(), getUri());
    }

    /**
     * Creates an <xs:element> element defining the presence of the named element representing a class
     */
    Element createXsElementForNofClass(
            final Document xsdDoc,
            final Element element,
            final boolean addCardinality,
            final Hashtable extensions) {

        // gather details from XML element
        final String localName = element.getLocalName();

        // 
        // 
        // 
        // 
        // 
        // 
        // 
        // 
        // 
        // 
        // 
        // 

        // xs:element/@name="class name"
        // add to XML schema as a global attribute
        final Element xsElementForNofClassElement = xsMeta.createXsElementElement(xsdDoc, localName, addCardinality);

        // xs:element/xs:complexType
        // xs:element/xs:complexType/xs:sequence
        final Element xsComplexTypeElement = xsMeta.complexTypeFor(xsElementForNofClassElement);
        final Element xsSequenceElement = xsMeta.sequenceFor(xsComplexTypeElement);

        // xs:element/xs:complexType/xs:sequence/xs:element ref="nof:title"
        final Element xsTitleElement = xsMeta.createXsElement(helper.docFor(xsSequenceElement), "element");
        xsTitleElement.setAttribute("ref", NofMetaModel.NOF_METAMODEL_NS_PREFIX + ":" + "title");
        xsSequenceElement.appendChild(xsTitleElement);
        xsMeta.setXsCardinality(xsTitleElement, 0, 1);

        // xs:element/xs:complexType/xs:sequence/xs:element ref="extensions"
        addXsElementForAppExtensions(xsSequenceElement, extensions);

        // xs:element/xs:complexType/xs:attribute ...
        xsMeta.addXsNofFeatureAttributeElements(xsComplexTypeElement, "class");
        xsMeta.addXsNofAttribute(xsComplexTypeElement, "oid");
        xsMeta.addXsNofAttribute(xsComplexTypeElement, "fqn");
        xsMeta.addXsNofAttribute(xsComplexTypeElement, "singular");
        xsMeta.addXsNofAttribute(xsComplexTypeElement, "plural");
        xsMeta.addXsNofAttribute(xsComplexTypeElement, "annotation");

        Place.setXsdElement(element, xsElementForNofClassElement);

        return xsElementForNofClassElement;
    }

    /**
     * Creates an xs:element element to represent a collection of application-defined
     * extensions
     * 
     * The returned element should be appended to xs:sequence element of the xs:element
     * representing the type of the owning object.
     */
    void addXsElementForAppExtensions(final Element parentXsElementElement, final Hashtable extensions) {

        if (extensions.size() == 0) {
            return;
        }

        // 
        // 
        // 
        // 
        // 
        // ...
        // 
        // 
        // 
        // 

        // xs:element name="nof-extensions"
        // xs:element/xs:complexType/xs:sequence
        final Element xsExtensionsSequenceElement = addExtensionsElement(parentXsElementElement);

        addExtensionElements(xsExtensionsSequenceElement, extensions);

        return;
    }

    /**
     * Adds an nof-extensions element and a complexType and sequence elements underneath.
     * 
     * 

* Returns the sequence element so that it can be appended to. */ private Element addExtensionsElement(final Element parentXsElement) { final Element xsExtensionsElementElement = xsMeta .createXsElementElement(helper.docFor(parentXsElement), "nof-extensions"); parentXsElement.appendChild(xsExtensionsElementElement); // xs:element/xs:complexType/xs:sequence/xs:element/xs:complexType/xs:sequence final Element xsExtensionsComplexTypeElement = xsMeta.complexTypeFor(xsExtensionsElementElement); final Element xsExtensionsSequenceElement = xsMeta.sequenceFor(xsExtensionsComplexTypeElement); return xsExtensionsSequenceElement; } private String shortName(final String className) { final int lastPeriodIdx = className.lastIndexOf('.'); if (lastPeriodIdx < 0) { return className; } return className.substring(lastPeriodIdx + 1); } /** * Creates an xs:element element to represent a value field in a class. * * The returned element should be appended to xs:sequence element of the xs:element * representing the type of the owning object. */ Element createXsElementForNofValue( final Element parentXsElementElement, final Element xmlValueElement, final Hashtable extensions) { // gather details from XML element final String datatype = xmlValueElement.getAttributeNS(NofMetaModel.NOF_METAMODEL_NS_URI, "datatype"); final String fieldName = xmlValueElement.getLocalName(); // // // // // // // // // // // // ... // // // // // // // // // // // // // // // xs:element/xs:complexType/xs:sequence final Element parentXsComplexTypeElement = xsMeta.complexTypeFor(parentXsElementElement); final Element parentXsSequenceElement = xsMeta.sequenceFor(parentXsComplexTypeElement); // xs:element/xs:complexType/xs:sequence/xs:element name="%%field object%" final Element xsFieldElementElement = xsMeta.createXsElementElement(helper.docFor(parentXsSequenceElement), fieldName); parentXsSequenceElement.appendChild(xsFieldElementElement); // xs:element/xs:complexType/xs:sequence/xs:element/xs:complexType final Element xsFieldComplexTypeElement = xsMeta.complexTypeFor(xsFieldElementElement); // NEW CODE TO SUPPORT EXTENSIONS; // uses a complexType/sequence // xs:element/xs:complexType/xs:sequence/xs:element/xs:complexType/xs:sequence final Element xsFieldSequenceElement = xsMeta.sequenceFor(xsFieldComplexTypeElement); // xs:element/xs:complexType/xs:sequence/xs:element/xs:complexType/xs:sequence/xs:element // name="nof-extensions" // xs:element/xs:complexType/xs:sequence/xs:element/xs:complexType/xs:sequence/xs:element/xs:complexType/xs:sequence addXsElementForAppExtensions(xsFieldSequenceElement, extensions); xsMeta.addXsNofFeatureAttributeElements(xsFieldComplexTypeElement, "value"); xsMeta.addXsNofAttribute(xsFieldComplexTypeElement, "datatype", datatype); xsMeta.addXsNofAttribute(xsFieldComplexTypeElement, "isEmpty"); xsMeta.addXsNofAttribute(xsFieldComplexTypeElement, "annotation"); // ORIGINAL CODE THAT DIDN'T EXPORT EXTENSIONS // uses a simpleContent // (I've left this code in in case there is a need to regenerate schemas the "old way"). // // // // // // // // // // // // // // // // // // // xs:element/xs:complexType/xs:sequence/xs:element/xs:complexType/xs:simpleContent/xs:extension // Element xsFieldSimpleContentElement = xsMeta.simpleContentFor(xsFieldComplexTypeElement); // Element xsFieldExtensionElement = xsMeta.extensionFor(xsFieldSimpleContentElement, "string"); // xsMeta.addXsNofFeatureAttributeElements(xsFieldExtensionElement, "value"); // xsMeta.addXsNofAttribute(xsFieldExtensionElement, "datatype", datatype); // xsMeta.addXsNofAttribute(xsFieldExtensionElement, "isEmpty"); // xsMeta.addXsNofAttribute(xsFieldExtensionElement, "annotation"); return xsFieldElementElement; } @SuppressWarnings("unchecked") private void addExtensionElements(final Element parentElement, final Hashtable extensions) { for (final Enumeration e = extensions.keys(); e.hasMoreElements();) { final Class extensionClass = (Class) e.nextElement(); final Object extensionObject = extensions.get(extensionClass); // xs:element/xs:complexType/xs:sequence/xs:element/xs:complexType/xs:sequence/xs:element // name="%extensionClassShortName%" final Element xsExtensionElementElement = xsMeta.createXsElementElement(helper.docFor(parentElement), "x-" + shortName(extensionClass.getName())); xsExtensionElementElement.setAttribute("default", extensionObject.toString()); // the value xsExtensionElementElement.setAttribute("minOccurs", "0"); // doesn't need to appear in XML (and // indeed won't) parentElement.appendChild(xsExtensionElementElement); } } /** * Creates an <xs:element> element defining the presence of the named element representing a * reference to a class; appended to xs:sequence element */ Element createXsElementForNofReference( final Element parentXsElementElement, final Element xmlReferenceElement, final String referencedClassName, final Hashtable extensions) { // gather details from XML element final String fieldName = xmlReferenceElement.getLocalName(); // // // // // // // // // // // // // ... // // // // // // // // // // // // // // // // xs:element/xs:complexType/xs:sequence final Element parentXsComplexTypeElement = xsMeta.complexTypeFor(parentXsElementElement); final Element parentXsSequenceElement = xsMeta.sequenceFor(parentXsComplexTypeElement); // xs:element/xs:complexType/xs:sequence/xs:element name="%%field object%" final Element xsFieldElementElement = xsMeta.createXsElementElement(helper.docFor(parentXsSequenceElement), fieldName); parentXsSequenceElement.appendChild(xsFieldElementElement); // xs:element/xs:complexType/xs:sequence/xs:element/xs:complexType/xs:sequence final Element xsFieldComplexTypeElement = xsMeta.complexTypeFor(xsFieldElementElement); final Element xsFieldSequenceElement = xsMeta.sequenceFor(xsFieldComplexTypeElement); // xs:element/xs:complexType/xs:sequence/xs:element/xs:complexType/xs:sequence/xs:element // ref="nof:title" final Element xsFieldTitleElement = xsMeta.createXsElement(helper.docFor(xsFieldSequenceElement), "element"); xsFieldTitleElement.setAttribute("ref", NofMetaModel.NOF_METAMODEL_NS_PREFIX + ":" + "title"); xsFieldSequenceElement.appendChild(xsFieldTitleElement); xsMeta.setXsCardinality(xsFieldTitleElement, 0, 1); // xs:element/xs:complexType/xs:sequence/xs:element/xs:complexType/xs:sequence/xs:element // name="nof-extensions" addXsElementForAppExtensions(xsFieldSequenceElement, extensions); // xs:element/xs:complexType/xs:sequence/xs:element/xs:complexType/xs:sequence/xs:sequence // // placeholder final Element xsReferencedElementSequenceElement = xsMeta.sequenceFor(xsFieldSequenceElement); xsMeta.setXsCardinality(xsReferencedElementSequenceElement, 0, 1); xsMeta.addXsNofFeatureAttributeElements(xsFieldComplexTypeElement, "reference"); xsMeta.addXsNofAttribute(xsFieldComplexTypeElement, "type", "app:" + referencedClassName, false); xsMeta.addXsNofAttribute(xsFieldComplexTypeElement, "isEmpty"); xsMeta.addXsNofAttribute(xsFieldComplexTypeElement, "annotation"); return xsFieldElementElement; } /** * Creates an <xs:element> element defining the presence of the named element representing a * collection in a class; appended to xs:sequence element */ Element createXsElementForNofCollection( final Element parentXsElementElement, final Element xmlCollectionElement, final String referencedClassName, final Hashtable extensions) { // gather details from XML element final String fieldName = xmlCollectionElement.getLocalName(); // // // // // // // // // // // // // // // // // // // xs:element/xs:complexType/xs:sequence final Element parentXsComplexTypeElement = xsMeta.complexTypeFor(parentXsElementElement); final Element parentXsSequenceElement = xsMeta.sequenceFor(parentXsComplexTypeElement); // xs:element/xs:complexType/xs:sequence/xs:element name="%field object%%" final Element xsFieldElementElement = xsMeta.createXsElementElement(helper.docFor(parentXsSequenceElement), fieldName); parentXsSequenceElement.appendChild(xsFieldElementElement); // xs:element/xs:complexType/xs:sequence/xs:element/xs:complexType final Element xsFieldComplexTypeElement = xsMeta.complexTypeFor(xsFieldElementElement); // xs:element/xs:complexType/xs:sequence/xs:element/xs:complexType/xs:sequence final Element xsFieldSequenceElement = xsMeta.sequenceFor(xsFieldComplexTypeElement); // xs:element/xs:complexType/xs:sequence/xs:element/xs:complexType/xs:sequence/xs:element // ref="nof:oids" final Element xsFieldOidsElement = xsMeta.createXsElement(helper.docFor(xsFieldSequenceElement), "element"); xsFieldOidsElement.setAttribute("ref", NofMetaModel.NOF_METAMODEL_NS_PREFIX + ":" + "oids"); xsFieldSequenceElement.appendChild(xsFieldOidsElement); xsMeta.setXsCardinality(xsFieldOidsElement, 0, 1); // extensions addXsElementForAppExtensions(xsFieldSequenceElement, extensions); // // xs:element/xs:complexType/xs:sequence/xs:element/xs:complexType/xs:sequence/xs:choice // Element xsFieldChoiceElement = xsMeta.choiceFor(xsFieldComplexTypeElement); // placeholder // xsMeta.setXsCardinality(xsFieldChoiceElement, 0, Integer.MAX_VALUE); // Element xsFieldTitleElement = addXsNofRefElementElement(xsFieldSequenceElement, "title"); // Element xsReferencedElementSequenceElement = sequenceFor(xsFieldSequenceElement); // setXsCardinality(xsReferencedElementSequenceElement, 0, 1); xsMeta.addXsNofFeatureAttributeElements(xsFieldComplexTypeElement, "collection"); xsMeta.addXsNofAttribute(xsFieldComplexTypeElement, "type", "app:" + referencedClassName, false); xsMeta.addXsNofAttribute(xsFieldComplexTypeElement, "size"); xsMeta.addXsNofAttribute(xsFieldComplexTypeElement, "annotation"); return xsFieldElementElement; } /** * *

     *     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     *     xsi:schemaLocation="http://www.nakedobjects.org/ns/app/sdm.common.fixture.schemes.ao.communications ddd.xsd"
     * 
* * Assumes that the URI has been specified. * * @param xmlDoc * @param fullyQualifiedClassName * @param schemaLocationFileName */ void assignSchema(final Document xmlDoc, final String fullyQualifiedClassName, final String schemaLocationFileName) { final String xsiSchemaLocationAttrValue = getUri() + " " + schemaLocationFileName; final Element rootElement = xmlDoc.getDocumentElement(); // xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" addNamespace(rootElement, XsMetaModel.W3_ORG_XSI_PREFIX, XsMetaModel.W3_ORG_XSI_URI); // xsi:schemaLocation="http://www.nakedobjects.org/ns/app/ // sdm.common.fixture.schemes.ao.communications // sdm.common.fixture.schemes.ao.communications.AO11ConfirmAnimalRegistration.xsd" rootElement.setAttributeNS(XsMetaModel.W3_ORG_XSI_URI, "xsi:schemaLocation", xsiSchemaLocationAttrValue); } /** * Adds a previously created <xs:element> element (representing a field of an object) to the * supplied element (presumed to be a complexType/sequence). */ void addFieldXsElement(final Element xsElement, final Element xsFieldElement) { if (xsFieldElement == null) { return; } final Element sequenceElement = xsMeta.sequenceForComplexTypeFor(xsElement); sequenceElement.appendChild(xsFieldElement); } /** * Adds a namespace using the supplied prefix and the supplied URI to the root element of the document * that is the parent of the supplied element. * * If the namespace declaration already exists but has a different URI (shouldn't normally happen) * overwrites with supplied URI. */ private void addNamespace(final Element element, final String prefix, final String nsUri) { final Element rootElement = helper.rootElementFor(element); // see if we have the NS prefix there already final String existingNsUri = rootElement.getAttributeNS(XsMetaModel.W3_ORG_XMLNS_URI, prefix); // if there is none (or it is different from what we want), then set the attribute if (existingNsUri == null || !existingNsUri.equals(nsUri)) { helper.rootElementFor(element).setAttributeNS(XsMetaModel.W3_ORG_XMLNS_URI, XsMetaModel.W3_ORG_XMLNS_PREFIX + ":" + prefix, nsUri); } } Element addXsElementIfNotPresent(final Element parentXsElement, final Element childXsElement) { final Element parentChoiceOrSequenceElement = xsMeta.choiceOrSequenceFor(xsMeta.complexTypeFor(parentXsElement)); if (parentChoiceOrSequenceElement == null) { throw new IllegalArgumentException( "Unable to locate complexType/sequence or complexType/choice under supplied parent XSD element"); } final NamedNodeMap childXsElementAttributeMap = childXsElement.getAttributes(); final Attr childXsElementAttr = (Attr) childXsElementAttributeMap.getNamedItem("name"); final String localName = childXsElementAttr.getValue(); final NodeList existingElements = parentChoiceOrSequenceElement .getElementsByTagNameNS("*", childXsElement.getLocalName()); for (int i = 0; i < existingElements.getLength(); i++) { final Element xsElement = (Element) existingElements.item(i); final NamedNodeMap xsElementAttributeMap = xsElement.getAttributes(); final Attr attr = (Attr) xsElementAttributeMap.getNamedItem("name"); if (attr != null && attr.getValue().equals(localName)) { return xsElement; } } parentChoiceOrSequenceElement.appendChild(childXsElement); return childXsElement; } } // Copyright (c) Naked Objects Group Ltd.




© 2015 - 2025 Weber Informatics LLC | Privacy Policy