You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@isis.apache.org by ah...@apache.org on 2018/10/05 09:59:40 UTC

[isis] branch v2 updated: ISIS-1976: adds a meta-model (XML) export to the prototyping menu

This is an automated email from the ASF dual-hosted git repository.

ahuber pushed a commit to branch v2
in repository https://gitbox.apache.org/repos/asf/isis.git


The following commit(s) were added to refs/heads/v2 by this push:
     new 9698a40  ISIS-1976: adds a meta-model (XML) export to the prototyping menu
9698a40 is described below

commit 9698a405ea18d71c88ee9641baa3040c76e8c1e5
Author: Andi Huber <ah...@apache.org>
AuthorDate: Fri Oct 5 11:59:16 2018 +0200

    ISIS-1976: adds a meta-model (XML) export to the prototyping menu
    
    Task-Url: https://issues.apache.org/jira/browse/ISIS-1976
---
 .../applib/services/metamodel/DomainModel.java     |  27 +++
 .../services/metamodel/MetaModelService.java       |   4 +-
 .../services/metamodel/MetaModelServicesMenu.java  |  54 ++++-
 .../services/metamodel/DomainMemberDefault.java    | 106 ++++++---
 .../services/metamodel/DomainModelDefault.java     |  58 +++++
 .../metamodel/MetaModelServiceDefault.java         |   8 +-
 .../metamodel/MetaModelServiceDefaultTest.java     | 254 +++++++++++++++++++++
 .../java/domainapp/application/menubars.layout.xml |   5 +-
 8 files changed, 471 insertions(+), 45 deletions(-)

diff --git a/core/applib/src/main/java/org/apache/isis/applib/services/metamodel/DomainModel.java b/core/applib/src/main/java/org/apache/isis/applib/services/metamodel/DomainModel.java
new file mode 100644
index 0000000..5861459
--- /dev/null
+++ b/core/applib/src/main/java/org/apache/isis/applib/services/metamodel/DomainModel.java
@@ -0,0 +1,27 @@
+/*
+ *  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.applib.services.metamodel;
+
+import java.util.List;
+
+public interface DomainModel {
+    
+    public List<DomainMember> getDomainMembers();
+    
+}
diff --git a/core/applib/src/main/java/org/apache/isis/applib/services/metamodel/MetaModelService.java b/core/applib/src/main/java/org/apache/isis/applib/services/metamodel/MetaModelService.java
index 5c7494a..5550093 100644
--- a/core/applib/src/main/java/org/apache/isis/applib/services/metamodel/MetaModelService.java
+++ b/core/applib/src/main/java/org/apache/isis/applib/services/metamodel/MetaModelService.java
@@ -18,8 +18,6 @@
  */
 package org.apache.isis.applib.services.metamodel;
 
-import java.util.List;
-
 import org.apache.isis.applib.AppManifest;
 import org.apache.isis.applib.AppManifest2;
 import org.apache.isis.applib.annotation.DomainObject;
