You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@isis.apache.org by da...@apache.org on 2012/12/05 09:07:34 UTC

[4/11] ISIS-188: more refactoring of artifacts

http://git-wip-us.apache.org/repos/asf/isis/blob/6de87443/framework/core/metamodel/src/main/java/org/apache/isis/core/runtime/snapshot/XmlSchema.java
----------------------------------------------------------------------
diff --git a/framework/core/metamodel/src/main/java/org/apache/isis/core/runtime/snapshot/XmlSchema.java b/framework/core/metamodel/src/main/java/org/apache/isis/core/runtime/snapshot/XmlSchema.java
new file mode 100644
index 0000000..1bbab91
--- /dev/null
+++ b/framework/core/metamodel/src/main/java/org/apache/isis/core/runtime/snapshot/XmlSchema.java
@@ -0,0 +1,623 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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 org.apache.isis.core.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 IsisSchema 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(IsisSchema.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 IsisSchema();
+        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 (IsisSchema.NS_URI.equals(base)) {
+            throw new IllegalArgumentException("Namespace URI reserved for NOF metamodel namespace.");
+        }
+        if (IsisSchema.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 <code>null</code> 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(IsisSchema.NS_URI, "nof:fqn", fullyQualifiedClassName);
+        element.setAttributeNS(IsisSchema.NS_URI, "nof:singular", singularName);
+        element.setAttributeNS(IsisSchema.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 <xs:schema> element attached");
+        }
+
+        // targetNamespace="http://isis.apache.org/ns/app/<fully qualified class
+        // name>
+        xsSchemaElement.setAttribute("targetNamespace", getUri());
+
+        addNamespace(xsSchemaElement, getPrefix(), getUri());
+    }
+
+    /**
+     * Creates an &lt;xs:element&gt; 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="AO11ConfirmAnimalRegistration">
+        // <xs:complexType>
+        // <xs:sequence>
+        // <xs:element ref="nof:title"/>
+        // <!-- placeholder -->
+        // </xs:sequence>
+        // <xs:attribute ref="nof:feature"
+        // default="class"/>
+        // <xs:attribute ref="nof:oid"/>
+        // <xs:attribute ref="nof:annotation"/>
+        // <xs:attribute ref="nof:fqn"/>
+        // </xs:complexType>
+        // </xs:element>
+
+        // 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", IsisSchema.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 <code>xs:element</code> element to represent a collection of
+     * application-defined extensions
+     * 
+     * The returned element should be appended to <code>xs:sequence</code>
+     * 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="extensions">
+        // <xs:complexType>
+        // <xs:sequence>
+        // <xs:element name="app:%extension class short name%" minOccurs="0"
+        // maxOccurs="1" default="%value%"/>
+        // <xs:element name="app:%extension class short name%" minOccurs="0"
+        // maxOccurs="1" default="%value%"/>
+        // ...
+        // <xs:element name="app:%extension class short name%" minOccurs="0"
+        // maxOccurs="1" default="%value%"/>
+        // </xs:sequence>
+        // </xs:complexType>
+        // </xs:element>
+
+        // 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.
+     * 
+     * <p>
+     * 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 <code>xs:element</code> element to represent a value field in
+     * a class.
+     * 
+     * The returned element should be appended to <code>xs:sequence</code>
+     * 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(IsisSchema.NS_URI, "datatype");
+        final String fieldName = xmlValueElement.getLocalName();
+
+        // <xs:element name="%owning object%">
+        // <xs:complexType>
+        // <xs:sequence>
+        // <xs:element name="%%field object%%">
+        // <xs:complexType>
+        // <xs:sequence>
+        // <xs:element name="nof-extensions">
+        // <xs:complexType>
+        // <xs:sequence>
+        // <xs:element name="%extensionClassShortName%"
+        // default="%extensionObjString" minOccurs="0"/>
+        // <xs:element name="%extensionClassShortName%"
+        // default="%extensionObjString" minOccurs="0"/>
+        // ...
+        // <xs:element name="%extensionClassShortName%"
+        // default="%extensionObjString" minOccurs="0"/>
+        // </xs:sequence>
+        // </xs:complexType>
+        // </xs:element>
+        // </xs:sequence>
+        // <xs:attribute ref="nof:feature" fixed="value"/>
+        // <xs:attribute ref="nof:datatype" fixed="nof:%datatype%"/>
+        // <xs:attribute ref="nof:isEmpty"/>
+        // <xs:attribute ref="nof:annotation"/>
+        // </xs:complexType>
+        // </xs:element>
+        // </xs:sequence>
+        // </xs:complexType>
+        // </xs:element>
+
+        // 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");
+
+        return xsFieldElementElement;
+    }
+
+    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 &lt;xs:element&gt; 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 name="%owning object%">
+        // <xs:complexType>
+        // <xs:sequence>
+        // <xs:element name="%%field object%%">
+        // <xs:complexType>
+        // <xs:sequence>
+        // <xs:element ref="nof:title" minOccurs="0"/>
+        // <xs:element name="nof-extensions">
+        // <xs:complexType>
+        // <xs:sequence>
+        // <xs:element name="app:%extension class short name%" minOccurs="0"
+        // maxOccurs="1" default="%value%"/>
+        // <xs:element name="app:%extension class short name%" minOccurs="0"
+        // maxOccurs="1" default="%value%"/>
+        // ...
+        // <xs:element name="app:%extension class short name%" minOccurs="0"
+        // maxOccurs="1" default="%value%"/>
+        // </xs:sequence>
+        // </xs:complexType>
+        // </xs:element>
+        // <xs:sequence minOccurs="0" maxOccurs="1"/>
+        // </xs:sequence>
+        // <xs:attribute ref="nof:feature" fixed="reference"/>
+        // <xs:attribute ref="nof:type" default="%%appX%%:%%type%%"/>
+        // <xs:attribute ref="nof:isEmpty"/>
+        // <xs:attribute ref="nof:annotation"/>
+        // </xs:complexType>
+        // </xs:element>
+        // </xs:sequence>
+        // </xs:complexType>
+        // </xs:element>
+
+        // 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", IsisSchema.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 &lt;xs:element&gt; 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 name="%owning object%">
+        // <xs:complexType>
+        // <xs:sequence>
+        // <xs:element name="%%field object%%">
+        // <xs:complexType>
+        // <xs:sequence>
+        // <xs:element ref="nof:oids" minOccurs="0" maxOccurs="1"/>
+        // <!-- nested element definitions go here -->
+        // </xs:sequence>
+        // <xs:attribute ref="nof:feature" fixed="collection"/>
+        // <xs:attribute ref="nof:type" fixed="%%appX%%:%%type%%"/>
+        // <xs:attribute ref="nof:size"/>
+        // <xs:attribute ref="nof:annotation"/>
+        // </xs:complexType>
+        // </xs:element>
+        // </xs:sequence>
+        // </xs:complexType>
+        // </xs:element>
+
+        // 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", IsisSchema.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;
+    }
+
+    /**
+     * 
+     * <pre>
+     *     xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;
+     *     xsi:schemaLocation=&quot;http://isis.apache.org/ns/app/sdm.common.fixture.schemes.ao.communications ddd.xsd&quot;
+     * </pre>
+     * 
+     * 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://isis.apache.org/ns/app/<fully qualified
+        // class name>
+        // 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 &lt;xs:element&gt; element (representing a
+     * field of an object) to the supplied element (presumed to be a
+     * <code>complexType/sequence</code>).
+     */
+    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;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/6de87443/framework/core/metamodel/src/main/java/org/apache/isis/core/runtime/snapshot/XmlSnapshot.java
----------------------------------------------------------------------
diff --git a/framework/core/metamodel/src/main/java/org/apache/isis/core/runtime/snapshot/XmlSnapshot.java b/framework/core/metamodel/src/main/java/org/apache/isis/core/runtime/snapshot/XmlSnapshot.java
new file mode 100644
index 0000000..f0591d8
--- /dev/null
+++ b/framework/core/metamodel/src/main/java/org/apache/isis/core/runtime/snapshot/XmlSnapshot.java
@@ -0,0 +1,823 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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 org.apache.isis.core.runtime.snapshot;
+
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.StringTokenizer;
+import java.util.Vector;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+
+import org.apache.log4j.Logger;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import org.apache.isis.applib.snapshot.SnapshottableWithInclusions;
+import org.apache.isis.core.commons.exceptions.IsisException;
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+import org.apache.isis.core.metamodel.adapter.oid.OidMarshaller;
+import org.apache.isis.core.metamodel.facetapi.FacetUtil;
+import org.apache.isis.core.metamodel.facets.collections.modify.CollectionFacet;
+import org.apache.isis.core.metamodel.facets.object.encodeable.EncodableFacet;
+import org.apache.isis.core.metamodel.facets.object.parseable.ParseableFacet;
+import org.apache.isis.core.metamodel.facets.object.value.ValueFacet;
+import org.apache.isis.core.metamodel.spec.ObjectSpecification;
+import org.apache.isis.core.metamodel.spec.ObjectSpecificationException;
+import org.apache.isis.core.metamodel.spec.feature.ObjectAssociation;
+import org.apache.isis.core.metamodel.spec.feature.OneToManyAssociation;
+import org.apache.isis.core.metamodel.spec.feature.OneToOneAssociation;
+
+/**
+ * Traverses object graph from specified root, so that an XML representation of
+ * the graph can be returned.
+ * 
+ * <p>
+ * Initially designed to allow snapshots to be easily created.
+ * 
+ * <p>
+ * Typical use:
+ * 
+ * <pre>
+ * XmlSnapshot snapshot = new XmlSnapshot(customer); // where customer is a
+ *                                                   // reference to an
+ *                                                   // ObjectAdapter
+ * Element customerAsXml = snapshot.toXml(); // returns customer's fields, titles
+ *                                           // of simple references, number of
+ *                                           // items in collections
+ * snapshot.include(&quot;placeOfBirth&quot;); // navigates to another object represented by
+ *                                   // simple reference &quot;placeOfBirth&quot;
+ * snapshot.include(&quot;orders/product&quot;); // navigates to all &lt;tt&gt;Order&lt;/tt&gt;s of
+ *                                     // &lt;tt&gt;Customer&lt;/tt&gt;, and from them for
+ *                                     // their &lt;tt&gt;Product&lt;/tt&gt;s
+ * </pre>
+ */
+public class XmlSnapshot {
+
+    private static final Logger LOG = Logger.getLogger(XmlSnapshot.class);
+
+    private final IsisSchema isisMetaModel;
+
+    private final Place rootPlace;
+
+    private final XmlSchema schema;
+
+    /**
+     * the suggested location for the schema (xsi:schemaLocation attribute)
+     */
+    private String schemaLocationFileName;
+    private boolean topLevelElementWritten = false;
+
+    private final Document xmlDocument;
+
+    /**
+     * root element of {@link #xmlDocument}
+     */
+    private Element xmlElement;
+    private final Document xsdDocument;
+    /**
+     * root element of {@link #xsdDocument}
+     */
+    private final Element xsdElement;
+
+    private final XsMetaModel xsMeta;
+
+    private final OidMarshaller oidMarshaller;
+
+    /**
+     * Start a snapshot at the root object, using own namespace manager.
+     * @param oidMarshaller TODO
+     */
+    public XmlSnapshot(final ObjectAdapter rootAdapter, OidMarshaller oidMarshaller) {
+        this(rootAdapter, new XmlSchema(), oidMarshaller);
+    }
+
+    /**
+     * Start a snapshot at the root object, using supplied namespace manager.
+     * @param oidMarshaller TODO
+     */
+    public XmlSnapshot(final ObjectAdapter rootAdapter, final XmlSchema schema, final OidMarshaller oidMarshaller) {
+
+        if (LOG.isDebugEnabled()) {
+            LOG.debug(".ctor(" + log("rootObj", rootAdapter) + andlog("schema", schema) + andlog("addOids", "" + true) + ")");
+        }
+
+        this.isisMetaModel = new IsisSchema();
+        this.xsMeta = new XsMetaModel();
+
+        this.schema = schema;
+        this.oidMarshaller = oidMarshaller;
+
+        final DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
+        dbf.setNamespaceAware(true);
+        DocumentBuilder db;
+        try {
+            db = dbf.newDocumentBuilder();
+            this.xmlDocument = db.newDocument();
+            this.xsdDocument = db.newDocument();
+
+            xsdElement = xsMeta.createXsSchemaElement(xsdDocument);
+
+            this.rootPlace = appendXml(rootAdapter);
+
+        } catch (final ParserConfigurationException e) {
+            LOG.error("unable to build snapshot", e);
+            throw new IsisException(e);
+        }
+
+        for (final String path : getPathsFor(rootAdapter.getObject())) {
+            include(path);
+        }
+
+    }
+
+    private List<String> getPathsFor(final Object object) {
+        if (!(object instanceof SnapshottableWithInclusions)) {
+            return Collections.emptyList();
+        }
+        final List<String> paths = ((SnapshottableWithInclusions) object).snapshotInclusions();
+        if (paths == null) {
+            return Collections.emptyList();
+        }
+        return paths;
+    }
+
+    private String andlog(final String label, final ObjectAdapter object) {
+        return ", " + log(label, object);
+    }
+
+    private String andlog(final String label, final Object object) {
+        return ", " + log(label, object);
+    }
+
+    /**
+     * Creates an Element representing this object, and appends it as the root
+     * element of the Document.
+     * 
+     * The Document must not yet have a root element Additionally, the supplied
+     * schemaManager must be populated with any application-level namespaces
+     * referenced in the document that the parentElement resides within.
+     * (Normally this is achieved simply by using appendXml passing in a new
+     * schemaManager - see {@link #toXml()}or {@link XmlSnapshot}).
+     */
+    private Place appendXml(final ObjectAdapter object) {
+
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("appendXml(" + log("obj", object) + "')");
+        }
+
+        final String fullyQualifiedClassName = object.getSpecification().getFullIdentifier();
+
+        schema.setUri(fullyQualifiedClassName); // derive
+        // URI
+        // from
+        // fully
+        // qualified
+        // name
+
+        final Place place = objectToElement(object);
+
+        final Element element = place.getXmlElement();
+        final Element xsElementElement = place.getXsdElement();
+
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("appendXml(NO): add as element to XML doc");
+        }
+        getXmlDocument().appendChild(element);
+
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("appendXml(NO): add as xs:element to xs:schema of the XSD document");
+        }
+        getXsdElement().appendChild(xsElementElement);
+
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("appendXml(NO): set target name in XSD, derived from FQCN of obj");
+        }
+        schema.setTargetNamespace(getXsdDocument(), fullyQualifiedClassName);
+
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("appendXml(NO): set schema location file name to XSD, derived from FQCN of obj");
+        }
+        final String schemaLocationFileName = fullyQualifiedClassName + ".xsd";
+        schema.assignSchema(getXmlDocument(), fullyQualifiedClassName, schemaLocationFileName);
+
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("appendXml(NO): copy into snapshot obj");
+        }
+        setXmlElement(element);
+        setSchemaLocationFileName(schemaLocationFileName);
+
+        return place;
+    }
+
+    /**
+     * Creates an Element representing this object, and appends it to the
+     * supplied parentElement, provided that an element for the object is not
+     * already appended.
+     * 
+     * The method uses the OID to determine if an object's element is already
+     * present. If the object is not yet persistent, then the hashCode is used
+     * instead.
+     * 
+     * The parentElement must have an owner document, and should define the nof
+     * namespace. Additionally, the supplied schemaManager must be populated
+     * with any application-level namespaces referenced in the document that the
+     * parentElement resides within. (Normally this is achieved simply by using
+     * appendXml passing in a rootElement and a new schemaManager - see
+     * {@link #toXml()}or {@link XmlSnapshot}).
+     */
+    private Element appendXml(final Place parentPlace, final ObjectAdapter childObject) {
+
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("appendXml(" + log("parentPlace", parentPlace) + andlog("childObj", childObject) + ")");
+        }
+
+        final Element parentElement = parentPlace.getXmlElement();
+        final Element parentXsElement = parentPlace.getXsdElement();
+
+        if (parentElement.getOwnerDocument() != getXmlDocument()) {
+            throw new IllegalArgumentException("parent XML Element must have snapshot's XML document as its owner");
+        }
+
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("appendXml(Pl, NO): invoking objectToElement() for " + log("childObj", childObject));
+        }
+        final Place childPlace = objectToElement(childObject);
+        Element childElement = childPlace.getXmlElement();
+        final Element childXsElement = childPlace.getXsdElement();
+
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("appendXml(Pl, NO): invoking mergeTree of parent with child");
+        }
+        childElement = mergeTree(parentElement, childElement);
+
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("appendXml(Pl, NO): adding XS Element to schema if required");
+        }
+        schema.addXsElementIfNotPresent(parentXsElement, childXsElement);
+
+        return childElement;
+    }
+
+    private boolean appendXmlThenIncludeRemaining(final Place parentPlace, final ObjectAdapter referencedObject, final Vector fieldNames, final String annotation) {
+
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("appendXmlThenIncludeRemaining(: " + log("parentPlace", parentPlace) + andlog("referencedObj", referencedObject) + andlog("fieldNames", fieldNames) + andlog("annotation", annotation) + ")");
+            LOG.debug("appendXmlThenIncludeRemaining(..): invoking appendXml(parentPlace, referencedObject)");
+        }
+
+        final Element referencedElement = appendXml(parentPlace, referencedObject);
+        final Place referencedPlace = new Place(referencedObject, referencedElement);
+
+        final boolean includedField = includeField(referencedPlace, fieldNames, annotation);
+
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("appendXmlThenIncludeRemaining(..): invoked includeField(referencedPlace, fieldNames)" + andlog("returned", "" + includedField));
+        }
+
+        return includedField;
+    }
+
+    private Vector elementsUnder(final Element parentElement, final String localName) {
+        final Vector v = new Vector();
+        final NodeList existingNodes = parentElement.getChildNodes();
+        for (int i = 0; i < existingNodes.getLength(); i++) {
+            final Node node = existingNodes.item(i);
+            if (!(node instanceof Element)) {
+                continue;
+            }
+            final Element element = (Element) node;
+            if (localName.equals("*") || element.getLocalName().equals(localName)) {
+                v.addElement(element);
+            }
+        }
+        return v;
+    }
+
+    public ObjectAdapter getObject() {
+        return rootPlace.getObject();
+    }
+
+    public XmlSchema getSchema() {
+        return schema;
+    }
+
+    /**
+     * The name of the <code>xsi:schemaLocation</code> in the XML document.
+     * 
+     * Taken from the <code>fullyQualifiedClassName</code> (which also is used
+     * as the basis for the <code>targetNamespace</code>.
+     * 
+     * Populated in {@link #appendXml(ObjectAdapter)}.
+     */
+    public String getSchemaLocationFileName() {
+        return schemaLocationFileName;
+    }
+
+    public Document getXmlDocument() {
+        return xmlDocument;
+    }
+
+    /**
+     * The root element of {@link #getXmlDocument()}. Returns <code>null</code>
+     * until the snapshot has actually been built.
+     */
+    public Element getXmlElement() {
+        return xmlElement;
+    }
+
+    public Document getXsdDocument() {
+        return xsdDocument;
+    }
+
+    /**
+     * The root element of {@link #getXsdDocument()}. Returns <code>null</code>
+     * until the snapshot has actually been built.
+     */
+    public Element getXsdElement() {
+        return xsdElement;
+    }
+
+    public void include(final String path) {
+        include(path, null);
+    }
+
+    public void include(final String path, final String annotation) {
+
+        // tokenize into successive fields
+        final Vector fieldNames = new Vector();
+        for (final StringTokenizer tok = new StringTokenizer(path, "/"); tok.hasMoreTokens();) {
+            final String token = tok.nextToken();
+
+            if (LOG.isDebugEnabled()) {
+                LOG.debug("include(..): " + log("token", token));
+            }
+            fieldNames.addElement(token);
+        }
+
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("include(..): " + log("fieldNames", fieldNames));
+        }
+
+        // navigate first field, from the root.
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("include(..): invoking includeField");
+        }
+        includeField(rootPlace, fieldNames, annotation);
+    }
+
+    /**
+     * @return true if able to navigate the complete vector of field names
+     *         successfully; false if a field could not be located or it turned
+     *         out to be a value.
+     */
+    private boolean includeField(final Place place, final Vector fieldNames, final String annotation) {
+
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("includeField(: " + log("place", place) + andlog("fieldNames", fieldNames) + andlog("annotation", annotation) + ")");
+        }
+
+        final ObjectAdapter object = place.getObject();
+        final Element xmlElement = place.getXmlElement();
+
+        // we use a copy of the path so that we can safely traverse collections
+        // without side-effects
+        final Vector originalNames = fieldNames;
+        final Vector names = new Vector();
+        for (final java.util.Enumeration e = originalNames.elements(); e.hasMoreElements();) {
+            names.addElement(e.nextElement());
+        }
+
+        // see if we have any fields to process
+        if (names.size() == 0) {
+            return true;
+        }
+
+        // take the first field name from the list, and remove
+        final String fieldName = (String) names.elementAt(0);
+        names.removeElementAt(0);
+
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("includeField(Pl, Vec, Str):" + log("processing field", fieldName) + andlog("left", "" + names.size()));
+        }
+
+        // locate the field in the object's class
+        final ObjectSpecification nos = object.getSpecification();
+        ObjectAssociation field = null;
+        try {
+            // HACK: really want a ObjectSpecification.hasField method to
+            // check first.
+            field = nos.getAssociation(fieldName);
+        } catch (final ObjectSpecificationException ex) {
+            if (LOG.isInfoEnabled()) {
+                LOG.info("includeField(Pl, Vec, Str): could not locate field, skipping");
+            }
+            return false;
+        }
+
+        // locate the corresponding XML element
+        // (the corresponding XSD element will later be attached to xmlElement
+        // as its userData)
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("includeField(Pl, Vec, Str): locating corresponding XML element");
+        }
+        final Vector xmlFieldElements = elementsUnder(xmlElement, field.getId());
+        if (xmlFieldElements.size() != 1) {
+            if (LOG.isInfoEnabled()) {
+                LOG.info("includeField(Pl, Vec, Str): could not locate " + log("field", field.getId()) + andlog("xmlFieldElements.size", "" + xmlFieldElements.size()));
+            }
+            return false;
+        }
+        final Element xmlFieldElement = (Element) xmlFieldElements.elementAt(0);
+
+        if (names.size() == 0 && annotation != null) {
+            // nothing left in the path, so we will apply the annotation now
+            isisMetaModel.setAnnotationAttribute(xmlFieldElement, annotation);
+        }
+
+        final Place fieldPlace = new Place(object, xmlFieldElement);
+
+        if (field instanceof OneToOneAssociation) {
+            if (field.getSpecification().getAssociations().size() == 0) {
+                if (LOG.isDebugEnabled()) {
+                    LOG.debug("includeField(Pl, Vec, Str): field is value; done");
+                }
+                return false;
+            }
+
+            if (LOG.isDebugEnabled()) {
+                LOG.debug("includeField(Pl, Vec, Str): field is 1->1");
+            }
+
+            final OneToOneAssociation oneToOneAssociation = ((OneToOneAssociation) field);
+            final ObjectAdapter referencedObject = oneToOneAssociation.get(fieldPlace.getObject());
+
+            if (referencedObject == null) {
+                return true; // not a failure if the reference was null
+            }
+
+            final boolean appendedXml = appendXmlThenIncludeRemaining(fieldPlace, referencedObject, names, annotation);
+            if (LOG.isDebugEnabled()) {
+                LOG.debug("includeField(Pl, Vec, Str): 1->1: invoked appendXmlThenIncludeRemaining for " + log("referencedObj", referencedObject) + andlog("returned", "" + appendedXml));
+            }
+
+            return appendedXml;
+
+        } else if (field instanceof OneToManyAssociation) {
+            if (LOG.isDebugEnabled()) {
+                LOG.debug("includeField(Pl, Vec, Str): field is 1->M");
+            }
+
+            final OneToManyAssociation oneToManyAssociation = (OneToManyAssociation) field;
+            final ObjectAdapter collection = oneToManyAssociation.get(fieldPlace.getObject());
+            final CollectionFacet facet = collection.getSpecification().getFacet(CollectionFacet.class);
+
+            if (LOG.isDebugEnabled()) {
+                LOG.debug("includeField(Pl, Vec, Str): 1->M: " + log("collection.size", "" + facet.size(collection)));
+            }
+            boolean allFieldsNavigated = true;
+            final Enumeration elements = facet.elements(collection);
+            while (elements.hasMoreElements()) {
+                final ObjectAdapter referencedObject = (ObjectAdapter) elements.nextElement();
+                final boolean appendedXml = appendXmlThenIncludeRemaining(fieldPlace, referencedObject, names, annotation);
+                if (LOG.isDebugEnabled()) {
+                    LOG.debug("includeField(Pl, Vec, Str): 1->M: + invoked appendXmlThenIncludeRemaining for " + log("referencedObj", referencedObject) + andlog("returned", "" + appendedXml));
+                }
+                allFieldsNavigated = allFieldsNavigated && appendedXml;
+            }
+            LOG.debug("includeField(Pl, Vec, Str): " + log("returning", "" + allFieldsNavigated));
+            return allFieldsNavigated;
+        }
+
+        return false; // fall through, shouldn't get here but just in
+        // case.
+    }
+
+    private String log(final String label, final ObjectAdapter adapter) {
+        return log(label, (adapter == null ? "(null)" : adapter.titleString() + "[" + oidAsString(adapter) + "]"));
+    }
+
+    private String log(final String label, final Object pojo) {
+        return (label == null ? "?" : label) + "='" + (pojo == null ? "(null)" : pojo.toString()) + "'";
+    }
+
+    /**
+     * Merges the tree of Elements whose root is <code>childElement</code>
+     * underneath the <code>parentElement</code>.
+     * 
+     * If the <code>parentElement</code> already has an element that matches the
+     * <code>childElement</code>, then recursively attaches the grandchildren
+     * instead.
+     * 
+     * The element returned will be either the supplied
+     * <code>childElement</code>, or an existing child element if one already
+     * existed under <code>parentElement</code>.
+     */
+    private Element mergeTree(final Element parentElement, final Element childElement) {
+
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("mergeTree(" + log("parent", parentElement) + andlog("child", childElement));
+        }
+
+        final String childElementOid = isisMetaModel.getAttribute(childElement, "oid");
+
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("mergeTree(El,El): " + log("childOid", childElementOid));
+        }
+        if (childElementOid != null) {
+
+            // before we add the child element, check to see if it is already
+            // there
+            if (LOG.isDebugEnabled()) {
+                LOG.debug("mergeTree(El,El): check if child already there");
+            }
+            final Vector existingChildElements = elementsUnder(parentElement, childElement.getLocalName());
+            for (final Enumeration childEnum = existingChildElements.elements(); childEnum.hasMoreElements();) {
+                final Element possibleMatchingElement = (Element) childEnum.nextElement();
+
+                final String possibleMatchOid = isisMetaModel.getAttribute(possibleMatchingElement, "oid");
+                if (possibleMatchOid == null || !possibleMatchOid.equals(childElementOid)) {
+                    continue;
+                }
+
+                if (LOG.isDebugEnabled()) {
+                    LOG.debug("mergeTree(El,El): child already there; merging grandchildren");
+                }
+
+                // match: transfer the children of the child (grandchildren) to
+                // the
+                // already existing matching child
+                final Element existingChildElement = possibleMatchingElement;
+                final Vector grandchildrenElements = elementsUnder(childElement, "*");
+                for (final Enumeration grandchildEnum = grandchildrenElements.elements(); grandchildEnum.hasMoreElements();) {
+                    final Element grandchildElement = (Element) grandchildEnum.nextElement();
+                    childElement.removeChild(grandchildElement);
+
+                    if (LOG.isDebugEnabled()) {
+                        LOG.debug("mergeTree(El,El): merging " + log("grandchild", grandchildElement));
+                    }
+
+                    mergeTree(existingChildElement, grandchildElement);
+                }
+                return existingChildElement;
+            }
+        }
+
+        parentElement.appendChild(childElement);
+        return childElement;
+    }
+
+    Place objectToElement(final ObjectAdapter object) {
+
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("objectToElement(" + log("object", object) + ")");
+        }
+
+        final ObjectSpecification nos = object.getSpecification();
+
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("objectToElement(NO): create element and nof:title");
+        }
+        final Element element = schema.createElement(getXmlDocument(), nos.getShortIdentifier(), nos.getFullIdentifier(), nos.getSingularName(), nos.getPluralName());
+        isisMetaModel.appendNofTitle(element, object.titleString());
+
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("objectToElement(NO): create XS element for NOF class");
+        }
+        final Element xsElement = schema.createXsElementForNofClass(getXsdDocument(), element, topLevelElementWritten, FacetUtil.getFacetsByType(nos));
+
+        // hack: every element in the XSD schema apart from first needs minimum
+        // cardinality setting.
+        topLevelElementWritten = true;
+
+        final Place place = new Place(object, element);
+
+        isisMetaModel.setAttributesForClass(element, oidAsString(object).toString());
+
+        final List<ObjectAssociation> fields = nos.getAssociations();
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("objectToElement(NO): processing fields");
+        }
+        eachField: for (int i = 0; i < fields.size(); i++) {
+            final ObjectAssociation field = fields.get(i);
+            final String fieldName = field.getId();
+
+            if (LOG.isDebugEnabled()) {
+                LOG.debug("objectToElement(NO): " + log("field", fieldName));
+            }
+
+            // Skip field if we have seen the name already
+            // This is a workaround for getLastActivity(). This method exists
+            // in AbstractObjectAdapter, but is not (at some level) being picked
+            // up
+            // by the dot-net reflector as a property. On the other hand it does
+            // exist as a field in the meta model (ObjectSpecification).
+            //
+            // Now, to re-expose the lastactivity field for .Net, a
+            // deriveLastActivity()
+            // has been added to BusinessObject. This caused another field of
+            // the
+            // same name, ultimately breaking the XSD.
+            for (int j = 0; j < i; j++) {
+                if (fieldName.equals(fields.get(i).getName())) {
+                    LOG.debug("objectToElement(NO): " + log("field", fieldName) + " SKIPPED");
+                    continue eachField;
+                }
+            }
+
+            Element xmlFieldElement = getXmlDocument().createElementNS(schema.getUri(), // scoped
+                                                                                        // by
+                                                                                        // namespace
+                    // of class of
+                    // containing object
+                    schema.getPrefix() + ":" + fieldName);
+
+            Element xsdFieldElement = null;
+
+            if (field.getSpecification().containsFacet(ValueFacet.class)) {
+                if (LOG.isDebugEnabled()) {
+                    LOG.debug("objectToElement(NO): " + log("field", fieldName) + " is value");
+                }
+
+                final ObjectSpecification fieldNos = field.getSpecification();
+                // skip fields of type XmlValue
+                if (fieldNos == null) {
+                    continue eachField;
+                }
+                if (fieldNos.getFullIdentifier() != null && fieldNos.getFullIdentifier().endsWith("XmlValue")) {
+                    continue eachField;
+                }
+
+                final OneToOneAssociation valueAssociation = ((OneToOneAssociation) field);
+                final Element xmlValueElement = xmlFieldElement; // more
+                                                                 // meaningful
+                                                                 // locally
+                                                                 // scoped name
+
+                ObjectAdapter value;
+                try {
+                    value = valueAssociation.get(object);
+
+                    final ObjectSpecification valueNos = value.getSpecification();
+
+                    // XML
+                    isisMetaModel.setAttributesForValue(xmlValueElement, valueNos.getShortIdentifier());
+
+                    // return parsed string, else encoded string, else title.
+                    String valueStr;
+                    final ParseableFacet parseableFacet = fieldNos.getFacet(ParseableFacet.class);
+                    final EncodableFacet encodeableFacet = fieldNos.getFacet(EncodableFacet.class);
+                    if (parseableFacet != null) {
+                        valueStr = parseableFacet.parseableTitle(value);
+                    } else if (encodeableFacet != null) {
+                        valueStr = encodeableFacet.toEncodedString(value);
+                    } else {
+                        valueStr = value.titleString();
+                    }
+
+                    final boolean notEmpty = (valueStr.length() > 0);
+                    if (notEmpty) {
+                        xmlValueElement.appendChild(getXmlDocument().createTextNode(valueStr));
+                    } else {
+                        isisMetaModel.setIsEmptyAttribute(xmlValueElement, true);
+                    }
+
+                } catch (final Exception ex) {
+                    LOG.warn("objectToElement(NO): " + log("field", fieldName) + ": getField() threw exception - skipping XML generation");
+                }
+
+                // XSD
+                xsdFieldElement = schema.createXsElementForNofValue(xsElement, xmlValueElement, FacetUtil.getFacetsByType(valueAssociation));
+
+            } else if (field instanceof OneToOneAssociation) {
+
+                if (LOG.isDebugEnabled()) {
+                    LOG.debug("objectToElement(NO): " + log("field", fieldName) + " is OneToOneAssociation");
+                }
+
+                final OneToOneAssociation oneToOneAssociation = ((OneToOneAssociation) field);
+                final String fullyQualifiedClassName = nos.getFullIdentifier();
+                final Element xmlReferenceElement = xmlFieldElement; // more
+                                                                     // meaningful
+                                                                     // locally
+                                                                     // scoped
+                                                                     // name
+
+                ObjectAdapter referencedObjectAdapter;
+
+                try {
+                    referencedObjectAdapter = oneToOneAssociation.get(object);
+
+                    // XML
+                    isisMetaModel.setAttributesForReference(xmlReferenceElement, schema.getPrefix(), fullyQualifiedClassName);
+
+                    if (referencedObjectAdapter != null) {
+                        isisMetaModel.appendNofTitle(xmlReferenceElement, referencedObjectAdapter.titleString());
+                    } else {
+                        isisMetaModel.setIsEmptyAttribute(xmlReferenceElement, true);
+                    }
+
+                } catch (final Exception ex) {
+                    LOG.warn("objectToElement(NO): " + log("field", fieldName) + ": getAssociation() threw exception - skipping XML generation");
+                }
+
+                // XSD
+                xsdFieldElement = schema.createXsElementForNofReference(xsElement, xmlReferenceElement, oneToOneAssociation.getSpecification().getFullIdentifier(), FacetUtil.getFacetsByType(oneToOneAssociation));
+
+            } else if (field instanceof OneToManyAssociation) {
+
+                if (LOG.isDebugEnabled()) {
+                    LOG.debug("objectToElement(NO): " + log("field", fieldName) + " is OneToManyAssociation");
+                }
+
+                final OneToManyAssociation oneToManyAssociation = (OneToManyAssociation) field;
+                final Element xmlCollectionElement = xmlFieldElement; // more meaningful locally scoped name
+
+                ObjectAdapter collection;
+                try {
+                    collection = oneToManyAssociation.get(object);
+                    final ObjectSpecification referencedTypeNos = oneToManyAssociation.getSpecification();
+                    final String fullyQualifiedClassName = referencedTypeNos.getFullIdentifier();
+
+                    // XML
+                    isisMetaModel.setIsisCollection(xmlCollectionElement, schema.getPrefix(), fullyQualifiedClassName, collection);
+                } catch (final Exception ex) {
+                    LOG.warn("objectToElement(NO): " + log("field", fieldName) + ": get(obj) threw exception - skipping XML generation");
+                }
+
+                // XSD
+                xsdFieldElement = schema.createXsElementForNofCollection(xsElement, xmlCollectionElement, oneToManyAssociation.getSpecification().getFullIdentifier(), FacetUtil.getFacetsByType(oneToManyAssociation));
+
+            } else {
+                if (LOG.isInfoEnabled()) {
+                    LOG.info("objectToElement(NO): " + log("field", fieldName) + " is unknown type; ignored");
+                }
+                continue;
+            }
+
+            if (xsdFieldElement != null) {
+                Place.setXsdElement(xmlFieldElement, xsdFieldElement);
+            }
+
+            // XML
+            if (LOG.isDebugEnabled()) {
+                LOG.debug("objectToElement(NO): invoking mergeTree for field");
+            }
+            xmlFieldElement = mergeTree(element, xmlFieldElement);
+
+            // XSD
+            if (xsdFieldElement != null) {
+                if (LOG.isDebugEnabled()) {
+                    LOG.debug("objectToElement(NO): adding XS element for field to schema");
+                }
+                schema.addFieldXsElement(xsElement, xsdFieldElement);
+            }
+        }
+
+        return place;
+    }
+
+    private String oidAsString(final ObjectAdapter adapter) {
+        return adapter.getOid().enString(oidMarshaller);
+    }
+
+    /**
+     * @param schemaLocationFileName
+     *            The schemaLocationFileName to set.
+     */
+    private void setSchemaLocationFileName(final String schemaLocationFileName) {
+        this.schemaLocationFileName = schemaLocationFileName;
+    }
+
+    /**
+     * @param xmlElement
+     *            The xmlElement to set.
+     */
+    private void setXmlElement(final Element xmlElement) {
+        this.xmlElement = xmlElement;
+    }
+
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/6de87443/framework/core/metamodel/src/main/java/org/apache/isis/core/runtime/snapshot/XsMetaModel.java
----------------------------------------------------------------------
diff --git a/framework/core/metamodel/src/main/java/org/apache/isis/core/runtime/snapshot/XsMetaModel.java b/framework/core/metamodel/src/main/java/org/apache/isis/core/runtime/snapshot/XsMetaModel.java
new file mode 100644
index 0000000..4d759e9
--- /dev/null
+++ b/framework/core/metamodel/src/main/java/org/apache/isis/core/runtime/snapshot/XsMetaModel.java
@@ -0,0 +1,337 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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 org.apache.isis.core.runtime.snapshot;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+
+/**
+ * Stateless utility methods relating to the w3.org schema and schema-instance
+ * meta models.
+ */
+public final class XsMetaModel {
+
+    private final Helper helper;
+    /**
+     * URI representing the namespace of the in-built xmlns namespace as defined
+     * by w3.org.
+     * 
+     * The NamespaceManager will not allow any namespaces with this URI to be
+     * added.
+     */
+    public static final String W3_ORG_XMLNS_URI = "http://www.w3.org/2000/xmlns/";
+    /**
+     * Namespace prefix for {@link W3_ORG_XMLNS_URI}.
+     * 
+     * The NamespaceManager will not allow any namespace to use this prefix.
+     */
+    public static final String W3_ORG_XMLNS_PREFIX = "xmlns";
+    /**
+     * Namespace prefix for XML schema.
+     */
+    public static final String W3_ORG_XS_URI = "http://www.w3.org/2001/XMLSchema";
+    /**
+     * Namespace prefix for {@link W3_ORG_XS_URI}.
+     * 
+     * The NamespaceManager will not allow any namespace to use this prefix.
+     */
+    public static final String W3_ORG_XS_PREFIX = "xs";
+    /**
+     * Namespace prefix for XML schema instance.
+     */
+    public static final String W3_ORG_XSI_URI = "http://www.w3.org/2001/XMLSchema-instance";
+    /**
+     * Namespace prefix for {@link W3_ORG_XSI_URI}.
+     * 
+     * The NamespaceManager will not allow any namespace to use this prefix.
+     */
+    public static final String W3_ORG_XSI_PREFIX = "xsi";
+
+    private final IsisSchema nofMeta;
+
+    public XsMetaModel() {
+        this.helper = new Helper();
+        this.nofMeta = new IsisSchema();
+    }
+
+    /**
+     * Creates an &lt;xs:schema&gt; element for the document to the provided
+     * element, attaching to root of supplied Xsd doc.
+     * 
+     * In addition:
+     * <ul>
+     * <li>the elementFormDefault is set
+     * <li>the NOF namespace is set
+     * <li>the <code>xs:import</code> element referencing the NOF namespace is
+     * added as a child
+     * </ul>
+     */
+    Element createXsSchemaElement(final Document xsdDoc) {
+        if (xsdDoc.getDocumentElement() != null) {
+            throw new IllegalArgumentException("XSD document already has content");
+        }
+        final Element xsSchemaElement = createXsElement(xsdDoc, "schema");
+
+        xsSchemaElement.setAttribute("elementFormDefault", "qualified");
+
+        nofMeta.addNamespace(xsSchemaElement);
+
+        xsdDoc.appendChild(xsSchemaElement);
+        final Element xsImportElement = createXsElement(xsdDoc, "import");
+        xsImportElement.setAttribute("namespace", IsisSchema.NS_URI);
+        xsImportElement.setAttribute("schemaLocation", IsisSchema.DEFAULT_LOCATION);
+
+        xsSchemaElement.appendChild(xsImportElement);
+
+        return xsSchemaElement;
+    }
+
+    Element createXsElementElement(final Document xsdDoc, final String className) {
+        return createXsElementElement(xsdDoc, className, true);
+    }
+
+    Element createXsElementElement(final Document xsdDoc, final String className, final boolean includeCardinality) {
+        final Element xsElementElement = createXsElement(xsdDoc, "element");
+        xsElementElement.setAttribute("name", className);
+        if (includeCardinality) {
+            setXsCardinality(xsElementElement, 0, Integer.MAX_VALUE);
+        }
+        return xsElementElement;
+    }
+
+    /**
+     * Creates an element in the XS namespace, adding the definition of the
+     * namespace to the root element of the document if required,
+     */
+    Element createXsElement(final Document xsdDoc, final String localName) {
+
+        final Element element = xsdDoc.createElementNS(XsMetaModel.W3_ORG_XS_URI, XsMetaModel.W3_ORG_XS_PREFIX + ":" + localName);
+        // xmlns:xs="..." added to root
+        helper.rootElementFor(element).setAttributeNS(XsMetaModel.W3_ORG_XMLNS_URI, XsMetaModel.W3_ORG_XMLNS_PREFIX + ":" + XsMetaModel.W3_ORG_XS_PREFIX, XsMetaModel.W3_ORG_XS_URI);
+        return element;
+    }
+
+    // private Element addAnyToSequence(final Element xsSequenceElement) {
+    // Element xsAnyElement = createXsElement(docFor(xsSequenceElement), "any");
+    // xsAnyElement.setAttribute("namespace", "##other");
+    // xsAnyElement.setAttribute("minOccurs", "0");
+    // xsAnyElement.setAttribute("maxOccurs", "unbounded");
+    // xsAnyElement.setAttribute("processContents", "lax");
+    // xsSequenceElement.appendChild(xsAnyElement);
+    // return xsSequenceElement;
+    // }
+
+    /**
+     * Creates an xs:attribute ref="nof:xxx" element, and appends to specified
+     * owning element.
+     */
+    Element addXsNofAttribute(final Element parentXsElement, final String nofAttributeRef) {
+        return addXsNofAttribute(parentXsElement, nofAttributeRef, null);
+    }
+
+    /**
+     * Adds <code>xs:attribute ref="nof:xxx" fixed="yyy"</code> element, and
+     * appends to specified parent XSD element.
+     */
+    Element addXsNofAttribute(final Element parentXsElement, final String nofAttributeRef, final String fixedValue) {
+        return addXsNofAttribute(parentXsElement, nofAttributeRef, fixedValue, true);
+    }
+
+    /**
+     * Adds <code>xs:attribute ref="nof:xxx" default="yyy"</code> element, and
+     * appends to specified parent XSD element.
+     * 
+     * The last parameter determines whether to use <code>fixed="yyy"</code>
+     * rather than <code>default="yyy"</code>.
+     */
+    Element addXsNofAttribute(final Element parentXsElement, final String nofAttributeRef, final String value, final boolean useFixed) {
+        final Element xsNofAttributeElement = createXsElement(helper.docFor(parentXsElement), "attribute");
+        xsNofAttributeElement.setAttribute("ref", IsisSchema.NS_PREFIX + ":" + nofAttributeRef);
+        parentXsElement.appendChild(xsNofAttributeElement);
+        if (value != null) {
+            if (useFixed) {
+                xsNofAttributeElement.setAttribute("fixed", value);
+            } else {
+                xsNofAttributeElement.setAttribute("default", value);
+            }
+        }
+        return parentXsElement;
+    }
+
+    /**
+     * Adds <code>xs:attribute ref="nof:feature" fixed="(feature)"</code>
+     * element as child to supplied XSD element, presumed to be an
+     * <xs:complexType</code>.
+     */
+    Element addXsNofFeatureAttributeElements(final Element parentXsElement, final String feature) {
+        final Element xsNofFeatureAttributeElement = createXsElement(helper.docFor(parentXsElement), "attribute");
+        xsNofFeatureAttributeElement.setAttribute("ref", "nof:feature");
+        xsNofFeatureAttributeElement.setAttribute("fixed", feature);
+        parentXsElement.appendChild(xsNofFeatureAttributeElement);
+        return xsNofFeatureAttributeElement;
+    }
+
+    /**
+     * returns child <code>xs:complexType</code> element allowing mixed content
+     * for supplied parent XSD element, creating and appending if necessary.
+     * 
+     * <p>
+     * The supplied element is presumed to be one for which
+     * <code>xs:complexType</code> is valid as a child (eg
+     * <code>xs:element</code>).
+     */
+    Element complexTypeFor(final Element parentXsElement) {
+        return complexTypeFor(parentXsElement, true);
+    }
+
+    /**
+     * returns child <code>xs:complexType</code> element, optionally allowing
+     * mixed content, for supplied parent XSD element, creating and appending if
+     * necessary.
+     * 
+     * <p>
+     * The supplied element is presumed to be one for which
+     * <code>xs:complexType</code> is valid as a child (eg
+     * <code>xs:element</code>).
+     */
+    Element complexTypeFor(final Element parentXsElement, final boolean mixed) {
+        final Element el = childXsElement(parentXsElement, "complexType");
+        if (mixed) {
+            el.setAttribute("mixed", "true");
+        }
+        return el;
+    }
+
+    /**
+     * returns child <code>xs:sequence</code> element for supplied parent XSD
+     * element, creating and appending if necessary.
+     * 
+     * The supplied element is presumed to be one for which
+     * <code>xs:simpleContent</code> is valid as a child (eg
+     * <code>xs:complexType</code>).
+     */
+    Element sequenceFor(final Element parentXsElement) {
+        return childXsElement(parentXsElement, "sequence");
+    }
+
+    /**
+     * returns child <code>xs:choice</code> element for supplied parent XSD
+     * element, creating and appending if necessary.
+     * 
+     * The supplied element is presumed to be one for which
+     * <code>xs:simpleContent</code> is valid as a child (eg
+     * <code>xs:complexType</code>).
+     */
+    Element choiceFor(final Element parentXsElement) {
+        return childXsElement(parentXsElement, "choice");
+    }
+
+    Element sequenceForComplexTypeFor(final Element parentXsElement) {
+        return sequenceFor(complexTypeFor(parentXsElement));
+    }
+
+    Element choiceForComplexTypeFor(final Element parentXsElement) {
+        return choiceFor(complexTypeFor(parentXsElement));
+    }
+
+    /**
+     * Returns the <code>xs:choice</code> or <code>xs:sequence</code> element
+     * under the supplied XSD element, or null if neither can be found.
+     */
+    Element choiceOrSequenceFor(final Element parentXsElement) {
+        final NodeList choiceNodeList = parentXsElement.getElementsByTagNameNS(XsMetaModel.W3_ORG_XS_URI, "choice");
+        if (choiceNodeList.getLength() > 0) {
+            return (Element) choiceNodeList.item(0);
+        }
+        final NodeList sequenceNodeList = parentXsElement.getElementsByTagNameNS(XsMetaModel.W3_ORG_XS_URI, "sequence");
+        if (sequenceNodeList.getLength() > 0) {
+            return (Element) sequenceNodeList.item(0);
+        }
+        return null;
+    }
+
+    /**
+     * returns child <code>xs:simpleContent</code> element for supplied parent
+     * XSD element, creating and appending if necessary.
+     * 
+     * The supplied element is presumed to be one for which
+     * <code>xs:simpleContent</code> is valid as a child (eg
+     * <code>xs:complexType</code>).
+     */
+    Element simpleContentFor(final Element parentXsElement) {
+        return childXsElement(parentXsElement, "simpleContent");
+    }
+
+    /**
+     * returns child <code>xs:extension</code> element for supplied parent XSD
+     * element, creating and appending if nec; also sets the <code>base</code>
+     * attribute.
+     * 
+     * The supplied element is presumed to be one for which
+     * <code>xs:extension</code> is valid as a child (eg
+     * <code>xs:complexType</code>).
+     */
+    Element extensionFor(final Element parentXsElement, final String base) {
+        final Element childXsElement = childXsElement(parentXsElement, "extension");
+        childXsElement.setAttribute("base", XsMetaModel.W3_ORG_XS_PREFIX + ":" + base);
+        return childXsElement;
+    }
+
+    Element childXsElement(final Element parentXsElement, final String localName) {
+        final NodeList nodeList = parentXsElement.getElementsByTagNameNS(XsMetaModel.W3_ORG_XS_URI, localName);
+        if (nodeList.getLength() > 0) {
+            return (Element) nodeList.item(0);
+        }
+
+        final Element childXsElement = createXsElement(helper.docFor(parentXsElement), localName);
+        parentXsElement.appendChild(childXsElement);
+
+        return childXsElement;
+    }
+
+    /**
+     * @return the <code>xs:schema</code> element (the root element of the
+     *         owning XSD Doc).
+     */
+    Element schemaFor(final Element xsElement) {
+        return xsElement.getOwnerDocument().getDocumentElement();
+    }
+
+    /**
+     * Sets the <code>minOccurs</code> and <code>maxOccurs</code> attributes for
+     * provided <code>element</code> (presumed to be an XSD element for which
+     * these attributes makes sense.
+     */
+    Element setXsCardinality(final Element xsElement, final int minOccurs, final int maxOccurs) {
+        if (maxOccurs >= 0) {
+            xsElement.setAttribute("minOccurs", "" + minOccurs);
+        }
+        if (maxOccurs >= 0) {
+            if (maxOccurs == Integer.MAX_VALUE) {
+                xsElement.setAttribute("maxOccurs", "unbounded");
+            } else {
+                xsElement.setAttribute("maxOccurs", "" + maxOccurs);
+            }
+        }
+        return xsElement;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/6de87443/framework/core/metamodel/src/main/java/org/apache/isis/core/runtime/sysout/SystemPrinter.java
----------------------------------------------------------------------
diff --git a/framework/core/metamodel/src/main/java/org/apache/isis/core/runtime/sysout/SystemPrinter.java b/framework/core/metamodel/src/main/java/org/apache/isis/core/runtime/sysout/SystemPrinter.java
new file mode 100644
index 0000000..76c6776
--- /dev/null
+++ b/framework/core/metamodel/src/main/java/org/apache/isis/core/runtime/sysout/SystemPrinter.java
@@ -0,0 +1,148 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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 org.apache.isis.core.runtime.sysout;
+
+import java.io.File;
+import java.io.FileReader;
+import java.io.FilenameFilter;
+import java.io.LineNumberReader;
+import java.io.PrintStream;
+import java.util.Enumeration;
+import java.util.Locale;
+import java.util.Properties;
+import java.util.TimeZone;
+
+import org.apache.isis.core.commons.exceptions.IsisException;
+import org.apache.isis.core.commons.lang.IoUtils;
+import org.apache.isis.core.runtime.about.AboutIsis;
+
+public class SystemPrinter {
+
+    private final PrintStream output;
+
+    public SystemPrinter() {
+        this(System.out);
+    }
+
+    public SystemPrinter(final PrintStream output) {
+        this.output = output;
+    }
+
+    protected PrintStream getOutput() {
+        return output;
+    }
+
+    void print(final String string) {
+        output.println(string);
+    }
+
+    void printBlock(final String title) {
+        print("");
+        print("------------------------------------------");
+        print(title);
+        print("------------------------------------------");
+    }
+
+    public void printDiagnostics() {
+        print("------- Apache Isis diagnostics report -------");
+        printVersion();
+
+        printBlock("System properties");
+        final Properties properties = System.getProperties();
+        final Enumeration<?> propertyNames = properties.propertyNames();
+        while (propertyNames.hasMoreElements()) {
+            final String name = (String) propertyNames.nextElement();
+            final String property = properties.getProperty(name);
+            final StringBuilder buf = new StringBuilder();
+            if (name.endsWith(".path") || name.endsWith(".dirs")) {
+                final String[] split = property.split(":");
+                buf.append(split[0]);
+                for (int i = 1; i < split.length; i++) {
+                    buf.append("\n\t\t" + split[i]);
+                }
+            }
+            print(name + "= " + buf.toString());
+        }
+
+        File file = new File("../lib");
+        if (file.isDirectory()) {
+            final String[] files = file.list(new FilenameFilter() {
+                @Override
+                public boolean accept(final File dir, final String name) {
+                    return name.endsWith(".jar");
+                }
+            });
+            printBlock("Libs");
+            for (final String file2 : files) {
+                print(file2);
+            }
+        }
+
+        printBlock("Locale information");
+        print("Default locale: " + Locale.getDefault());
+        print("Default timezone: " + TimeZone.getDefault());
+
+        file = new File("config");
+        if (file.isDirectory()) {
+            final String[] files = file.list(new FilenameFilter() {
+                @Override
+                public boolean accept(final File dir, final String name) {
+                    return new File(dir, name).isFile();
+                }
+            });
+            printBlock("Config files");
+            for (final String file2 : files) {
+                print(file2);
+            }
+
+            for (final String file2 : files) {
+                print("");
+                print("--------------------------------------------------------------------------------------------------------");
+                print(file2);
+                print("");
+                LineNumberReader fileInputStream = null;
+                try {
+                    fileInputStream = new LineNumberReader(new FileReader(new File(file, file2)));
+                    String line;
+                    while ((line = fileInputStream.readLine()) != null) {
+                        print(fileInputStream.getLineNumber() + "  " + line);
+                    }
+                } catch (final Exception e) {
+                    throw new IsisException(e);
+                } finally {
+                    IoUtils.closeSafely(fileInputStream);
+                }
+                print("");
+            }
+
+        }
+    }
+
+    public void printVersion() {
+        final String date = AboutIsis.getFrameworkCompileDate();
+        final String compileDate = date == null ? "" : ", compiled on " + date;
+        print(AboutIsis.getFrameworkName() + ", " + AboutIsis.getFrameworkVersion() + compileDate);
+    }
+
+    public void printErrorMessage(final String message) {
+        output.println("Error: " + message);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/6de87443/framework/core/metamodel/src/main/java/org/apache/isis/core/runtime/userprofile/Options.java
----------------------------------------------------------------------
diff --git a/framework/core/metamodel/src/main/java/org/apache/isis/core/runtime/userprofile/Options.java b/framework/core/metamodel/src/main/java/org/apache/isis/core/runtime/userprofile/Options.java
new file mode 100644
index 0000000..a959889
--- /dev/null
+++ b/framework/core/metamodel/src/main/java/org/apache/isis/core/runtime/userprofile/Options.java
@@ -0,0 +1,113 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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 org.apache.isis.core.runtime.userprofile;
+
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.Properties;
+
+import org.apache.isis.core.commons.debug.DebugBuilder;
+import org.apache.isis.core.commons.debug.DebuggableWithTitle;
+
+public class Options implements DebuggableWithTitle {
+
+    private final Properties properties = new Properties();
+
+    public void addOption(final String name, final String value) {
+        properties.put(name, value);
+    }
+
+    public void addOptions(final String name, final Options options) {
+        properties.put(name, options);
+    }
+
+    public Iterator<String> names() {
+        final Enumeration<?> propertyNames = properties.propertyNames();
+        return new Iterator<String>() {
+            @Override
+            public boolean hasNext() {
+                return propertyNames.hasMoreElements();
+            }
+
+            @Override
+            public String next() {
+                return (String) propertyNames.nextElement();
+            }
+
+            @Override
+            public void remove() {
+                throw new UnsupportedOperationException();
+            }
+        };
+    }
+
+    public String getString(final String name) {
+        return properties.getProperty(name);
+    }
+
+    public String getString(final String name, final String defaultValue) {
+        return properties.getProperty(name, defaultValue);
+    }
+
+    public int getInteger(final String name, final int defaultValue) {
+        final String value = getString(name);
+        if (value == null) {
+            return defaultValue;
+        } else {
+            return Integer.valueOf(value).intValue();
+        }
+    }
+
+    public Options getOptions(final String name) {
+        Options options = (Options) properties.get(name);
+        if (options == null) {
+            options = new Options();
+            addOptions(name, options);
+        }
+        return options;
+    }
+
+    public boolean isOptions(final String name) {
+        return properties.get(name) instanceof Options;
+    }
+
+    public void copy(final Options options) {
+        properties.putAll(options.properties);
+    }
+
+    // ///////////////////////////////
+    // Debugging
+    // ///////////////////////////////
+
+    @Override
+    public String debugTitle() {
+        return "Options";
+    }
+
+    @Override
+    public void debugData(final DebugBuilder debug) {
+        final Enumeration<Object> keys = properties.keys();
+        while (keys.hasMoreElements()) {
+            final String name = (String) keys.nextElement();
+            debug.appendln(name, properties.get(name));
+        }
+    }
+
+}