Example #1
0
        // Start a snapshot at the root object, using supplied namespace manager.
        public XmlSnapshot(object obj, XmlSchema schema) {
            INakedObject rootObject = PersistorUtils.CreateAdapter(obj);
            Log.Debug(".ctor(" + DoLog("rootObj", rootObject) + AndLog("schema", schema) + AndLog("addOids", "" + true) + ")");

            Schema = schema;

            try {
                XmlDocument = new XDocument();
                XsdDocument = new XDocument();

                XsdElement = XsMetaModel.CreateXsSchemaElement(XsdDocument);

                rootPlace = AppendXml(rootObject);
            }
            catch (ArgumentException e) {
                Log.Error("unable to build snapshot", e);
                throw new NakedObjectSystemException(e);
            }
        }
        // Start a snapshot at the root object, using supplied namespace manager.
        public XmlSnapshot(object obj, XmlSchema schema, INakedObjectManager nakedObjectManager, IMetamodelManager metamodelManager) {
            this.nakedObjectManager = nakedObjectManager;
            this.metamodelManager = metamodelManager;

            INakedObjectAdapter rootObjectAdapter = nakedObjectManager.CreateAdapter(obj, null, null);

            Schema = schema;

            try {
                XmlDocument = new XDocument();
                XsdDocument = new XDocument();

                XsdElement = XsMetaModel.CreateXsSchemaElement(XsdDocument);

                rootPlace = AppendXml(rootObjectAdapter);
            }
            catch (ArgumentException e) {
                throw new NakedObjectSystemException(Log.LogAndReturn("Unable to build snapshot"), e);
            }
        }
        // Start a snapshot at the root object, using supplied namespace manager.
        public XmlSnapshot(object obj, XmlSchema schema, INakedObjectManager nakedObjectManager, IMetamodelManager metamodelManager) {
            this.nakedObjectManager = nakedObjectManager;
            this.metamodelManager = metamodelManager;

            INakedObjectAdapter rootObjectAdapter = nakedObjectManager.CreateAdapter(obj, null, null);
            Log.Debug(".ctor(" + DoLog("rootObj", rootObjectAdapter) + AndLog("schema", schema) + AndLog("addOids", "" + true) + ")");

            Schema = schema;

            try {
                XmlDocument = new XDocument();
                XsdDocument = new XDocument();

                XsdElement = XsMetaModel.CreateXsSchemaElement(XsdDocument);

                rootPlace = AppendXml(rootObjectAdapter);
            }
            catch (ArgumentException e) {
                Log.Error("unable to build snapshot", e);
                throw new NakedObjectSystemException(e);
            }
        }
        public Place ObjectToElement(INakedObjectAdapter nakedObjectAdapter) {
            Log.Debug("objectToElement(" + DoLog("object", nakedObjectAdapter) + ")");

            var nos = (IObjectSpec) nakedObjectAdapter.Spec;

            Log.Debug("objectToElement(NO): create element and nof:title");
            XElement element = Schema.CreateElement(XmlDocument, nos.ShortName, nos.FullName, nos.SingularName, nos.PluralName);
            NofMetaModel.AppendNofTitle(element, nakedObjectAdapter.TitleString());

            Log.Debug("objectToElement(NO): create XS element for NOF class");
            XElement xsElement = Schema.CreateXsElementForNofClass(XsdDocument, element, topLevelElementWritten);

            // hack: every element in the XSD schema apart from first needs minimum cardinality setting.
            topLevelElementWritten = true;

            var place = new Place(nakedObjectAdapter, element);

            NofMetaModel.SetAttributesForClass(element, OidOrHashCode(nakedObjectAdapter));

            IAssociationSpec[] fields = nos.Properties;
            Log.Debug("objectToElement(NO): processing fields");

            var seenFields = new List<string>();

            foreach (IAssociationSpec field in fields) {
                string fieldName = field.Id;

                Log.Debug("objectToElement(NO): " + DoLog("field", fieldName));

                // Skip field if we have seen the name already
                // This is a workaround for getLastActivity(). This method exists
                // in AbstractNakedObject, 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 (NakedObjectSpecification).
                //
                // Now, to re-expose the lastactivity field for .Net, a deriveLastActivity()
                // has been added to BusinessObject. This caused another field ofthe
                // same name, ultimately breaking the XSD.

                if (seenFields.Contains(fieldName)) {
                    Log.Debug("objectToElement(NO): " + DoLog("field", fieldName) + " SKIPPED");
                    continue;
                }
                seenFields.Add(fieldName);

                XNamespace ns = Schema.GetUri();

                var xmlFieldElement = new XElement(ns + fieldName);

                XElement xsdFieldElement;
                var oneToOneAssociation = field as IOneToOneAssociationSpec;
                var oneToManyAssociation = field as IOneToManyAssociationSpec;

                if (field.ReturnSpec.IsParseable && oneToOneAssociation != null) {
                    Log.Debug("objectToElement(NO): " + DoLog("field", fieldName) + " is value");

                    IObjectSpec fieldNos = field.ReturnSpec;
                    // skip fields of type XmlValue
                    if (fieldNos != null &&
                        fieldNos.FullName != null &&
                        fieldNos.FullName.EndsWith("XmlValue")) {
                        continue;
                    }

                    XElement xmlValueElement = xmlFieldElement; // more meaningful locally scoped name

                    try {
                        INakedObjectAdapter value = oneToOneAssociation.GetNakedObject(nakedObjectAdapter);

                        // a null value would be a programming error, but we protect
                        // against it anyway
                        if (value == null) {
                            continue;
                        }

                        ITypeSpec valueNos = value.Spec;

                        // XML
                        NofMetaModel.SetAttributesForValue(xmlValueElement, valueNos.ShortName);

                        bool notEmpty = (value.TitleString().Length > 0);
                        if (notEmpty) {
                            string valueStr = value.TitleString();
                            xmlValueElement.Add(new XText(valueStr));
                        }
                        else {
                            NofMetaModel.SetIsEmptyAttribute(xmlValueElement, true);
                        }
                    }
                    catch (Exception) {
                        Log.Warn("objectToElement(NO): " + DoLog("field", fieldName) + ": getField() threw exception - skipping XML generation");
                    }

                    // XSD
                    xsdFieldElement = Schema.CreateXsElementForNofValue(xsElement, xmlValueElement);
                }
                else if (oneToOneAssociation != null) {
                    Log.Debug("objectToElement(NO): " + DoLog("field", fieldName) + " is IOneToOneAssociation");

                    XElement xmlReferenceElement = xmlFieldElement; // more meaningful locally scoped name

                    try {
                        INakedObjectAdapter referencedNakedObjectAdapter = oneToOneAssociation.GetNakedObject(nakedObjectAdapter);
                        string fullyQualifiedClassName = field.ReturnSpec.FullName;

                        // XML
                        NofMetaModel.SetAttributesForReference(xmlReferenceElement, Schema.Prefix, fullyQualifiedClassName);

                        if (referencedNakedObjectAdapter != null) {
                            NofMetaModel.AppendNofTitle(xmlReferenceElement, referencedNakedObjectAdapter.TitleString());
                        }
                        else {
                            NofMetaModel.SetIsEmptyAttribute(xmlReferenceElement, true);
                        }
                    }
                    catch (Exception) {
                        Log.Warn("objectToElement(NO): " + DoLog("field", fieldName) + ": getAssociation() threw exception - skipping XML generation");
                    }

                    // XSD
                    xsdFieldElement = Schema.CreateXsElementForNofReference(xsElement, xmlReferenceElement, oneToOneAssociation.ReturnSpec.FullName);
                }
                else if (oneToManyAssociation != null) {
                    Log.Debug("objectToElement(NO): " + DoLog("field", fieldName) + " is IOneToManyAssociation");

                    XElement xmlCollectionElement = xmlFieldElement; // more meaningful locally scoped name

                    try {
                        INakedObjectAdapter collection = oneToManyAssociation.GetNakedObject(nakedObjectAdapter);
                        ITypeOfFacet facet = collection.GetTypeOfFacetFromSpec();

                        IObjectSpecImmutable referencedTypeNos = facet.GetValueSpec(collection, metamodelManager.Metamodel);
                        string fullyQualifiedClassName = referencedTypeNos.FullName;

                        // XML
                        NofMetaModel.SetNofCollection(xmlCollectionElement, Schema.Prefix, fullyQualifiedClassName, collection, nakedObjectManager);
                    }
                    catch (Exception) {
                        Log.Warn("objectToElement(NO): " + DoLog("field", fieldName) + ": get(obj) threw exception - skipping XML generation");
                    }

                    // XSD
                    xsdFieldElement = Schema.CreateXsElementForNofCollection(xsElement, xmlCollectionElement, oneToManyAssociation.ReturnSpec.FullName);
                }
                else {
                    Log.Info("objectToElement(NO): " + DoLog("field", fieldName) + " is unknown type; ignored");
                    continue;
                }

                if (xsdFieldElement != null) {
                    xmlFieldElement.AddAnnotation(xsdFieldElement);
                }

                // XML
                Log.Debug("objectToElement(NO): invoking mergeTree for field");
                MergeTree(element, xmlFieldElement);

                // XSD
                if (xsdFieldElement != null) {
                    Log.Debug("objectToElement(NO): adding XS element for field to schema");
                    Schema.AddFieldXsElement(xsElement, xsdFieldElement);
                }
            }

            return place;
        }
        //  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 bool IncludeField(Place place, IList<string> fieldNames, string annotation) {
            Log.DebugFormat("includeField(: {0})", DoLog("place", place) + AndLog("fieldNames", fieldNames) + AndLog("annotation", annotation));

            INakedObjectAdapter nakedObjectAdapter = place.NakedObjectAdapter;
            XElement xmlElement = place.XmlElement;

            // we use a copy of the path so that we can safely traverse collections
            // without side-effects
            fieldNames = fieldNames.ToList();

            // see if we have any fields to process
            if (!fieldNames.Any()) {
                return true;
            }

            // take the first field name from the list, and remove
            string fieldName = fieldNames.First();
            fieldNames.Remove(fieldName);

            Log.Debug("includeField(Pl, Vec, Str):" + DoLog("processing field", fieldName) + AndLog("left", "" + fieldNames.Count()));

            // locate the field in the object's class
            var nos = (IObjectSpec) nakedObjectAdapter.Spec;
            IAssociationSpec field = nos.Properties.SingleOrDefault(p => p.Id.ToLower() == fieldName);

            if (field == null) {
                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)
            Log.Debug("includeField(Pl, Vec, Str): locating corresponding XML element");
            XElement[] xmlFieldElements = ElementsUnder(xmlElement, field.Id).ToArray();
            int fieldCount = xmlFieldElements.Count();
            if (fieldCount != 1) {
                Log.Info("includeField(Pl, Vec, Str): could not locate " + DoLog("field", field.Id) + AndLog("xmlFieldElements.size", "" + fieldCount));
                return false;
            }
            XElement xmlFieldElement = xmlFieldElements.First();

            if (!fieldNames.Any() && annotation != null) {
                // nothing left in the path, so we will apply the annotation now
                NofMetaModel.SetAnnotationAttribute(xmlFieldElement, annotation);
            }

            var fieldPlace = new Place(nakedObjectAdapter, xmlFieldElement);

            if (field.ReturnSpec.IsParseable) {
                Log.Debug("includeField(Pl, Vec, Str): field is value; done");
                return false;
            }
            var oneToOneAssociation = field as IOneToOneAssociationSpec;
            if (oneToOneAssociation != null) {
                Log.Debug("includeField(Pl, Vec, Str): field is 1->1");

                INakedObjectAdapter referencedObjectAdapter = oneToOneAssociation.GetNakedObject(fieldPlace.NakedObjectAdapter);

                if (referencedObjectAdapter == null) {
                    return true; // not a failure if the reference was null
                }

                bool appendedXml = AppendXmlThenIncludeRemaining(fieldPlace, referencedObjectAdapter, fieldNames, annotation);

                Log.Debug("includeField(Pl, Vec, Str): 1->1: invoked appendXmlThenIncludeRemaining for " + DoLog("referencedObj", referencedObjectAdapter) + AndLog("returned", "" + appendedXml));

                return appendedXml;
            }
            var oneToManyAssociation = field as IOneToManyAssociationSpec;
            if (oneToManyAssociation != null) {
                Log.Debug("includeField(Pl, Vec, Str): field is 1->M");

                INakedObjectAdapter collection = oneToManyAssociation.GetNakedObject(fieldPlace.NakedObjectAdapter);

                INakedObjectAdapter[] collectionAsEnumerable = collection.GetAsEnumerable(nakedObjectManager).ToArray();

                Log.Debug("includeField(Pl, Vec, Str): 1->M: " + DoLog("collection.size", "" + collectionAsEnumerable.Count()));
                bool allFieldsNavigated = true;

                foreach (INakedObjectAdapter referencedObject in collectionAsEnumerable) {
                    bool appendedXml = AppendXmlThenIncludeRemaining(fieldPlace, referencedObject, fieldNames, annotation);

                    Log.Debug("includeField(Pl, Vec, Str): 1->M: + invoked appendXmlThenIncludeRemaining for " + DoLog("referencedObj", referencedObject) + AndLog("returned", "" + appendedXml));

                    allFieldsNavigated = allFieldsNavigated && appendedXml;
                }
                Log.Debug("includeField(Pl, Vec, Str): " + DoLog("returning", "" + allFieldsNavigated));

                return allFieldsNavigated;
            }

            return false; // fall through, shouldn't get here but just in case.
        }
        private bool AppendXmlThenIncludeRemaining(Place parentPlace, INakedObjectAdapter referencedObjectAdapter, IList<string> fieldNames,
                                                   string annotation) {
            Log.Debug("appendXmlThenIncludeRemaining(: " + DoLog("parentPlace", parentPlace)
                      + AndLog("referencedObj", referencedObjectAdapter) + AndLog("fieldNames", fieldNames) + AndLog("annotation", annotation)
                      + ")");

            Log.Debug("appendXmlThenIncludeRemaining(..): invoking appendXml(parentPlace, referencedObjectAdapter)");

            XElement referencedElement = AppendXml(parentPlace, referencedObjectAdapter);
            var referencedPlace = new Place(referencedObjectAdapter, referencedElement);

            bool includedField = IncludeField(referencedPlace, fieldNames, annotation);

            Log.Debug("appendXmlThenIncludeRemaining(..): invoked includeField(referencedPlace, fieldNames)"
                      + AndLog("returned", "" + includedField));

            return includedField;
        }
        // Creates an XElement 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
        // ToXml() or XmlSnapshot).

        private XElement AppendXml(Place parentPlace, INakedObjectAdapter childObjectAdapter) {
            Log.Debug("appendXml(" + DoLog("parentPlace", parentPlace) + AndLog("childObj", childObjectAdapter) + ")");

            XElement parentElement = parentPlace.XmlElement;
            var parentXsElement = parentElement.Annotation<XElement>();

            if (parentElement.Document != XmlDocument) {
                throw new ArgumentException("parent XML XElement must have snapshot's XML document as its owner");
            }

            Log.Debug("appendXml(Pl, NO): invoking objectToElement() for " + DoLog("childObj", childObjectAdapter));
            Place childPlace = ObjectToElement(childObjectAdapter);
            XElement childElement = childPlace.XmlElement;
            var childXsElement = childElement.Annotation<XElement>();

            Log.Debug("appendXml(Pl, NO): invoking mergeTree of parent with child");
            childElement = MergeTree(parentElement, childElement);

            Log.Debug("appendXml(Pl, NO): adding XS XElement to schema if required");
            Schema.AddXsElementIfNotPresent(parentXsElement, childXsElement);

            return childElement;
        }
        //  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 bool IncludeField(Place place, IList<string> fieldNames, string annotation) {

            INakedObjectAdapter nakedObjectAdapter = place.NakedObjectAdapter;
            XElement xmlElement = place.XmlElement;

            // we use a copy of the path so that we can safely traverse collections
            // without side-effects
            fieldNames = fieldNames.ToList();

            // see if we have any fields to process
            if (!fieldNames.Any()) {
                return true;
            }

            // take the first field name from the list, and remove
            string fieldName = fieldNames.First();
            fieldNames.Remove(fieldName);


            // locate the field in the object's class
            var nos = (IObjectSpec) nakedObjectAdapter.Spec;
            IAssociationSpec field = nos.Properties.SingleOrDefault(p => p.Id.ToLower() == fieldName);

            if (field == null) {
                return false;
            }

            // locate the corresponding XML element
            // (the corresponding XSD element will later be attached to xmlElement
            // as its userData)
            XElement[] xmlFieldElements = ElementsUnder(xmlElement, field.Id).ToArray();
            int fieldCount = xmlFieldElements.Length;
            if (fieldCount != 1) {
                return false;
            }
            XElement xmlFieldElement = xmlFieldElements.First();

            if (!fieldNames.Any() && annotation != null) {
                // nothing left in the path, so we will apply the annotation now
                NofMetaModel.SetAnnotationAttribute(xmlFieldElement, annotation);
            }

            var fieldPlace = new Place(nakedObjectAdapter, xmlFieldElement);

            if (field.ReturnSpec.IsParseable) {
                return false;
            }
            var oneToOneAssociation = field as IOneToOneAssociationSpec;
            if (oneToOneAssociation != null) {

                INakedObjectAdapter referencedObjectAdapter = oneToOneAssociation.GetNakedObject(fieldPlace.NakedObjectAdapter);

                if (referencedObjectAdapter == null) {
                    return true; // not a failure if the reference was null
                }

                bool appendedXml = AppendXmlThenIncludeRemaining(fieldPlace, referencedObjectAdapter, fieldNames, annotation);


                return appendedXml;
            }
            var oneToManyAssociation = field as IOneToManyAssociationSpec;
            if (oneToManyAssociation != null) {

                INakedObjectAdapter collection = oneToManyAssociation.GetNakedObject(fieldPlace.NakedObjectAdapter);

                INakedObjectAdapter[] collectionAsEnumerable = collection.GetAsEnumerable(nakedObjectManager).ToArray();

                bool allFieldsNavigated = true;

                foreach (INakedObjectAdapter referencedObject in collectionAsEnumerable) {
                    bool appendedXml = AppendXmlThenIncludeRemaining(fieldPlace, referencedObject, fieldNames, annotation);


                    allFieldsNavigated = allFieldsNavigated && appendedXml;
                }

                return allFieldsNavigated;
            }

            return false; // fall through, shouldn't get here but just in case.
        }
        private bool AppendXmlThenIncludeRemaining(Place parentPlace, INakedObjectAdapter referencedObjectAdapter, IList<string> fieldNames,
                                                   string annotation) {
            


            XElement referencedElement = AppendXml(parentPlace, referencedObjectAdapter);
            var referencedPlace = new Place(referencedObjectAdapter, referencedElement);

            bool includedField = IncludeField(referencedPlace, fieldNames, annotation);

           

            return includedField;
        }
        // Creates an XElement 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
        // ToXml() or XmlSnapshot).

        private XElement AppendXml(Place parentPlace, INakedObjectAdapter childObjectAdapter) {

            XElement parentElement = parentPlace.XmlElement;
            var parentXsElement = parentElement.Annotation<XElement>();

            if (parentElement.Document != XmlDocument) {
                throw new ArgumentException(Log.LogAndReturn("parent XML XElement must have snapshot's XML document as its owner"));
            }

            Place childPlace = ObjectToElement(childObjectAdapter);
            XElement childElement = childPlace.XmlElement;
            var childXsElement = childElement.Annotation<XElement>();

            childElement = MergeTree(parentElement, childElement);

            Schema.AddXsElementIfNotPresent(parentXsElement, childXsElement);

            return childElement;
        }
        //  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 bool IncludeField(Place place, IList<string> fieldNames, string annotation)
        {
            LOG.DebugFormat("includeField(: {0})", Log("place", place) + AndLog("fieldNames", fieldNames) + AndLog("annotation", annotation));

            INakedObject nakedObject = place.NakedObject;
            XElement xmlElement = place.XmlElement;

            // we use a copy of the path so that we can safely traverse collections
            // without side-effects
            fieldNames = fieldNames.ToList();

            // see if we have any fields to process
            if (!fieldNames.Any()) {
                return true;
            }

            // take the first field name from the list, and remove
            string fieldName = fieldNames.First();
            fieldNames.Remove(fieldName);

            LOG.Debug("includeField(Pl, Vec, Str):" + Log("processing field", fieldName) + AndLog("left", "" + fieldNames.Count()));

            // locate the field in the object's class
            INakedObjectSpecification nos = nakedObject.Specification;
            INakedObjectAssociation field = nos.Properties.Where(p => p.Id.ToLower() == fieldName).SingleOrDefault();

            if (field == null) {
                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)
            LOG.Debug("includeField(Pl, Vec, Str): locating corresponding XML element");
            IEnumerable<XElement> xmlFieldElements = ElementsUnder(xmlElement, field.Id);
            if (xmlFieldElements.Count() != 1) {
                LOG.Info("includeField(Pl, Vec, Str): could not locate " + Log("field", field.Id) + AndLog("xmlFieldElements.size", "" + xmlFieldElements.Count()));
                return false;
            }
            XElement xmlFieldElement = xmlFieldElements.First();

            if (!fieldNames.Any() && annotation != null) {
                // nothing left in the path, so we will apply the annotation now
                NofMetaModel.SetAnnotationAttribute(xmlFieldElement, annotation);
            }

            var fieldPlace = new Place(nakedObject, xmlFieldElement);

            if (field.Specification.IsParseable) {
                LOG.Debug("includeField(Pl, Vec, Str): field is value; done");
                return false;
            }
            if (field is IOneToOneAssociation) {
                LOG.Debug("includeField(Pl, Vec, Str): field is 1->1");

                var oneToOneAssociation = ((IOneToOneAssociation) field);
                INakedObject referencedObject = oneToOneAssociation.GetNakedObject(fieldPlace.NakedObject);

                if (referencedObject == null) {
                    return true; // not a failure if the reference was null
                }

                bool appendedXml = AppendXmlThenIncludeRemaining(fieldPlace, referencedObject, fieldNames, annotation);

                LOG.Debug("includeField(Pl, Vec, Str): 1->1: invoked appendXmlThenIncludeRemaining for " + Log("referencedObj", referencedObject) + AndLog("returned", "" + appendedXml));

                return appendedXml;
            }
            if (field is IOneToManyAssociation) {
                LOG.Debug("includeField(Pl, Vec, Str): field is 1->M");

                var oneToManyAssociation = (IOneToManyAssociation) field;
                var collection = oneToManyAssociation.GetNakedObject(fieldPlace.NakedObject);
                var facet = collection.GetCollectionFacetFromSpec();

                LOG.Debug("includeField(Pl, Vec, Str): 1->M: " /*+ Log("collection.size", "" + facet.Size(collection))*/);
                var allFieldsNavigated = true;

                for (IEnumerator<INakedObject> enumer = facet.AsEnumerable(collection).GetEnumerator(); enumer.MoveNext();) {
                    var referencedObject = enumer.Current;

                    var appendedXml = AppendXmlThenIncludeRemaining(fieldPlace, referencedObject, fieldNames, annotation);

                    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.
        }
        public Place ObjectToElement(INakedObjectAdapter nakedObjectAdapter)
        {
            var nos = (IObjectSpec)nakedObjectAdapter.Spec;

            var element = Schema.CreateElement(XmlDocument, nos.ShortName, nos.FullName, nos.SingularName, nos.PluralName);

            NofMetaModel.AppendNofTitle(element, nakedObjectAdapter.TitleString());

            var xsElement = Schema.CreateXsElementForNofClass(XsdDocument, element, topLevelElementWritten);

            // hack: every element in the XSD schema apart from first needs minimum cardinality setting.
            topLevelElementWritten = true;

            var place = new Place(nakedObjectAdapter, element);

            NofMetaModel.SetAttributesForClass(element, OidOrHashCode(nakedObjectAdapter));

            var fields = nos.Properties;

            var seenFields = new List <string>();

            foreach (var field in fields)
            {
                var fieldName = field.Id;

                // Skip field if we have seen the name already
                // This is a workaround for getLastActivity(). This method exists
                // in AbstractNakedObject, 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 (NakedObjectSpecification).
                //
                // 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.

                if (seenFields.Contains(fieldName))
                {
                    continue;
                }

                seenFields.Add(fieldName);

                XNamespace ns = Schema.GetUri();

                var xmlFieldElement = new XElement(ns + fieldName);

                XElement xsdFieldElement;
                var      oneToOneAssociation  = field as IOneToOneAssociationSpec;
                var      oneToManyAssociation = field as IOneToManyAssociationSpec;

                if (field.ReturnSpec.IsParseable && oneToOneAssociation != null)
                {
                    var fieldNos = field.ReturnSpec;
                    // skip fields of type XmlValue
                    if (fieldNos?.FullName != null && fieldNos.FullName.EndsWith("XmlValue"))
                    {
                        continue;
                    }

                    var xmlValueElement = xmlFieldElement; // more meaningful locally scoped name

                    try {
                        var value = oneToOneAssociation.GetNakedObject(nakedObjectAdapter);

                        // a null value would be a programming error, but we protect
                        // against it anyway
                        if (value == null)
                        {
                            continue;
                        }

                        var valueNos = value.Spec;

                        // XML
                        NofMetaModel.SetAttributesForValue(xmlValueElement, valueNos.ShortName);

                        var notEmpty = value.TitleString().Length > 0;
                        if (notEmpty)
                        {
                            var valueStr = value.TitleString();
                            xmlValueElement.Add(new XText(valueStr));
                        }
                        else
                        {
                            NofMetaModel.SetIsEmptyAttribute(xmlValueElement, true);
                        }
                    }
                    catch (Exception) {
                        logger.LogWarning($"objectToElement(NO): {DoLog("field", fieldName)}: getField() threw exception - skipping XML generation");
                    }

                    // XSD
                    xsdFieldElement = Schema.CreateXsElementForNofValue(xsElement, xmlValueElement);
                }
                else if (oneToOneAssociation != null)
                {
                    var xmlReferenceElement = xmlFieldElement; // more meaningful locally scoped name

                    try {
                        var referencedNakedObjectAdapter = oneToOneAssociation.GetNakedObject(nakedObjectAdapter);
                        var fullyQualifiedClassName      = field.ReturnSpec.FullName;

                        // XML
                        NofMetaModel.SetAttributesForReference(xmlReferenceElement, Schema.Prefix, fullyQualifiedClassName);

                        if (referencedNakedObjectAdapter != null)
                        {
                            NofMetaModel.AppendNofTitle(xmlReferenceElement, referencedNakedObjectAdapter.TitleString());
                        }
                        else
                        {
                            NofMetaModel.SetIsEmptyAttribute(xmlReferenceElement, true);
                        }
                    }
                    catch (Exception) {
                        logger.LogWarning($"objectToElement(NO): {DoLog("field", fieldName)}: getAssociation() threw exception - skipping XML generation");
                    }

                    // XSD
                    xsdFieldElement = Schema.CreateXsElementForNofReference(xsElement, xmlReferenceElement, oneToOneAssociation.ReturnSpec.FullName);
                }
                else if (oneToManyAssociation != null)
                {
                    var xmlCollectionElement = xmlFieldElement; // more meaningful locally scoped name

                    try {
                        var collection = oneToManyAssociation.GetNakedObject(nakedObjectAdapter);
                        var facet      = collection.GetTypeOfFacetFromSpec();

                        var referencedTypeNos       = facet.GetValueSpec(collection, metamodelManager.Metamodel);
                        var fullyQualifiedClassName = referencedTypeNos.FullName;

                        // XML
                        NofMetaModel.SetNofCollection(xmlCollectionElement, Schema.Prefix, fullyQualifiedClassName, collection, nakedObjectManager);
                    }
                    catch (Exception) {
                        logger.LogWarning($"objectToElement(NO): {DoLog("field", fieldName)}: get(obj) threw exception - skipping XML generation");
                    }

                    // XSD
                    xsdFieldElement = Schema.CreateXsElementForNofCollection(xsElement, xmlCollectionElement, oneToManyAssociation.ReturnSpec.FullName);
                }
                else
                {
                    continue;
                }

                if (xsdFieldElement != null)
                {
                    xmlFieldElement.AddAnnotation(xsdFieldElement);
                }

                // XML
                MergeTree(element, xmlFieldElement);

                // XSD
                if (xsdFieldElement != null)
                {
                    Schema.AddFieldXsElement(xsElement, xsdFieldElement);
                }
            }

            return(place);
        }
        //  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 bool IncludeField(Place place, IList <string> fieldNames, string annotation)
        {
            var nakedObjectAdapter = place.NakedObjectAdapter;
            var xmlElement         = place.XmlElement;

            // we use a copy of the path so that we can safely traverse collections
            // without side-effects
            fieldNames = fieldNames.ToList();

            // see if we have any fields to process
            if (!fieldNames.Any())
            {
                return(true);
            }

            // take the first field name from the list, and remove
            var fieldName = fieldNames.First();

            fieldNames.Remove(fieldName);

            // locate the field in the object's class
            var nos   = (IObjectSpec)nakedObjectAdapter.Spec;
            var field = nos.Properties.SingleOrDefault(p => p.Id.ToLower() == fieldName);

            if (field == null)
            {
                return(false);
            }

            // locate the corresponding XML element
            // (the corresponding XSD element will later be attached to xmlElement
            // as its userData)
            var xmlFieldElements = ElementsUnder(xmlElement, field.Id).ToArray();
            var fieldCount       = xmlFieldElements.Length;

            if (fieldCount != 1)
            {
                return(false);
            }

            var xmlFieldElement = xmlFieldElements.First();

            if (!fieldNames.Any() && annotation != null)
            {
                // nothing left in the path, so we will apply the annotation now
                NofMetaModel.SetAnnotationAttribute(xmlFieldElement, annotation);
            }

            var fieldPlace = new Place(nakedObjectAdapter, xmlFieldElement);

            if (field.ReturnSpec.IsParseable)
            {
                return(false);
            }

            var oneToOneAssociation = field as IOneToOneAssociationSpec;

            if (oneToOneAssociation != null)
            {
                var referencedObjectAdapter = oneToOneAssociation.GetNakedObject(fieldPlace.NakedObjectAdapter);

                if (referencedObjectAdapter == null)
                {
                    return(true); // not a failure if the reference was null
                }

                var appendedXml = AppendXmlThenIncludeRemaining(fieldPlace, referencedObjectAdapter, fieldNames, annotation);

                return(appendedXml);
            }

            var oneToManyAssociation = field as IOneToManyAssociationSpec;

            if (oneToManyAssociation != null)
            {
                var collection = oneToManyAssociation.GetNakedObject(fieldPlace.NakedObjectAdapter);

                var collectionAsEnumerable = collection.GetAsEnumerable(nakedObjectManager).ToArray();

                var allFieldsNavigated = true;

                foreach (var referencedObject in collectionAsEnumerable)
                {
                    var appendedXml = AppendXmlThenIncludeRemaining(fieldPlace, referencedObject, fieldNames, annotation);

                    allFieldsNavigated = allFieldsNavigated && appendedXml;
                }

                return(allFieldsNavigated);
            }

            return(false); // fall through, shouldn't get here but just in case.
        }