@@ -60,7 +58,7 @@ public interface MetaModelService {
      * </p>
      */
     @Programmatic
-    List<DomainMember> export();
+    DomainModel getDomainModel();
 
     @Programmatic
     Sort sortOf(Class<?> domainType, Mode mode);
diff --git a/core/applib/src/main/java/org/apache/isis/applib/services/metamodel/MetaModelServicesMenu.java b/core/applib/src/main/java/org/apache/isis/applib/services/metamodel/MetaModelServicesMenu.java
index 2f41686..5112727 100644
--- a/core/applib/src/main/java/org/apache/isis/applib/services/metamodel/MetaModelServicesMenu.java
+++ b/core/applib/src/main/java/org/apache/isis/applib/services/metamodel/MetaModelServicesMenu.java
@@ -24,6 +24,7 @@ import java.util.stream.Stream;
 
 import javax.activation.MimeType;
 import javax.activation.MimeTypeParseException;
+import javax.inject.Inject;
 
 import org.apache.isis.applib.IsisApplibModule;
 import org.apache.isis.applib.annotation.Action;
@@ -35,7 +36,9 @@ import org.apache.isis.applib.annotation.NatureOfService;
 import org.apache.isis.applib.annotation.ParameterLayout;
 import org.apache.isis.applib.annotation.RestrictTo;
 import org.apache.isis.applib.annotation.SemanticsOf;
+import org.apache.isis.applib.services.jaxb.JaxbService;
 import org.apache.isis.applib.value.Clob;
+import org.apache.isis.commons.internal.base._Strings;
 import org.apache.isis.commons.internal.collections._Lists;
 
 @DomainService(
@@ -72,15 +75,40 @@ public class MetaModelServicesMenu {
             )
     @ActionLayout(
             cssClassFa = "fa-download",
-            named = "Download Meta Model (CSV)"
+            named = "Download Meta Model (XML)"
             )
     @MemberOrder(sequence="500.500.1")
-    public Clob downloadMetaModel(
+    public Clob downloadMetaModelXml(
+            @ParameterLayout(named = ".xml file name")
+            final String fileName) {
+
+        final DomainModel domainMembers =  metaModelService.getDomainModel();
+        final String xml = toXml(domainMembers);
+        return new Clob(_Strings.asFileNameWithExtension(fileName,  ".xml"), "text/xml", xml);
+    }
+
+    public String default0DownloadMetaModelXml() {
+        return "metamodel.xml";
+    }
+    
+    // //////////////////////////////////////
+    
+    @Action(
+            domainEvent = DownloadMetaModelEvent.class,
+            semantics = SemanticsOf.SAFE,
+            restrictTo = RestrictTo.PROTOTYPING
+            )
+    @ActionLayout(
+            cssClassFa = "fa-download",
+            named = "Download Meta Model (CSV)"
+            )
+    @MemberOrder(sequence="500.500.2")
+    public Clob downloadMetaModelCsv(
             @ParameterLayout(named = ".csv file name")
             final String csvFileName) {
 
-        final List<DomainMember> rows =  metaModelService.export();
-        final List<String> list = asList(rows);
+        final DomainModel domainMembers =  metaModelService.getDomainModel();
+        final List<String> list = asList(domainMembers);
         final StringBuilder buf = asBuf(list);
 
         return new Clob(
@@ -88,11 +116,19 @@ public class MetaModelServicesMenu {
                 mimeTypeTextCsv, buf.toString().toCharArray());
     }
 
-    public String default0DownloadMetaModel() {
+    public String default0DownloadMetaModelCsv() {
         return "metamodel.csv";
     }
 
-    // //////////////////////////////////////
+    // -- XML HELPER
+    
+    @Inject JaxbService jaxbService;
+    
+    private String toXml(DomainModel model) {
+        return jaxbService.toXml(model);    
+    }
+    
+    // -- CSV HELPER
 
     private static StringBuilder asBuf(final List<String> list) {
         final StringBuilder buf = new StringBuilder();
@@ -102,17 +138,15 @@ public class MetaModelServicesMenu {
         return buf;
     }
 
-    private static List<String> asList(final List<DomainMember> rows) {
+    private static List<String> asList(final DomainModel model) {
         final List<String> list = _Lists.newArrayList();
         list.add(header());
-        for (final DomainMember row : rows) {
+        for (final DomainMember row : model.getDomainMembers()) {
             list.add(asTextCsv(row));
         }
         return list;
     }
 
-
-
     private static String header() {
         return "classType,packageName,className,memberType,memberName,numParams,contributed?,contributedBy,mixedIn?,mixin,hidden,disabled,choices,autoComplete,default,validate";
     }
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/metamodel/DomainMemberDefault.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/metamodel/DomainMemberDefault.java
index 180cad9..e05b9e2 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/metamodel/DomainMemberDefault.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/metamodel/DomainMemberDefault.java
@@ -24,10 +24,15 @@ import java.util.List;
 import java.util.SortedSet;
 import java.util.stream.Collectors;
 
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlRootElement;
+
 import org.apache.isis.applib.services.metamodel.DomainMember;
 import org.apache.isis.commons.internal.base._Strings;
 import org.apache.isis.commons.internal.collections._Sets;
-import org.apache.isis.core.commons.lang.StringExtensions;
+import org.apache.isis.commons.internal.exceptions._Exceptions;
 import org.apache.isis.core.metamodel.facetapi.Facet;
 import org.apache.isis.core.metamodel.facets.ImperativeFacet;
 import org.apache.isis.core.metamodel.facets.actions.defaults.ActionDefaultsFacet;
@@ -55,6 +60,8 @@ import org.apache.isis.core.metamodel.spec.feature.OneToOneAssociation;
 import org.apache.isis.core.metamodel.specloader.specimpl.ContributeeMember;
 import org.apache.isis.core.metamodel.specloader.specimpl.MixedInMember;
 
+@XmlRootElement(name = "domain-member")
+@XmlAccessorType(XmlAccessType.PROPERTY)
 public class DomainMemberDefault implements DomainMember {
 
     private final ObjectSpecification spec;
@@ -62,6 +69,11 @@ public class DomainMemberDefault implements DomainMember {
     private final ObjectMember member;
     private ObjectAction action;
 
+    // to support JAX-B marshaling 
+    DomainMemberDefault(){
+        throw _Exceptions.unexpectedCodeReach();
+    }
+    
     DomainMemberDefault(ObjectSpecification spec, OneToOneAssociation property) {
         this.spec = spec;
         this.member = property;
@@ -80,40 +92,53 @@ public class DomainMemberDefault implements DomainMember {
         this.memberType = MemberType.ACTION;
     }
 
-    @Override public String getClassType() {
+    @XmlElement @Override
+    public String getClassType() {
         boolean service = false;
         for(ObjectSpecification subspecs: spec.subclasses(Hierarchical.Depth.DIRECT)) {
             service = service || subspecs.isService();
         }
         return service || spec.isService() ?"2 Service":spec.isValue()?"3 Value":spec.isParentedOrFreeCollection()?"4 Collection":"1 Object";
     }
-    @Override public String getClassName() {
+    
+    @XmlElement @Override
+    public String getClassName() {
         final String fullIdentifier = spec.getFullIdentifier();
         final int lastDot = fullIdentifier.lastIndexOf(".");
         return lastDot>0 && lastDot < fullIdentifier.length()-1
                 ?fullIdentifier.substring(lastDot+1,fullIdentifier.length())
                         :fullIdentifier;
     }
-    @Override public String getPackageName() {
+    
+    @XmlElement @Override
+    public String getPackageName() {
         final String fullIdentifier = spec.getFullIdentifier();
         final int lastDot = fullIdentifier.lastIndexOf(".");
         return lastDot>0?fullIdentifier.substring(0,lastDot):fullIdentifier;
     }
-    @Override public String getType() {
+    
+    @XmlElement @Override
+    public String getType() {
         return memberType.name().toLowerCase();
     }
-    @Override public String getMemberName() {
+    
+    @XmlElement @Override
+    public String getMemberName() {
         return member.getId();
     }
-    @Override public String getNumParams() {
+    
+    @XmlElement @Override
+    public String getNumParams() {
         return action!=null?""+action.getParameterCount():"";
     }
 
-    @Override public boolean isContributed() {
+    @XmlElement @Override
+    public boolean isContributed() {
         return member instanceof ContributeeMember;
     }
 
-    @Override public String getContributedBy() {
+    @XmlElement @Override
+    public String getContributedBy() {
         if(member instanceof ContributeeMember) {
             final ObjectSpecification serviceContributedBy = ((ContributeeMember) member).getServiceContributedBy();
             return serviceContributedBy.getCorrespondingClass().getSimpleName();
@@ -121,11 +146,13 @@ public class DomainMemberDefault implements DomainMember {
         return "";
     }
 
-    @Override public boolean isMixedIn() {
+    @XmlElement @Override
+    public boolean isMixedIn() {
         return member instanceof MixedInMember;
     }
 
-    @Override public String getMixin() {
+    @XmlElement @Override
+    public String getMixin() {
         if(member instanceof MixedInMember) {
             final MixedInMember mixedInMember = (MixedInMember) this.member;
 
@@ -135,13 +162,18 @@ public class DomainMemberDefault implements DomainMember {
         return "";
     }
 
-    @Override public String getHidden() {
+    @XmlElement @Override
+    public String getHidden() {
         return interpret(HiddenFacet.class);
     }
-    @Override public String getDisabled() {
+    
+    @XmlElement @Override
+    public String getDisabled() {
         return interpret(DisabledFacet.class);
     }
-    @Override public String getChoices() {
+    
+    @XmlElement @Override
+    public String getChoices() {
         switch (memberType) {
         case PROPERTY:
             return interpretRowAndFacet(PropertyChoicesFacet.class);
@@ -159,7 +191,9 @@ public class DomainMemberDefault implements DomainMember {
                         interpretRowAndFacet(ActionChoicesFacet.class);
         }
     }
-    @Override public String getAutoComplete() {
+    
+    @XmlElement @Override
+    public String getAutoComplete() {
         if(memberType == MemberType.PROPERTY) {
             return interpretRowAndFacet(PropertyAutoCompleteFacet.class);
         } else if(memberType == MemberType.COLLECTION) {
@@ -174,7 +208,9 @@ public class DomainMemberDefault implements DomainMember {
             return interpretations.stream().collect(Collectors.joining(";"));
         }
     }
-    @Override public String getDefault() {
+    
+    @XmlElement @Override
+    public String getDefault() {
         if(memberType == MemberType.PROPERTY) {
             return interpretRowAndFacet(PropertyDefaultFacet.class);
         } else if(memberType == MemberType.COLLECTION) {
@@ -191,7 +227,9 @@ public class DomainMemberDefault implements DomainMember {
                             : interpretRowAndFacet(ActionDefaultsFacet.class);
         }
     }
-    @Override public String getValidate() {
+    
+    @XmlElement @Override
+    public String getValidate() {
         if(memberType == MemberType.PROPERTY) {
             return interpretRowAndFacet(PropertyValidateFacet.class);
         } else if(memberType == MemberType.COLLECTION) {
@@ -204,6 +242,14 @@ public class DomainMemberDefault implements DomainMember {
         }
     }
 
+    // -- COMPARATOR
+    
+    @Override
+    public int compareTo(DomainMember o) {
+        return comparator.compare(this, o);
+    }
+    
+    // -- HELPER
 
     private String interpretRowAndFacet(Class<? extends Facet> facetClass) {
         final Facet facet = member.getFacet(facetClass);
@@ -232,21 +278,23 @@ public class DomainMemberDefault implements DomainMember {
         if (ignore(name)) {
             return "";
         }
-        final String abbr = StringExtensions.toAbbreviation(name);
-        return abbr.length()>0 ? abbr : name;
+//[ahuber] not sure why abbreviated, so I disabled abbreviation        
+//        final String abbr = StringExtensions.toAbbreviation(name);
+//        return abbr.length()>0 ? abbr : name;
+        
+        return name;
     }
 
     protected static boolean ignore(final String name) {
-        return Arrays.asList("PropertyValidateFacetDefault","PropertyDefaultFacetDerivedFromDefaultedFacet").contains(name);
+        return Arrays.asList("PropertyValidateFacetDefault","PropertyDefaultFacetDerivedFromDefaultedFacet")
+                .contains(name);
     }
+    
+    private final static Comparator<DomainMember> comparator = 
+            Comparator.comparing(DomainMember::getClassType)
+            .thenComparing(DomainMember::getClassName)
+            .thenComparing(DomainMember::getType, Comparator.reverseOrder()) // desc
+            .thenComparing(DomainMember::getMemberName);
 
-    @Override
-    public int compareTo(DomainMember o) {
-        // legacy of ObjectContracts.compare(this, o, "classType,className,type desc,memberName");
-        return Comparator.comparing(DomainMember::getClassType)
-                .thenComparing(DomainMember::getClassName)
-                .thenComparing(DomainMember::getType, Comparator.reverseOrder()) // desc
-                .thenComparing(DomainMember::getMemberName)
-                .compare(this, o);
-    }
+    
 }
\ No newline at end of file
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/metamodel/DomainModelDefault.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/metamodel/DomainModelDefault.java
new file mode 100644
index 0000000..732a3ea
--- /dev/null
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/metamodel/DomainModelDefault.java
@@ -0,0 +1,58 @@
+/*
+ *  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.metamodel.services.metamodel;
+
+import java.util.List;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlRootElement;
+
+import org.apache.isis.applib.services.metamodel.DomainMember;
+import org.apache.isis.applib.services.metamodel.DomainModel;
+import org.apache.isis.commons.internal.exceptions._Exceptions;
+
+@XmlRootElement(name="domain")
+@XmlAccessorType(XmlAccessType.PROPERTY)
+public class DomainModelDefault implements DomainModel {
+
+    // to support JAX-B marshaling 
+    DomainModelDefault(){
+        throw _Exceptions.unexpectedCodeReach();
+    }
+    
+    public DomainModelDefault(List<DomainMember> memberList) {
+        this.memberList = memberList;
+    }
+    
+    // --
+    
+    private List<DomainMember> memberList;
+
+    @XmlElement(name="domain-member", type=DomainMemberDefault.class)
+    @Override
+    public List<DomainMember> getDomainMembers() {
+        return memberList;
+    }
+    
+    // --
+   
+
+}
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/metamodel/MetaModelServiceDefault.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/metamodel/MetaModelServiceDefault.java
index a295174..588ddde 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/metamodel/MetaModelServiceDefault.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/metamodel/MetaModelServiceDefault.java
@@ -36,6 +36,7 @@ import org.apache.isis.applib.services.bookmark.Bookmark;
 import org.apache.isis.applib.services.command.CommandDtoProcessor;
 import org.apache.isis.applib.services.grid.GridService;
 import org.apache.isis.applib.services.metamodel.DomainMember;
+import org.apache.isis.applib.services.metamodel.DomainModel;
 import org.apache.isis.applib.services.metamodel.MetaModelService;
 import org.apache.isis.commons.internal.collections._Lists;
 import org.apache.isis.core.metamodel.JdoMetamodelUtil;
@@ -107,7 +108,7 @@ public class MetaModelServiceDefault implements MetaModelService {
 
     @Override
     @Programmatic
-    public List<DomainMember> export() {
+    public DomainModel getDomainModel() {
 
         final Collection<ObjectSpecification> specifications = specificationLookup.allSpecifications();
 
@@ -151,7 +152,7 @@ public class MetaModelServiceDefault implements MetaModelService {
 
         Collections.sort(rows);
 
-        return rows;
+        return new DomainModelDefault(rows);
     }
 
 
@@ -285,4 +286,7 @@ public class MetaModelServiceDefault implements MetaModelService {
     @javax.inject.Inject
     AppManifestProvider appManifestProvider;
 
+
+
+
 }
diff --git a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/services/metamodel/MetaModelServiceDefaultTest.java b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/services/metamodel/MetaModelServiceDefaultTest.java
new file mode 100644
index 0000000..a54abe1
--- /dev/null
+++ b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/services/metamodel/MetaModelServiceDefaultTest.java
@@ -0,0 +1,254 @@
+/*
+ *  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.metamodel.services.metamodel;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import javax.xml.bind.JAXBContext;
+import javax.xml.bind.JAXBException;
+import javax.xml.bind.Marshaller;
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlRootElement;
+
+import org.hamcrest.Matcher;
+import org.hamcrest.Matchers;
+import org.jmock.Expectations;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+import org.apache.isis.applib.Identifier;
+import org.apache.isis.applib.services.metamodel.DomainMember;
+import org.apache.isis.applib.services.metamodel.DomainModel;
+import org.apache.isis.commons.internal.base._Casts;
+import org.apache.isis.commons.internal.collections._Lists;
+import org.apache.isis.core.commons.config.IsisConfigurationDefault;
+import org.apache.isis.core.metamodel.facetapi.Facet;
+import org.apache.isis.core.metamodel.facets.FacetedMethod;
+import org.apache.isis.core.metamodel.services.ServicesInjector;
+import org.apache.isis.core.metamodel.services.persistsession.PersistenceSessionServiceInternal;
+import org.apache.isis.core.metamodel.spec.Hierarchical.Depth;
+import org.apache.isis.core.metamodel.spec.ObjectSpecification;
+import org.apache.isis.core.metamodel.spec.feature.ObjectAction;
+import org.apache.isis.core.metamodel.specloader.SpecificationLoader;
+import org.apache.isis.core.metamodel.specloader.specimpl.ObjectActionDefault;
+import org.apache.isis.core.unittestsupport.jmocking.JUnitRuleMockery2;
+import org.apache.isis.core.unittestsupport.jmocking.JUnitRuleMockery2.Mode;
+
+class MetaModelServiceDefaultTest {
+
+    IsisConfigurationDefault stubConfiguration;
+    ServicesInjector stubServicesInjector;
+    MetaModelServiceDefault mockMetaModelService;
+    SpecificationLoader specificationLoader;
+    ObjectAction action;
+    ObjectSpecification mockSpec;
+
+    FacetedMethod mockFacetedMethod;
+    
+    
+    @BeforeEach
+    void setUp() throws Exception {
+        
+        JUnitRuleMockery2 context = JUnitRuleMockery2.createFor(Mode.INTERFACES_AND_CLASSES);
+        
+        stubConfiguration = new IsisConfigurationDefault();
+
+        stubServicesInjector =
+                new ServicesInjector(_Lists.of(context.mock(
+                        PersistenceSessionServiceInternal.class
+                        )), stubConfiguration);
+        
+        specificationLoader = 
+                new SpecificationLoader(stubConfiguration, null, null, stubServicesInjector);
+        
+        stubServicesInjector.addFallbackIfRequired(SpecificationLoader.class, specificationLoader);
+        
+        mockFacetedMethod = context.mock(FacetedMethod.class);
+        Matcher<Class<? extends Facet>> facetMatcher = _Casts.uncheckedCast(Matchers.any(Class.class));
+        context.checking(new Expectations() {
+            {
+                allowing(mockFacetedMethod).getIdentifier();
+                will(returnValue(Identifier.actionIdentifier("Customer", "reduceheadcount")));
+                
+                allowing(mockFacetedMethod).getFacet(with(facetMatcher));
+                will(returnValue(null));
+                
+                allowing(mockFacetedMethod).getParameters();
+                will(returnValue(Collections.emptyList()));
+            }
+        });
+
+        mockSpec = context.mock(ObjectSpecification.class);
+        context.checking(new Expectations() {
+            {
+                allowing(mockSpec).getFullIdentifier();
+                will(returnValue("mocked"));
+                
+                allowing(mockSpec).subclasses(Depth.DIRECT);
+                will(returnValue(Collections.emptyList()));
+                
+                allowing(mockSpec).isService();
+                will(returnValue(true));
+            }
+        });
+        
+        action = new ObjectActionDefault(mockFacetedMethod, stubServicesInjector);
+        
+        mockMetaModelService = context.mock(MetaModelServiceDefault.class);
+        context.checking(new Expectations() {
+            {
+                allowing(mockMetaModelService).getDomainModel();
+                will(returnValue(new DomainModelDefault(_Lists.of(new DomainMemberDefault(mockSpec, action)))));
+                
+            }
+        });
+        
+    }
+
+    @AfterEach
+    void tearDown() throws Exception {
+    }
+
+    @Test @DisplayName("member to XML marshalling should not throw")
+    void member_marshalling() throws JAXBException {
+        DomainMember domainMember = new DomainMemberDefault(mockSpec, action);
+        
+        JAXBContext jaxbContext = JAXBContext.newInstance(DomainMemberDefault.class);
+        Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
+        jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
+        
+        jaxbMarshaller.marshal(domainMember, noopOutput());
+    }
+    
+    @Test @DisplayName("model to XML marshalling should not throw")
+    void model_marshalling() throws JAXBException {
+        DomainModel domainMembers = mockMetaModelService.getDomainModel();
+        
+        JAXBContext jaxbContext = JAXBContext.newInstance(DomainModelDefault.class);
+        Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
+        jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
+        
+        jaxbMarshaller.marshal(domainMembers, System.out);
+    }
+    
+    
+    @Test @DisplayName("example to XML marshalling should not throw")
+    void example_marshalling() throws JAXBException {
+        
+        JAXBContext jaxbContext = JAXBContext.newInstance(Employees.class);
+        Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
+        jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
+         
+        jaxbMarshaller.marshal(createEmployees(), noopOutput());
+    }
+
+    // -- HELPER
+    
+    private OutputStream noopOutput(){
+        return new OutputStream() {
+            @Override public void write(int b) throws IOException {}
+        };  
+    }
+    
+    @XmlRootElement(name = "employee")
+    @XmlAccessorType (XmlAccessType.FIELD)
+    public static class Employee
+    {
+        private Integer id;
+        private String firstName;
+        private String lastName;
+        private double income;
+        
+        //Getters and Setters
+        
+        public Integer getId() {
+            return id;
+        }
+        public void setId(Integer id) {
+            this.id = id;
+        }
+        public String getFirstName() {
+            return firstName;
+        }
+        public void setFirstName(String firstName) {
+            this.firstName = firstName;
+        }
+        public String getLastName() {
+            return lastName;
+        }
+        public void setLastName(String lastName) {
+            this.lastName = lastName;
+        }
+        public double getIncome() {
+            return income;
+        }
+        public void setIncome(double income) {
+            this.income = income;
+        }
+        
+    }
+    
+    @XmlRootElement(name = "employees")
+    @XmlAccessorType (XmlAccessType.FIELD)
+    public static class Employees
+    {
+        @XmlElement(name = "employee")
+        private List<Employee> employees = null;
+     
+        public List<Employee> getEmployees() {
+            return employees;
+        }
+     
+        public void setEmployees(List<Employee> employees) {
+            this.employees = employees;
+        }
+    }
+    
+    private Employees createEmployees(){
+        Employees employees = new Employees();
+        employees.setEmployees(new ArrayList<Employee>());
+        //Create two employees
+        Employee emp1 = new Employee();
+        emp1.setId(1);
+        emp1.setFirstName("Lokesh");
+        emp1.setLastName("Gupta");
+        emp1.setIncome(100.0);
+         
+        Employee emp2 = new Employee();
+        emp2.setId(2);
+        emp2.setFirstName("John");
+        emp2.setLastName("Mclane");
+        emp2.setIncome(200.0);
+         
+        //Add the employees in list
+        employees.getEmployees().add(emp1);
+        employees.getEmployees().add(emp2);
+        
+        return employees;
+    }
+
+    
+    
+}
diff --git a/example/application/helloworld/src/main/java/domainapp/application/menubars.layout.xml b/example/application/helloworld/src/main/java/domainapp/application/menubars.layout.xml
index 200bcc9..97482c7 100644
--- a/example/application/helloworld/src/main/java/domainapp/application/menubars.layout.xml
+++ b/example/application/helloworld/src/main/java/domainapp/application/menubars.layout.xml
@@ -60,7 +60,10 @@
                 </mb3:serviceAction>
             </mb3:section>
             <mb3:section>
-                <mb3:serviceAction objectType="isisApplib.MetaModelServicesMenu" id="downloadMetaModel">
+                <mb3:serviceAction objectType="isisApplib.MetaModelServicesMenu" id="downloadMetaModelXml">
+                    <cpt:named>Download Meta Model (XML)</cpt:named>
+                </mb3:serviceAction>
+                <mb3:serviceAction objectType="isisApplib.MetaModelServicesMenu" id="downloadMetaModelCsv">
                     <cpt:named>Download Meta Model (CSV)</cpt:named>
                 </mb3:serviceAction>
             </mb3:section>