001/*
002 *  Licensed to the Apache Software Foundation (ASF) under one
003 *  or more contributor license agreements.  See the NOTICE file
004 *  distributed with this work for additional information
005 *  regarding copyright ownership.  The ASF licenses this file
006 *  to you under the Apache License, Version 2.0 (the
007 *  "License"); you may not use this file except in compliance
008 *  with the License.  You may obtain a copy of the License at
009 *
010 *        http://www.apache.org/licenses/LICENSE-2.0
011 *
012 *  Unless required by applicable law or agreed to in writing,
013 *  software distributed under the License is distributed on an
014 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 *  KIND, either express or implied.  See the License for the
016 *  specific language governing permissions and limitations
017 *  under the License.
018 */
019
020package org.apache.isis.core.runtime.snapshot;
021
022import org.w3c.dom.Document;
023import org.w3c.dom.Element;
024import org.w3c.dom.NodeList;
025
026/**
027 * Stateless utility methods relating to the w3.org schema and schema-instance
028 * meta models.
029 */
030public final class XsMetaModel {
031
032    private final Helper helper;
033    /**
034     * URI representing the namespace of the in-built xmlns namespace as defined
035     * by w3.org.
036     * 
037     * The NamespaceManager will not allow any namespaces with this URI to be
038     * added.
039     */
040    public static final String W3_ORG_XMLNS_URI = "http://www.w3.org/2000/xmlns/";
041    /**
042     * Namespace prefix for {@link W3_ORG_XMLNS_URI}.
043     * 
044     * The NamespaceManager will not allow any namespace to use this prefix.
045     */
046    public static final String W3_ORG_XMLNS_PREFIX = "xmlns";
047    /**
048     * Namespace prefix for XML schema.
049     */
050    public static final String W3_ORG_XS_URI = "http://www.w3.org/2001/XMLSchema";
051    /**
052     * Namespace prefix for {@link W3_ORG_XS_URI}.
053     * 
054     * The NamespaceManager will not allow any namespace to use this prefix.
055     */
056    public static final String W3_ORG_XS_PREFIX = "xs";
057    /**
058     * Namespace prefix for XML schema instance.
059     */
060    public static final String W3_ORG_XSI_URI = "http://www.w3.org/2001/XMLSchema-instance";
061    /**
062     * Namespace prefix for {@link W3_ORG_XSI_URI}.
063     * 
064     * The NamespaceManager will not allow any namespace to use this prefix.
065     */
066    public static final String W3_ORG_XSI_PREFIX = "xsi";
067
068    private final IsisSchema isisMeta;
069
070    public XsMetaModel() {
071        this.helper = new Helper();
072        this.isisMeta = new IsisSchema();
073    }
074
075    /**
076     * Creates an <xs:schema> element for the document to the provided
077     * element, attaching to root of supplied Xsd doc.
078     * 
079     * In addition:
080     * <ul>
081     * <li>the elementFormDefault is set
082     * <li>the NOF namespace is set
083     * <li>the <code>xs:import</code> element referencing the NOF namespace is
084     * added as a child
085     * </ul>
086     */
087    Element createXsSchemaElement(final Document xsdDoc) {
088        if (xsdDoc.getDocumentElement() != null) {
089            throw new IllegalArgumentException("XSD document already has content");
090        }
091        final Element xsSchemaElement = createXsElement(xsdDoc, "schema");
092
093        xsSchemaElement.setAttribute("elementFormDefault", "qualified");
094
095        isisMeta.addNamespace(xsSchemaElement);
096
097        xsdDoc.appendChild(xsSchemaElement);
098        final Element xsImportElement = createXsElement(xsdDoc, "import");
099        xsImportElement.setAttribute("namespace", IsisSchema.NS_URI);
100        xsImportElement.setAttribute("schemaLocation", IsisSchema.DEFAULT_LOCATION);
101
102        xsSchemaElement.appendChild(xsImportElement);
103
104        return xsSchemaElement;
105    }
106
107    Element createXsElementElement(final Document xsdDoc, final String className) {
108        return createXsElementElement(xsdDoc, className, true);
109    }
110
111    Element createXsElementElement(final Document xsdDoc, final String className, final boolean includeCardinality) {
112        final Element xsElementElement = createXsElement(xsdDoc, "element");
113        xsElementElement.setAttribute("name", className);
114        if (includeCardinality) {
115            setXsCardinality(xsElementElement, 0, Integer.MAX_VALUE);
116        }
117        return xsElementElement;
118    }
119
120    /**
121     * Creates an element in the XS namespace, adding the definition of the
122     * namespace to the root element of the document if required,
123     */
124    Element createXsElement(final Document xsdDoc, final String localName) {
125
126        final Element element = xsdDoc.createElementNS(XsMetaModel.W3_ORG_XS_URI, XsMetaModel.W3_ORG_XS_PREFIX + ":" + localName);
127        // xmlns:xs="..." added to root
128        helper.rootElementFor(element).setAttributeNS(XsMetaModel.W3_ORG_XMLNS_URI, XsMetaModel.W3_ORG_XMLNS_PREFIX + ":" + XsMetaModel.W3_ORG_XS_PREFIX, XsMetaModel.W3_ORG_XS_URI);
129        return element;
130    }
131
132    // private Element addAnyToSequence(final Element xsSequenceElement) {
133    // Element xsAnyElement = createXsElement(docFor(xsSequenceElement), "any");
134    // xsAnyElement.setAttribute("namespace", "##other");
135    // xsAnyElement.setAttribute("minOccurs", "0");
136    // xsAnyElement.setAttribute("maxOccurs", "unbounded");
137    // xsAnyElement.setAttribute("processContents", "lax");
138    // xsSequenceElement.appendChild(xsAnyElement);
139    // return xsSequenceElement;
140    // }
141
142    /**
143     * Creates an xs:attribute ref="isis:xxx" element, and appends to specified
144     * owning element.
145     */
146    Element addXsIsisAttribute(final Element parentXsElement, final String isisAttributeRef) {
147        return addXsIsisAttribute(parentXsElement, isisAttributeRef, null);
148    }
149
150    /**
151     * Adds <code>xs:attribute ref="isis:xxx" fixed="yyy"</code> element, and
152     * appends to specified parent XSD element.
153     */
154    Element addXsIsisAttribute(final Element parentXsElement, final String isisAttributeRef, final String fixedValue) {
155        return addXsIsisAttribute(parentXsElement, isisAttributeRef, fixedValue, true);
156    }
157
158    /**
159     * Adds <code>xs:attribute ref="isis:xxx" default="yyy"</code> element, and
160     * appends to specified parent XSD element.
161     * 
162     * The last parameter determines whether to use <code>fixed="yyy"</code>
163     * rather than <code>default="yyy"</code>.
164     */
165    Element addXsIsisAttribute(final Element parentXsElement, final String isisAttributeRef, final String value, final boolean useFixed) {
166        final Element xsIsisAttributeElement = createXsElement(helper.docFor(parentXsElement), "attribute");
167        xsIsisAttributeElement.setAttribute("ref", IsisSchema.NS_PREFIX + ":" + isisAttributeRef);
168        parentXsElement.appendChild(xsIsisAttributeElement);
169        if (value != null) {
170            if (useFixed) {
171                xsIsisAttributeElement.setAttribute("fixed", value);
172            } else {
173                xsIsisAttributeElement.setAttribute("default", value);
174            }
175        }
176        return parentXsElement;
177    }
178
179    /**
180     * Adds <code>xs:attribute ref="isis:feature" fixed="(feature)"</code>
181     * element as child to supplied XSD element, presumed to be an
182     * <xs:complexType</code>.
183     */
184    Element addXsIsisFeatureAttributeElements(final Element parentXsElement, final String feature) {
185        final Element xsNofFeatureAttributeElement = createXsElement(helper.docFor(parentXsElement), "attribute");
186        xsNofFeatureAttributeElement.setAttribute("ref", IsisSchema.NS_PREFIX + ":feature");
187        xsNofFeatureAttributeElement.setAttribute("fixed", feature);
188        parentXsElement.appendChild(xsNofFeatureAttributeElement);
189        return xsNofFeatureAttributeElement;
190    }
191
192    /**
193     * returns child <code>xs:complexType</code> element allowing mixed content
194     * for supplied parent XSD element, creating and appending if necessary.
195     * 
196     * <p>
197     * The supplied element is presumed to be one for which
198     * <code>xs:complexType</code> is valid as a child (eg
199     * <code>xs:element</code>).
200     */
201    Element complexTypeFor(final Element parentXsElement) {
202        return complexTypeFor(parentXsElement, true);
203    }
204
205    /**
206     * returns child <code>xs:complexType</code> element, optionally allowing
207     * mixed content, for supplied parent XSD element, creating and appending if
208     * necessary.
209     * 
210     * <p>
211     * The supplied element is presumed to be one for which
212     * <code>xs:complexType</code> is valid as a child (eg
213     * <code>xs:element</code>).
214     */
215    Element complexTypeFor(final Element parentXsElement, final boolean mixed) {
216        final Element el = childXsElement(parentXsElement, "complexType");
217        if (mixed) {
218            el.setAttribute("mixed", "true");
219        }
220        return el;
221    }
222
223    /**
224     * returns child <code>xs:sequence</code> element for supplied parent XSD
225     * element, creating and appending if necessary.
226     * 
227     * The supplied element is presumed to be one for which
228     * <code>xs:simpleContent</code> is valid as a child (eg
229     * <code>xs:complexType</code>).
230     */
231    Element sequenceFor(final Element parentXsElement) {
232        return childXsElement(parentXsElement, "sequence");
233    }
234
235    /**
236     * returns child <code>xs:choice</code> element for supplied parent XSD
237     * element, creating and appending if necessary.
238     * 
239     * The supplied element is presumed to be one for which
240     * <code>xs:simpleContent</code> is valid as a child (eg
241     * <code>xs:complexType</code>).
242     */
243    Element choiceFor(final Element parentXsElement) {
244        return childXsElement(parentXsElement, "choice");
245    }
246
247    Element sequenceForComplexTypeFor(final Element parentXsElement) {
248        return sequenceFor(complexTypeFor(parentXsElement));
249    }
250
251    Element choiceForComplexTypeFor(final Element parentXsElement) {
252        return choiceFor(complexTypeFor(parentXsElement));
253    }
254
255    /**
256     * Returns the <code>xs:choice</code> or <code>xs:sequence</code> element
257     * under the supplied XSD element, or null if neither can be found.
258     */
259    Element choiceOrSequenceFor(final Element parentXsElement) {
260        final NodeList choiceNodeList = parentXsElement.getElementsByTagNameNS(XsMetaModel.W3_ORG_XS_URI, "choice");
261        if (choiceNodeList.getLength() > 0) {
262            return (Element) choiceNodeList.item(0);
263        }
264        final NodeList sequenceNodeList = parentXsElement.getElementsByTagNameNS(XsMetaModel.W3_ORG_XS_URI, "sequence");
265        if (sequenceNodeList.getLength() > 0) {
266            return (Element) sequenceNodeList.item(0);
267        }
268        return null;
269    }
270
271    /**
272     * returns child <code>xs:simpleContent</code> element for supplied parent
273     * XSD element, creating and appending if necessary.
274     * 
275     * The supplied element is presumed to be one for which
276     * <code>xs:simpleContent</code> is valid as a child (eg
277     * <code>xs:complexType</code>).
278     */
279    Element simpleContentFor(final Element parentXsElement) {
280        return childXsElement(parentXsElement, "simpleContent");
281    }
282
283    /**
284     * returns child <code>xs:extension</code> element for supplied parent XSD
285     * element, creating and appending if nec; also sets the <code>base</code>
286     * attribute.
287     * 
288     * The supplied element is presumed to be one for which
289     * <code>xs:extension</code> is valid as a child (eg
290     * <code>xs:complexType</code>).
291     */
292    Element extensionFor(final Element parentXsElement, final String base) {
293        final Element childXsElement = childXsElement(parentXsElement, "extension");
294        childXsElement.setAttribute("base", XsMetaModel.W3_ORG_XS_PREFIX + ":" + base);
295        return childXsElement;
296    }
297
298    Element childXsElement(final Element parentXsElement, final String localName) {
299        final NodeList nodeList = parentXsElement.getElementsByTagNameNS(XsMetaModel.W3_ORG_XS_URI, localName);
300        if (nodeList.getLength() > 0) {
301            return (Element) nodeList.item(0);
302        }
303
304        final Element childXsElement = createXsElement(helper.docFor(parentXsElement), localName);
305        parentXsElement.appendChild(childXsElement);
306
307        return childXsElement;
308    }
309
310    /**
311     * @return the <code>xs:schema</code> element (the root element of the
312     *         owning XSD Doc).
313     */
314    Element schemaFor(final Element xsElement) {
315        return xsElement.getOwnerDocument().getDocumentElement();
316    }
317
318    /**
319     * Sets the <code>minOccurs</code> and <code>maxOccurs</code> attributes for
320     * provided <code>element</code> (presumed to be an XSD element for which
321     * these attributes makes sense.
322     */
323    Element setXsCardinality(final Element xsElement, final int minOccurs, final int maxOccurs) {
324        if (maxOccurs >= 0) {
325            xsElement.setAttribute("minOccurs", "" + minOccurs);
326        }
327        if (maxOccurs >= 0) {
328            if (maxOccurs == Integer.MAX_VALUE) {
329                xsElement.setAttribute("maxOccurs", "unbounded");
330            } else {
331                xsElement.setAttribute("maxOccurs", "" + maxOccurs);
332            }
333        }
334        return xsElement;
335    }
336
337}