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 java.util.Enumeration;
023import java.util.Hashtable;
024
025import org.w3c.dom.Attr;
026import org.w3c.dom.Document;
027import org.w3c.dom.Element;
028import org.w3c.dom.NamedNodeMap;
029import org.w3c.dom.NodeList;
030
031import org.apache.isis.applib.services.xmlsnapshot.XmlSnapshotService.Snapshot;
032
033/**
034 * Represents the schema for the derived snapshot.
035 */
036public final class XmlSchema {
037
038    private final String prefix;
039    private final String uriBase;
040    private String uri;
041
042    private final IsisSchema isisMeta;
043    private final XsMetaModel xsMeta;
044    private final Helper helper;
045
046    /**
047     * The base part of the namespace prefix to use if none explicitly supplied
048     * in the constructor.
049     */
050    public final static String DEFAULT_PREFIX = "app";
051
052    public XmlSchema() {
053        this(IsisSchema.DEFAULT_URI_BASE, XmlSchema.DEFAULT_PREFIX);
054    }
055
056    /**
057     * @param uriBase
058     *            the prefix for the application namespace's URIs
059     * @param prefix
060     *            the prefix for the application namespace's prefix
061     */
062    public XmlSchema(final String uriBase, final String prefix) {
063        this.isisMeta = new IsisSchema();
064        this.xsMeta = new XsMetaModel();
065        this.helper = new Helper();
066
067        final String base = new Helper().trailingSlash(uriBase);
068        if (XsMetaModel.W3_ORG_XMLNS_URI.equals(base)) {
069            throw new IllegalArgumentException("Namespace URI reserved for w3.org XMLNS namespace");
070        }
071        if (XsMetaModel.W3_ORG_XMLNS_PREFIX.equals(prefix)) {
072            throw new IllegalArgumentException("Namespace prefix reserved for w3.org XMLNS namespace.");
073        }
074        if (XsMetaModel.W3_ORG_XS_URI.equals(base)) {
075            throw new IllegalArgumentException("Namespace URI reserved for w3.org XML schema namespace.");
076        }
077        if (XsMetaModel.W3_ORG_XS_PREFIX.equals(prefix)) {
078            throw new IllegalArgumentException("Namespace prefix reserved for w3.org XML schema namespace.");
079        }
080        if (XsMetaModel.W3_ORG_XSI_URI.equals(base)) {
081            throw new IllegalArgumentException("Namespace URI reserved for w3.org XML schema-instance namespace.");
082        }
083        if (XsMetaModel.W3_ORG_XSI_PREFIX.equals(prefix)) {
084            throw new IllegalArgumentException("Namespace prefix reserved for w3.org XML schema-instance namespace.");
085        }
086        if (IsisSchema.NS_URI.equals(base)) {
087            throw new IllegalArgumentException("Namespace URI reserved for NOF metamodel namespace.");
088        }
089        if (IsisSchema.NS_PREFIX.equals(prefix)) {
090            throw new IllegalArgumentException("Namespace prefix reserved for NOF metamodel namespace.");
091        }
092        this.uriBase = base;
093        this.prefix = prefix;
094    }
095
096    /**
097     * The base of the Uri in use. All namespaces are concatenated with this.
098     * 
099     * The namespace string will be the concatenation of the plus the package
100     * name of the class of the object being referenced.
101     * 
102     * If not specified in the constructor, then {@link #DEFAULT_URI_PREFIX} is
103     * used.
104     */
105    public String getUriBase() {
106        return uriBase;
107    }
108
109    /**
110     * Returns the namespace URI for the class.
111     */
112    void setUri(final String fullyQualifiedClassName) {
113        if (uri != null) {
114            throw new IllegalStateException("URI has already been specified.");
115        }
116        this.uri = getUriBase() + helper.packageNameFor(fullyQualifiedClassName) + "/" + helper.classNameFor(fullyQualifiedClassName);
117    }
118
119    /**
120     * The URI of the application namespace.
121     * 
122     * The value returned will be <code>null</code> until a {@link Snapshot} is
123     * created.
124     */
125    public String getUri() {
126        if (uri == null) {
127            throw new IllegalStateException("URI has not been specified.");
128        }
129        return uri;
130    }
131
132    /**
133     * The prefix to the namespace for the application.
134     */
135    public String getPrefix() {
136        return this.prefix;
137    }
138
139    /**
140     * Creates an element with the specified localName, in the appropriate
141     * namespace for the NOS.
142     * 
143     * If necessary the namespace definition is added to the root element of the
144     * doc used to create the element. The element is not parented but to avoid
145     * an error can only be added as a child of another element in the same doc.
146     */
147    Element createElement(final Document doc, final String localName, final String fullyQualifiedClassName, final String singularName, final String pluralName) {
148        final Element element = doc.createElementNS(getUri(), getPrefix() + ":" + localName);
149        element.setAttributeNS(IsisSchema.NS_URI, IsisSchema.NS_PREFIX + ":fqn", fullyQualifiedClassName);
150        element.setAttributeNS(IsisSchema.NS_URI, IsisSchema.NS_PREFIX + ":singular", singularName);
151        element.setAttributeNS(IsisSchema.NS_URI, IsisSchema.NS_PREFIX + ":plural", pluralName);
152        isisMeta.addNamespace(element); // good a place as any
153
154        addNamespace(element, getPrefix(), getUri());
155        return element;
156    }
157
158    /**
159     * Sets the target namespace for the XSD document to a URI derived from the
160     * fully qualified class name of the supplied object
161     */
162    void setTargetNamespace(final Document xsdDoc, final String fullyQualifiedClassName) {
163
164        final Element xsSchemaElement = xsdDoc.getDocumentElement();
165        if (xsSchemaElement == null) {
166            throw new IllegalArgumentException("XSD Document must have <xs:schema> element attached");
167        }
168
169        // targetNamespace="http://isis.apache.org/ns/app/<fully qualified class
170        // name>
171        xsSchemaElement.setAttribute("targetNamespace", getUri());
172
173        addNamespace(xsSchemaElement, getPrefix(), getUri());
174    }
175
176    /**
177     * Creates an &lt;xs:element&gt; element defining the presence of the named
178     * element representing a class
179     */
180    Element createXsElementForNofClass(final Document xsdDoc, final Element element, final boolean addCardinality, final Hashtable extensions) {
181
182        // gather details from XML element
183        final String localName = element.getLocalName();
184
185        // <xs:element name="AO11ConfirmAnimalRegistration">
186        // <xs:complexType>
187        // <xs:sequence>
188        // <xs:element ref="isis:title"/>
189        // <!-- placeholder -->
190        // </xs:sequence>
191        // <xs:attribute ref="isis:feature"
192        // default="class"/>
193        // <xs:attribute ref="isis:oid"/>
194        // <xs:attribute ref="isis:annotation"/>
195        // <xs:attribute ref="isis:fqn"/>
196        // </xs:complexType>
197        // </xs:element>
198
199        // xs:element/@name="class name"
200        // add to XML schema as a global attribute
201        final Element xsElementForNofClassElement = xsMeta.createXsElementElement(xsdDoc, localName, addCardinality);
202
203        // xs:element/xs:complexType
204        // xs:element/xs:complexType/xs:sequence
205        final Element xsComplexTypeElement = xsMeta.complexTypeFor(xsElementForNofClassElement);
206        final Element xsSequenceElement = xsMeta.sequenceFor(xsComplexTypeElement);
207
208        // xs:element/xs:complexType/xs:sequence/xs:element ref="isis:title"
209        final Element xsTitleElement = xsMeta.createXsElement(helper.docFor(xsSequenceElement), "element");
210        xsTitleElement.setAttribute("ref", IsisSchema.NS_PREFIX + ":" + "title");
211        xsSequenceElement.appendChild(xsTitleElement);
212        xsMeta.setXsCardinality(xsTitleElement, 0, 1);
213
214        // xs:element/xs:complexType/xs:sequence/xs:element ref="extensions"
215        addXsElementForAppExtensions(xsSequenceElement, extensions);
216
217        // xs:element/xs:complexType/xs:attribute ...
218        xsMeta.addXsIsisFeatureAttributeElements(xsComplexTypeElement, "class");
219        xsMeta.addXsIsisAttribute(xsComplexTypeElement, "oid");
220        xsMeta.addXsIsisAttribute(xsComplexTypeElement, "fqn");
221        xsMeta.addXsIsisAttribute(xsComplexTypeElement, "singular");
222        xsMeta.addXsIsisAttribute(xsComplexTypeElement, "plural");
223        xsMeta.addXsIsisAttribute(xsComplexTypeElement, "annotation");
224
225        Place.setXsdElement(element, xsElementForNofClassElement);
226
227        return xsElementForNofClassElement;
228    }
229
230    /**
231     * Creates an <code>xs:element</code> element to represent a collection of
232     * application-defined extensions
233     * 
234     * The returned element should be appended to <code>xs:sequence</code>
235     * element of the xs:element representing the type of the owning object.
236     */
237    void addXsElementForAppExtensions(final Element parentXsElementElement, final Hashtable extensions) {
238
239        if (extensions.size() == 0) {
240            return;
241        }
242
243        // <xs:element name="extensions">
244        // <xs:complexType>
245        // <xs:sequence>
246        // <xs:element name="app:%extension class short name%" minOccurs="0"
247        // maxOccurs="1" default="%value%"/>
248        // <xs:element name="app:%extension class short name%" minOccurs="0"
249        // maxOccurs="1" default="%value%"/>
250        // ...
251        // <xs:element name="app:%extension class short name%" minOccurs="0"
252        // maxOccurs="1" default="%value%"/>
253        // </xs:sequence>
254        // </xs:complexType>
255        // </xs:element>
256
257        // xs:element name="isis-extensions"
258        // xs:element/xs:complexType/xs:sequence
259        final Element xsExtensionsSequenceElement = addExtensionsElement(parentXsElementElement);
260
261        addExtensionElements(xsExtensionsSequenceElement, extensions);
262
263        return;
264    }
265
266    /**
267     * Adds an isis-extensions element and a complexType and sequence elements
268     * underneath.
269     * 
270     * <p>
271     * Returns the sequence element so that it can be appended to.
272     */
273    private Element addExtensionsElement(final Element parentXsElement) {
274        final Element xsExtensionsElementElement = xsMeta.createXsElementElement(helper.docFor(parentXsElement), "isis-extensions");
275        parentXsElement.appendChild(xsExtensionsElementElement);
276
277        // xs:element/xs:complexType/xs:sequence/xs:element/xs:complexType/xs:sequence
278        final Element xsExtensionsComplexTypeElement = xsMeta.complexTypeFor(xsExtensionsElementElement);
279        final Element xsExtensionsSequenceElement = xsMeta.sequenceFor(xsExtensionsComplexTypeElement);
280
281        return xsExtensionsSequenceElement;
282    }
283
284    private String shortName(final String className) {
285        final int lastPeriodIdx = className.lastIndexOf('.');
286        if (lastPeriodIdx < 0) {
287            return className;
288        }
289        return className.substring(lastPeriodIdx + 1);
290    }
291
292    /**
293     * Creates an <code>xs:element</code> element to represent a value field in
294     * a class.
295     * 
296     * The returned element should be appended to <code>xs:sequence</code>
297     * element of the xs:element representing the type of the owning object.
298     */
299    Element createXsElementForNofValue(final Element parentXsElementElement, final Element xmlValueElement, final Hashtable extensions) {
300
301        // gather details from XML element
302        final String datatype = xmlValueElement.getAttributeNS(IsisSchema.NS_URI, "datatype");
303        final String fieldName = xmlValueElement.getLocalName();
304
305        // <xs:element name="%owning object%">
306        // <xs:complexType>
307        // <xs:sequence>
308        // <xs:element name="%%field object%%">
309        // <xs:complexType>
310        // <xs:sequence>
311        // <xs:element name="isis-extensions">
312        // <xs:complexType>
313        // <xs:sequence>
314        // <xs:element name="%extensionClassShortName%"
315        // default="%extensionObjString" minOccurs="0"/>
316        // <xs:element name="%extensionClassShortName%"
317        // default="%extensionObjString" minOccurs="0"/>
318        // ...
319        // <xs:element name="%extensionClassShortName%"
320        // default="%extensionObjString" minOccurs="0"/>
321        // </xs:sequence>
322        // </xs:complexType>
323        // </xs:element>
324        // </xs:sequence>
325        // <xs:attribute ref="isis:feature" fixed="value"/>
326        // <xs:attribute ref="isis:datatype" fixed="isis:%datatype%"/>
327        // <xs:attribute ref="isis:isEmpty"/>
328        // <xs:attribute ref="isis:annotation"/>
329        // </xs:complexType>
330        // </xs:element>
331        // </xs:sequence>
332        // </xs:complexType>
333        // </xs:element>
334
335        // xs:element/xs:complexType/xs:sequence
336        final Element parentXsComplexTypeElement = xsMeta.complexTypeFor(parentXsElementElement);
337        final Element parentXsSequenceElement = xsMeta.sequenceFor(parentXsComplexTypeElement);
338
339        // xs:element/xs:complexType/xs:sequence/xs:element
340        // name="%%field object%"
341        final Element xsFieldElementElement = xsMeta.createXsElementElement(helper.docFor(parentXsSequenceElement), fieldName);
342        parentXsSequenceElement.appendChild(xsFieldElementElement);
343
344        // xs:element/xs:complexType/xs:sequence/xs:element/xs:complexType
345        final Element xsFieldComplexTypeElement = xsMeta.complexTypeFor(xsFieldElementElement);
346
347        // NEW CODE TO SUPPORT EXTENSIONS;
348        // uses a complexType/sequence
349
350        // xs:element/xs:complexType/xs:sequence/xs:element/xs:complexType/xs:sequence
351        final Element xsFieldSequenceElement = xsMeta.sequenceFor(xsFieldComplexTypeElement);
352
353        // xs:element/xs:complexType/xs:sequence/xs:element/xs:complexType/xs:sequence/xs:element
354        // name="isis-extensions"
355        // xs:element/xs:complexType/xs:sequence/xs:element/xs:complexType/xs:sequence/xs:element/xs:complexType/xs:sequence
356        addXsElementForAppExtensions(xsFieldSequenceElement, extensions);
357
358        xsMeta.addXsIsisFeatureAttributeElements(xsFieldComplexTypeElement, "value");
359        xsMeta.addXsIsisAttribute(xsFieldComplexTypeElement, "datatype", datatype);
360        xsMeta.addXsIsisAttribute(xsFieldComplexTypeElement, "isEmpty");
361        xsMeta.addXsIsisAttribute(xsFieldComplexTypeElement, "annotation");
362
363        return xsFieldElementElement;
364    }
365
366    private void addExtensionElements(final Element parentElement, final Hashtable extensions) {
367        for (final Enumeration e = extensions.keys(); e.hasMoreElements();) {
368            final Class<?> extensionClass = (Class<?>) e.nextElement();
369            final Object extensionObject = extensions.get(extensionClass);
370            // xs:element/xs:complexType/xs:sequence/xs:element/xs:complexType/xs:sequence/xs:element
371            // name="%extensionClassShortName%"
372            final Element xsExtensionElementElement = xsMeta.createXsElementElement(helper.docFor(parentElement), "x-" + shortName(extensionClass.getName()));
373            xsExtensionElementElement.setAttribute("default", extensionObject.toString()); // the
374                                                                                           // value
375            xsExtensionElementElement.setAttribute("minOccurs", "0"); // doesn't
376                                                                      // need to
377                                                                      // appear
378                                                                      // in XML
379                                                                      // (and
380            // indeed won't)
381            parentElement.appendChild(xsExtensionElementElement);
382        }
383    }
384
385    /**
386     * Creates an &lt;xs:element&gt; element defining the presence of the named
387     * element representing a reference to a class; appended to xs:sequence
388     * element
389     */
390    Element createXsElementForNofReference(final Element parentXsElementElement, final Element xmlReferenceElement, final String referencedClassName, final Hashtable extensions) {
391
392        // gather details from XML element
393        final String fieldName = xmlReferenceElement.getLocalName();
394
395        // <xs:element name="%owning object%">
396        // <xs:complexType>
397        // <xs:sequence>
398        // <xs:element name="%%field object%%">
399        // <xs:complexType>
400        // <xs:sequence>
401        // <xs:element ref="isis:title" minOccurs="0"/>
402        // <xs:element name="isis-extensions">
403        // <xs:complexType>
404        // <xs:sequence>
405        // <xs:element name="app:%extension class short name%" minOccurs="0"
406        // maxOccurs="1" default="%value%"/>
407        // <xs:element name="app:%extension class short name%" minOccurs="0"
408        // maxOccurs="1" default="%value%"/>
409        // ...
410        // <xs:element name="app:%extension class short name%" minOccurs="0"
411        // maxOccurs="1" default="%value%"/>
412        // </xs:sequence>
413        // </xs:complexType>
414        // </xs:element>
415        // <xs:sequence minOccurs="0" maxOccurs="1"/>
416        // </xs:sequence>
417        // <xs:attribute ref="isis:feature" fixed="reference"/>
418        // <xs:attribute ref="isis:type" default="%%appX%%:%%type%%"/>
419        // <xs:attribute ref="isis:isEmpty"/>
420        // <xs:attribute ref="isis:annotation"/>
421        // </xs:complexType>
422        // </xs:element>
423        // </xs:sequence>
424        // </xs:complexType>
425        // </xs:element>
426
427        // xs:element/xs:complexType/xs:sequence
428        final Element parentXsComplexTypeElement = xsMeta.complexTypeFor(parentXsElementElement);
429        final Element parentXsSequenceElement = xsMeta.sequenceFor(parentXsComplexTypeElement);
430
431        // xs:element/xs:complexType/xs:sequence/xs:element
432        // name="%%field object%"
433        final Element xsFieldElementElement = xsMeta.createXsElementElement(helper.docFor(parentXsSequenceElement), fieldName);
434        parentXsSequenceElement.appendChild(xsFieldElementElement);
435
436        // xs:element/xs:complexType/xs:sequence/xs:element/xs:complexType/xs:sequence
437        final Element xsFieldComplexTypeElement = xsMeta.complexTypeFor(xsFieldElementElement);
438        final Element xsFieldSequenceElement = xsMeta.sequenceFor(xsFieldComplexTypeElement);
439
440        // xs:element/xs:complexType/xs:sequence/xs:element/xs:complexType/xs:sequence/xs:element
441        // ref="isis:title"
442        final Element xsFieldTitleElement = xsMeta.createXsElement(helper.docFor(xsFieldSequenceElement), "element");
443        xsFieldTitleElement.setAttribute("ref", IsisSchema.NS_PREFIX + ":" + "title");
444        xsFieldSequenceElement.appendChild(xsFieldTitleElement);
445        xsMeta.setXsCardinality(xsFieldTitleElement, 0, 1);
446
447        // xs:element/xs:complexType/xs:sequence/xs:element/xs:complexType/xs:sequence/xs:element
448        // name="isis-extensions"
449        addXsElementForAppExtensions(xsFieldSequenceElement, extensions);
450
451        // xs:element/xs:complexType/xs:sequence/xs:element/xs:complexType/xs:sequence/xs:sequence
452        // //
453        // placeholder
454        final Element xsReferencedElementSequenceElement = xsMeta.sequenceFor(xsFieldSequenceElement);
455        xsMeta.setXsCardinality(xsReferencedElementSequenceElement, 0, 1);
456
457        xsMeta.addXsIsisFeatureAttributeElements(xsFieldComplexTypeElement, "reference");
458        xsMeta.addXsIsisAttribute(xsFieldComplexTypeElement, "type", "app:" + referencedClassName, false);
459        xsMeta.addXsIsisAttribute(xsFieldComplexTypeElement, "isEmpty");
460        xsMeta.addXsIsisAttribute(xsFieldComplexTypeElement, "annotation");
461
462        return xsFieldElementElement;
463    }
464
465    /**
466     * Creates an &lt;xs:element&gt; element defining the presence of the named
467     * element representing a collection in a class; appended to xs:sequence
468     * element
469     */
470    Element createXsElementForNofCollection(final Element parentXsElementElement, final Element xmlCollectionElement, final String referencedClassName, final Hashtable extensions) {
471
472        // gather details from XML element
473        final String fieldName = xmlCollectionElement.getLocalName();
474
475        // <xs:element name="%owning object%">
476        // <xs:complexType>
477        // <xs:sequence>
478        // <xs:element name="%%field object%%">
479        // <xs:complexType>
480        // <xs:sequence>
481        // <xs:element ref="isis:oids" minOccurs="0" maxOccurs="1"/>
482        // <!-- nested element definitions go here -->
483        // </xs:sequence>
484        // <xs:attribute ref="isis:feature" fixed="collection"/>
485        // <xs:attribute ref="isis:type" fixed="%%appX%%:%%type%%"/>
486        // <xs:attribute ref="isis:size"/>
487        // <xs:attribute ref="isis:annotation"/>
488        // </xs:complexType>
489        // </xs:element>
490        // </xs:sequence>
491        // </xs:complexType>
492        // </xs:element>
493
494        // xs:element/xs:complexType/xs:sequence
495        final Element parentXsComplexTypeElement = xsMeta.complexTypeFor(parentXsElementElement);
496        final Element parentXsSequenceElement = xsMeta.sequenceFor(parentXsComplexTypeElement);
497
498        // xs:element/xs:complexType/xs:sequence/xs:element
499        // name="%field object%%"
500        final Element xsFieldElementElement = xsMeta.createXsElementElement(helper.docFor(parentXsSequenceElement), fieldName);
501        parentXsSequenceElement.appendChild(xsFieldElementElement);
502
503        // xs:element/xs:complexType/xs:sequence/xs:element/xs:complexType
504        final Element xsFieldComplexTypeElement = xsMeta.complexTypeFor(xsFieldElementElement);
505        // xs:element/xs:complexType/xs:sequence/xs:element/xs:complexType/xs:sequence
506        final Element xsFieldSequenceElement = xsMeta.sequenceFor(xsFieldComplexTypeElement);
507
508        // xs:element/xs:complexType/xs:sequence/xs:element/xs:complexType/xs:sequence/xs:element
509        // ref="isis:oids"
510        final Element xsFieldOidsElement = xsMeta.createXsElement(helper.docFor(xsFieldSequenceElement), "element");
511        xsFieldOidsElement.setAttribute("ref", IsisSchema.NS_PREFIX + ":" + "oids");
512        xsFieldSequenceElement.appendChild(xsFieldOidsElement);
513        xsMeta.setXsCardinality(xsFieldOidsElement, 0, 1);
514
515        // extensions
516        addXsElementForAppExtensions(xsFieldSequenceElement, extensions);
517
518        // //
519        // xs:element/xs:complexType/xs:sequence/xs:element/xs:complexType/xs:sequence/xs:choice
520        // Element xsFieldChoiceElement =
521        // xsMeta.choiceFor(xsFieldComplexTypeElement); // placeholder
522        // xsMeta.setXsCardinality(xsFieldChoiceElement, 0, Integer.MAX_VALUE);
523
524        // Element xsFieldTitleElement =
525        // addXsNofRefElementElement(xsFieldSequenceElement, "title");
526
527        // Element xsReferencedElementSequenceElement =
528        // sequenceFor(xsFieldSequenceElement);
529        // setXsCardinality(xsReferencedElementSequenceElement, 0, 1);
530
531        xsMeta.addXsIsisFeatureAttributeElements(xsFieldComplexTypeElement, "collection");
532        xsMeta.addXsIsisAttribute(xsFieldComplexTypeElement, "type", "app:" + referencedClassName, false);
533        xsMeta.addXsIsisAttribute(xsFieldComplexTypeElement, "size");
534        xsMeta.addXsIsisAttribute(xsFieldComplexTypeElement, "annotation");
535
536        return xsFieldElementElement;
537    }
538
539    /**
540     * 
541     * <pre>
542     *     xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;
543     *     xsi:schemaLocation=&quot;http://isis.apache.org/ns/app/sdm.common.fixture.schemes.ao.communications ddd.xsd&quot;
544     * </pre>
545     * 
546     * Assumes that the URI has been specified.
547     * 
548     * @param xmlDoc
549     * @param fullyQualifiedClassName
550     * @param schemaLocationFileName
551     */
552    void assignSchema(final Document xmlDoc, final String fullyQualifiedClassName, final String schemaLocationFileName) {
553
554        final String xsiSchemaLocationAttrValue = getUri() + " " + schemaLocationFileName;
555
556        final Element rootElement = xmlDoc.getDocumentElement();
557
558        // xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
559        addNamespace(rootElement, XsMetaModel.W3_ORG_XSI_PREFIX, XsMetaModel.W3_ORG_XSI_URI);
560
561        // xsi:schemaLocation="http://isis.apache.org/ns/app/<fully qualified
562        // class name>
563        // sdm.common.fixture.schemes.ao.communications
564        // sdm.common.fixture.schemes.ao.communications.AO11ConfirmAnimalRegistration.xsd"
565        rootElement.setAttributeNS(XsMetaModel.W3_ORG_XSI_URI, "xsi:schemaLocation", xsiSchemaLocationAttrValue);
566    }
567
568    /**
569     * Adds a previously created &lt;xs:element&gt; element (representing a
570     * field of an object) to the supplied element (presumed to be a
571     * <code>complexType/sequence</code>).
572     */
573    void addFieldXsElement(final Element xsElement, final Element xsFieldElement) {
574        if (xsFieldElement == null) {
575            return;
576        }
577        final Element sequenceElement = xsMeta.sequenceForComplexTypeFor(xsElement);
578        sequenceElement.appendChild(xsFieldElement);
579    }
580
581    /**
582     * Adds a namespace using the supplied prefix and the supplied URI to the
583     * root element of the document that is the parent of the supplied element.
584     * 
585     * If the namespace declaration already exists but has a different URI
586     * (shouldn't normally happen) overwrites with supplied URI.
587     */
588    private void addNamespace(final Element element, final String prefix, final String nsUri) {
589        final Element rootElement = helper.rootElementFor(element);
590        // see if we have the NS prefix there already
591        final String existingNsUri = rootElement.getAttributeNS(XsMetaModel.W3_ORG_XMLNS_URI, prefix);
592        // if there is none (or it is different from what we want), then set the
593        // attribute
594        if (existingNsUri == null || !existingNsUri.equals(nsUri)) {
595            helper.rootElementFor(element).setAttributeNS(XsMetaModel.W3_ORG_XMLNS_URI, XsMetaModel.W3_ORG_XMLNS_PREFIX + ":" + prefix, nsUri);
596        }
597    }
598
599    Element addXsElementIfNotPresent(final Element parentXsElement, final Element childXsElement) {
600
601        final Element parentChoiceOrSequenceElement = xsMeta.choiceOrSequenceFor(xsMeta.complexTypeFor(parentXsElement));
602
603        if (parentChoiceOrSequenceElement == null) {
604            throw new IllegalArgumentException("Unable to locate complexType/sequence or complexType/choice under supplied parent XSD element");
605        }
606
607        final NamedNodeMap childXsElementAttributeMap = childXsElement.getAttributes();
608        final Attr childXsElementAttr = (Attr) childXsElementAttributeMap.getNamedItem("name");
609        final String localName = childXsElementAttr.getValue();
610
611        final NodeList existingElements = parentChoiceOrSequenceElement.getElementsByTagNameNS("*", childXsElement.getLocalName());
612        for (int i = 0; i < existingElements.getLength(); i++) {
613            final Element xsElement = (Element) existingElements.item(i);
614            final NamedNodeMap xsElementAttributeMap = xsElement.getAttributes();
615            final Attr attr = (Attr) xsElementAttributeMap.getNamedItem("name");
616            if (attr != null && attr.getValue().equals(localName)) {
617                return xsElement;
618            }
619        }
620
621        parentChoiceOrSequenceElement.appendChild(childXsElement);
622        return childXsElement;
623    }
624
625}