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 2019/01/08 07:25:57 UTC

[isis] 07/10: ISIS-2080: adds GridService2 and GridLoaderService2 with support for loading different layouts

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

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

commit f2841bc91a612b1400eb824c943b3d449debd608
Author: danhaywood <da...@haywood-associates.co.uk>
AuthorDate: Tue Jan 8 06:55:00 2019 +0000

    ISIS-2080: adds GridService2 and GridLoaderService2 with support for loading different layouts
    
    based on the value of LayoutFacet for the target ObjectAdapter
---
 .../applib/services/grid/GridLoaderService2.java   |  31 ++++
 .../isis/applib/services/grid/GridService2.java    |  32 +++++
 .../metamodel/facets/object/grid/GridFacet.java    |   3 +-
 .../facets/object/grid/GridFacetDefault.java       |  20 +--
 .../facets/object/grid/GridFacetFactory.java       |   4 +-
 .../facets/object/layout/LayoutFacet.java          |   2 +-
 .../facets/object/layout/LayoutFacetFactory.java   |   6 +-
 .../facets/object/layout/LayoutFacetFallback.java  |   2 +-
 .../facets/object/layout/LayoutFacetMethod.java    |   8 +-
 .../services/grid/GridLoaderServiceDefault.java    | 159 +++++++++++++++------
 .../services/grid/GridServiceDefault.java          |  14 +-
 .../services/layout/LayoutServiceDefault.java      |   2 +-
 ...dLoaderServiceDefault_resourceNameFor_Test.java |   8 +-
 .../resources/DomainObjectResourceServerside.java  |   3 +-
 .../resources/DomainTypeResourceServerside.java    |   2 +-
 .../CollectionContentsAsAjaxTablePanel.java        |   5 +-
 .../links/EntityLinksSelectorPanelFactory.java     |   4 +-
 .../viewer/wicket/ui/pages/entity/EntityPage.java  |   2 +-
 18 files changed, 233 insertions(+), 74 deletions(-)

diff --git a/core/applib/src/main/java/org/apache/isis/applib/services/grid/GridLoaderService2.java b/core/applib/src/main/java/org/apache/isis/applib/services/grid/GridLoaderService2.java
new file mode 100644
index 0000000..ed4b3e9
--- /dev/null
+++ b/core/applib/src/main/java/org/apache/isis/applib/services/grid/GridLoaderService2.java
@@ -0,0 +1,31 @@
+/**
+ *  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.grid;
+
+import org.apache.isis.applib.annotation.Programmatic;
+import org.apache.isis.applib.layout.grid.Grid;
+
+public interface GridLoaderService2 extends GridLoaderService {
+
+    /**
+     * Returns a new instance of a {@link Grid} for the specified domain class, eg from a
+     * <code>layout.xml</code> file, else <code>null</code>.
+     */
+    @Programmatic
+    Grid load(final Class<?> domainClass, String layout);
+
+}
diff --git a/core/applib/src/main/java/org/apache/isis/applib/services/grid/GridService2.java b/core/applib/src/main/java/org/apache/isis/applib/services/grid/GridService2.java
new file mode 100644
index 0000000..8d4431c
--- /dev/null
+++ b/core/applib/src/main/java/org/apache/isis/applib/services/grid/GridService2.java
@@ -0,0 +1,32 @@
+/**
+ *  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.grid;
+
+import org.apache.isis.applib.annotation.Programmatic;
+import org.apache.isis.applib.layout.grid.Grid;
+
+public interface GridService2 extends GridService {
+
+    /**
+     * Returns a new instance of a {@link Grid} for the specified domain class, eg from a
+     * <code>[domainClass].layout.[layout].xml</code> file, else <code>null</code>.
+     */
+    @Programmatic
+    Grid load(Class<?> domainClass, String layout);
+
+
+}
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/grid/GridFacet.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/grid/GridFacet.java
index 11ce9ad..9930ea0 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/grid/GridFacet.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/grid/GridFacet.java
@@ -21,6 +21,7 @@ package org.apache.isis.core.metamodel.facets.object.grid;
 import org.apache.isis.applib.layout.grid.Grid;
 import org.apache.isis.applib.services.grid.GridSystemService;
 import org.apache.isis.applib.services.layout.LayoutService;
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
 import org.apache.isis.core.metamodel.facetapi.Facet;
 
 /**
@@ -34,7 +35,7 @@ import org.apache.isis.core.metamodel.facetapi.Facet;
  */
 public interface GridFacet extends Facet {
 
-    Grid getGrid();
+    Grid getGrid(final ObjectAdapter objectAdapter);
 
 
 }
\ No newline at end of file
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/grid/GridFacetDefault.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/grid/GridFacetDefault.java
index 9ce20af..b36de92 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/grid/GridFacetDefault.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/grid/GridFacetDefault.java
@@ -22,10 +22,12 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import org.apache.isis.applib.layout.grid.Grid;
-import org.apache.isis.applib.services.grid.GridService;
+import org.apache.isis.applib.services.grid.GridService2;
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
 import org.apache.isis.core.metamodel.facetapi.Facet;
 import org.apache.isis.core.metamodel.facetapi.FacetAbstract;
 import org.apache.isis.core.metamodel.facetapi.FacetHolder;
+import org.apache.isis.core.metamodel.facets.object.layout.LayoutFacet;
 import org.apache.isis.core.metamodel.spec.ObjectSpecification;
 
 public class GridFacetDefault
@@ -42,33 +44,35 @@ public class GridFacetDefault
 
     public static GridFacet create(
             final FacetHolder facetHolder,
-            final GridService gridService) {
+            final GridService2 gridService) {
         return new GridFacetDefault(facetHolder, gridService);
     }
 
-    private final GridService gridService;
+    private final GridService2 gridService;
 
     private Grid grid;
 
     private GridFacetDefault(
             final FacetHolder facetHolder,
-            final GridService gridService) {
+            final GridService2 gridService) {
         super(GridFacetDefault.type(), facetHolder, Derivation.NOT_DERIVED);
         this.gridService = gridService;
     }
 
-    public Grid getGrid() {
+    public Grid getGrid(final ObjectAdapter objectAdapterIfAny) {
         if (!gridService.supportsReloading() && this.grid != null) {
             return this.grid;
         }
         final Class<?> domainClass = getSpecification().getCorrespondingClass();
-        this.grid = load(domainClass);
+        final LayoutFacet layoutFacet = getFacetHolder().getFacet(LayoutFacet.class);
+        final String layout = layoutFacet != null ? layoutFacet.layout(objectAdapterIfAny) : null;
+        this.grid = load(domainClass, layout);
 
         return this.grid;
     }
 
-    private Grid load(final Class<?> domainClass) {
-        Grid grid = gridService.load(domainClass);
+    private Grid load(final Class<?> domainClass, final String layout) {
+        Grid grid = gridService.load(domainClass, layout);
         if(grid == null) {
             grid = gridService.defaultGridFor(domainClass);
         }
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/grid/GridFacetFactory.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/grid/GridFacetFactory.java
index 8c00734..a6d74a3 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/grid/GridFacetFactory.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/grid/GridFacetFactory.java
@@ -19,7 +19,7 @@ package org.apache.isis.core.metamodel.facets.object.grid;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import org.apache.isis.applib.services.grid.GridService;
+import org.apache.isis.applib.services.grid.GridService2;
 import org.apache.isis.core.metamodel.facetapi.FacetHolder;
 import org.apache.isis.core.metamodel.facetapi.FacetUtil;
 import org.apache.isis.core.metamodel.facetapi.FeatureType;
@@ -37,7 +37,7 @@ public class GridFacetFactory extends FacetFactoryAbstract {
     public void process(final ProcessClassContext processClassContext) {
         final FacetHolder facetHolder = processClassContext.getFacetHolder();
 
-        final GridService gridService = servicesInjector.lookupService(GridService.class);
+        final GridService2 gridService = servicesInjector.lookupService(GridService2.class);
 
         FacetUtil.addFacet(GridFacetDefault.create(facetHolder, gridService));
     }
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/layout/LayoutFacet.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/layout/LayoutFacet.java
index 8a072de..31d8627 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/layout/LayoutFacet.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/layout/LayoutFacet.java
@@ -33,5 +33,5 @@ import org.apache.isis.core.metamodel.facetapi.Facet;
  */
 public interface LayoutFacet extends Facet {
 
-    public String layout(final ObjectAdapter object);
+    public String layout(final ObjectAdapter objectAdapterIfAny);
 }
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/layout/LayoutFacetFactory.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/layout/LayoutFacetFactory.java
index 61b549b..f4df289 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/layout/LayoutFacetFactory.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/layout/LayoutFacetFactory.java
@@ -30,9 +30,9 @@ import org.apache.isis.core.metamodel.methodutils.MethodScope;
 
 public class LayoutFacetFactory extends MethodPrefixBasedFacetFactoryAbstract {
 
-    private static final String ICON_NAME_PREFIX = "iconName";
+    private static final String LAYOUT_METHOD_NAME = "layout";
 
-    private static final String[] PREFIXES = { ICON_NAME_PREFIX, };
+    private static final String[] PREFIXES = { LAYOUT_METHOD_NAME, };
 
     public LayoutFacetFactory() {
         super(FeatureType.OBJECTS_ONLY, OrphanValidation.VALIDATE, PREFIXES);
@@ -43,7 +43,7 @@ public class LayoutFacetFactory extends MethodPrefixBasedFacetFactoryAbstract {
         final Class<?> cls = processClassContext.getCls();
         final FacetHolder facetHolder = processClassContext.getFacetHolder();
 
-        final Method method = MethodFinderUtils.findMethod(cls, MethodScope.OBJECT, ICON_NAME_PREFIX, String.class, NO_PARAMETERS_TYPES);
+        final Method method = MethodFinderUtils.findMethod(cls, MethodScope.OBJECT, LAYOUT_METHOD_NAME, String.class, NO_PARAMETERS_TYPES);
 
         final LayoutFacet facet;
         if (method == null) {
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/layout/LayoutFacetFallback.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/layout/LayoutFacetFallback.java
index 4f47c37..863b1e7 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/layout/LayoutFacetFallback.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/layout/LayoutFacetFallback.java
@@ -31,7 +31,7 @@ public class LayoutFacetFallback extends LayoutFacetAbstract {
     }
 
     @Override
-    public String layout(final ObjectAdapter owningAdapter) {
+    public String layout(final ObjectAdapter objectAdapterIfAny) {
         return null;
     }
 
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/layout/LayoutFacetMethod.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/layout/LayoutFacetMethod.java
index 27f72c3..f4c3d2b 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/layout/LayoutFacetMethod.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/layout/LayoutFacetMethod.java
@@ -24,7 +24,6 @@ import java.util.Map;
 
 import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
 import org.apache.isis.core.metamodel.facetapi.FacetHolder;
-import org.apache.isis.core.metamodel.facets.object.layout.LayoutFacetAbstract;
 
 public class LayoutFacetMethod extends LayoutFacetAbstract {
 
@@ -36,9 +35,12 @@ public class LayoutFacetMethod extends LayoutFacetAbstract {
     }
 
     @Override
-    public String layout(final ObjectAdapter owningAdapter) {
+    public String layout(final ObjectAdapter objectAdapterIfAny) {
+        if(objectAdapterIfAny == null) {
+            return null;
+        }
         try {
-            return (String) ObjectAdapter.InvokeUtils.invoke(method, owningAdapter);
+            return (String) ObjectAdapter.InvokeUtils.invoke(method, objectAdapterIfAny);
         } catch (final RuntimeException ex) {
             return null;
         }
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/grid/GridLoaderServiceDefault.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/grid/GridLoaderServiceDefault.java
index cf9d0cf..28bf78a 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/grid/GridLoaderServiceDefault.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/grid/GridLoaderServiceDefault.java
@@ -23,13 +23,13 @@ import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 
-import javax.annotation.Nullable;
 import javax.annotation.PostConstruct;
 import javax.xml.bind.JAXBContext;
 import javax.xml.bind.JAXBException;
 
 import com.google.common.base.Function;
 import com.google.common.collect.FluentIterable;
+import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
 import com.google.common.io.Resources;
 
@@ -41,7 +41,7 @@ import org.apache.isis.applib.annotation.DomainService;
 import org.apache.isis.applib.annotation.NatureOfService;
 import org.apache.isis.applib.annotation.Programmatic;
 import org.apache.isis.applib.layout.grid.Grid;
-import org.apache.isis.applib.services.grid.GridLoaderService;
+import org.apache.isis.applib.services.grid.GridLoaderService2;
 import org.apache.isis.applib.services.grid.GridSystemService;
 import org.apache.isis.applib.services.jaxb.JaxbService;
 import org.apache.isis.core.metamodel.deployment.DeploymentCategoryProvider;
@@ -50,16 +50,81 @@ import org.apache.isis.core.metamodel.deployment.DeploymentCategoryProvider;
         nature = NatureOfService.DOMAIN,
         menuOrder = "" + Integer.MAX_VALUE
 )
-public class GridLoaderServiceDefault implements GridLoaderService {
+public class GridLoaderServiceDefault implements GridLoaderService2 {
 
     private static final Logger LOG = LoggerFactory.getLogger(GridLoaderServiceDefault.class);
 
 
+    static class DomainClassAndLayout {
+        private final Class<?> domainClass;
+        private final String layoutIfAny;
+
+        DomainClassAndLayout(final Class<?> domainClass, final String layoutIfAny) {
+            this.domainClass = domainClass;
+            this.layoutIfAny = layoutIfAny;
+        }
+
+        @Override public boolean equals(final Object o) {
+            if (this == o)
+                return true;
+            if (o == null || getClass() != o.getClass())
+                return false;
+
+            final DomainClassAndLayout that = (DomainClassAndLayout) o;
+
+            if (domainClass != null ? !domainClass.equals(that.domainClass) : that.domainClass != null)
+                return false;
+            return layoutIfAny != null ? layoutIfAny.equals(that.layoutIfAny) : that.layoutIfAny == null;
+        }
+
+        @Override public int hashCode() {
+            int result = domainClass != null ? domainClass.hashCode() : 0;
+            result = 31 * result + (layoutIfAny != null ? layoutIfAny.hashCode() : 0);
+            return result;
+        }
+
+        @Override public String toString() {
+            return "domainClass=" + domainClass +
+                    ", layout='" + layoutIfAny + '\'';
+        }
+    }
+
     // for better logging messages (used only in prototyping mode)
-    private final Map<Class<?>, String> badXmlByClass = Maps.newHashMap();
+    private final Map<DomainClassAndLayout, String> badXmlByDomainClassAndLayout = Maps.newHashMap();
+
+    static class DomainClassAndLayoutAndXml {
+        private final DomainClassAndLayout domainClassAndLayout;
+        private final String xml;
+
+        @Override public boolean equals(final Object o) {
+            if (this == o)
+                return true;
+            if (o == null || getClass() != o.getClass())
+                return false;
+
+            final DomainClassAndLayoutAndXml that = (DomainClassAndLayoutAndXml) o;
+
+            if (domainClassAndLayout != null ?
+                    !domainClassAndLayout.equals(that.domainClassAndLayout) :
+                    that.domainClassAndLayout != null)
+                return false;
+            return xml != null ? xml.equals(that.xml) : that.xml == null;
+        }
+
+        @Override public int hashCode() {
+            int result = domainClassAndLayout != null ? domainClassAndLayout.hashCode() : 0;
+            result = 31 * result + (xml != null ? xml.hashCode() : 0);
+            return result;
+        }
+
+        DomainClassAndLayoutAndXml(final DomainClassAndLayout domainClassAndLayout, final String xml) {
+            this.domainClassAndLayout = domainClassAndLayout;
+            this.xml = xml;
+        }
+    }
 
     // cache (used only in prototyping mode)
-    private final Map<String, Grid> gridByXml = Maps.newHashMap();
+    private final Map<DomainClassAndLayoutAndXml, Grid> gridByDomainClassAndLayoutAndXml = Maps.newHashMap();
 
     private JAXBContext jaxbContext;
 
@@ -93,45 +158,48 @@ public class GridLoaderServiceDefault implements GridLoaderService {
 
     @Override
     public void remove(final Class<?> domainClass) {
+        final String layoutIfAny = null;
+        final DomainClassAndLayout dcal = new DomainClassAndLayout(domainClass, layoutIfAny);
         if(!supportsReloading()) {
             return;
         }
-        badXmlByClass.remove(domainClass);
-        final String xml = loadXml(domainClass);
+        badXmlByDomainClassAndLayout.remove(dcal);
+        final String xml = loadXml(dcal);
         if(xml == null) {
             return;
         }
-        gridByXml.remove(xml);
+        gridByDomainClassAndLayoutAndXml.remove(new DomainClassAndLayoutAndXml(dcal, xml));
     }
 
     @Override
     @Programmatic
     public boolean existsFor(final Class<?> domainClass) {
-        return resourceNameFor(domainClass) != null;
+        return resourceNameFor(new DomainClassAndLayout(domainClass, null)) != null;
     }
 
     @Override
-    @Programmatic
-    public Grid load(final Class<?> domainClass) {
-        final String xml = loadXml(domainClass);
+    public Grid load(final Class<?> domainClass, final String layoutIfAny) {
+        final DomainClassAndLayout dcal = new DomainClassAndLayout(domainClass, layoutIfAny);
+        final String xml = loadXml(dcal);
         if(xml == null) {
             return null;
         }
 
+        final DomainClassAndLayoutAndXml dcalax = new DomainClassAndLayoutAndXml(dcal, xml);
         if(supportsReloading()) {
-            final Grid grid = gridByXml.get(xml);
+            final Grid grid = gridByDomainClassAndLayoutAndXml.get(dcalax);
             if(grid != null) {
                 return grid;
             }
 
-            final String badXml = badXmlByClass.get(domainClass);
+            final String badXml = badXmlByDomainClassAndLayout.get(dcal);
             if(badXml != null) {
                 if(Objects.equals(xml, badXml)) {
                     // seen this before and already logged; just quit
                     return null;
                 } else {
                     // this different XML might be good
-                    badXmlByClass.remove(domainClass);
+                    badXmlByDomainClassAndLayout.remove(dcal);
                 }
             }
         }
@@ -141,23 +209,23 @@ public class GridLoaderServiceDefault implements GridLoaderService {
                 // shouldn't occur, indicates that initialization failed to locate any GridSystemService implementations.
                 return null;
             }
-            
+
             final Grid grid = (Grid) jaxbService.fromXml(jaxbContext, xml);
             grid.setDomainClass(domainClass);
             if(supportsReloading()) {
-                gridByXml.put(xml, grid);
+                gridByDomainClassAndLayoutAndXml.put(dcalax, grid);
             }
             return grid;
         } catch(Exception ex) {
 
             if(supportsReloading()) {
                 // save fact that this was bad XML, so that we don't log again if called next time
-                badXmlByClass.put(domainClass, xml);
+                badXmlByDomainClassAndLayout.put(dcal, xml);
             }
 
             // note that we don't blacklist if the file exists but couldn't be parsed;
             // the developer might fix so we will want to retry.
-            final String resourceName = resourceNameFor(domainClass);
+            final String resourceName = resourceNameFor(dcal);
             final String message = "Failed to parse " + resourceName + " file (" + ex.getMessage() + ")";
             if(supportsReloading()) {
                 container.warnUser(message);
@@ -168,32 +236,46 @@ public class GridLoaderServiceDefault implements GridLoaderService {
         }
     }
 
-    private String loadXml(final Class<?> domainClass) {
-        final String resourceName = resourceNameFor(domainClass);
+    @Override
+    @Programmatic
+    public Grid load(final Class<?> domainClass) {
+        return load(domainClass, null);
+    }
+
+    private String loadXml(final DomainClassAndLayout dcal) {
+        final String resourceName = resourceNameFor(dcal);
         if(resourceName == null) {
-            LOG.debug("Failed to locate layout file for '{}'", domainClass.getName());
+            LOG.debug("Failed to locate layout file for '{}'", dcal.toString());
             return null;
         }
         try {
-            return resourceContentOf(domainClass, resourceName);
+            return resourceContentOf(dcal, resourceName);
         } catch (IOException ex) {
             LOG.debug(
                     "Failed to locate file {} (relative to {}.class)",
-                    resourceName, domainClass.getName(), ex);
+                    resourceName, dcal.domainClass.getName(), ex);
             return null;
         }
     }
 
-    private static String resourceContentOf(final Class<?> cls, final String resourceName) throws IOException {
-        final URL url = Resources.getResource(cls, resourceName);
+    private static String resourceContentOf(final DomainClassAndLayout dcal, final String resourceName) throws IOException {
+        final URL url = Resources.getResource(dcal.domainClass, resourceName);
         return Resources.toString(url, Charset.defaultCharset());
     }
 
-    String resourceNameFor(final Class<?> domainClass) {
-        for (final Type type : Type.values()) {
-            final String candidateResourceName = resourceNameFor(domainClass, type);
+    String resourceNameFor(final DomainClassAndLayout dcal) {
+        final List<String> candidateResourceNames = Lists.newArrayList();
+        if(dcal.layoutIfAny != null) {
+            candidateResourceNames.add(
+                    String.format("%s.layout.%s.xml", dcal.domainClass.getSimpleName(), dcal.layoutIfAny));
+        }
+        candidateResourceNames.add(
+                String.format("%s.layout.xml", dcal.domainClass.getSimpleName()));
+        candidateResourceNames.add(
+                String.format("%s.layout.fallback.xml", dcal.domainClass.getSimpleName()));
+        for (final String candidateResourceName : candidateResourceNames) {
             try {
-                final URL resource = Resources.getResource(domainClass, candidateResourceName);
+                final URL resource = Resources.getResource(dcal.domainClass, candidateResourceName);
                 if (resource != null) {
                     return candidateResourceName;
                 }
@@ -204,34 +286,29 @@ public class GridLoaderServiceDefault implements GridLoaderService {
         return null;
     }
 
+
     enum Type {
         DEFAULT {
             @Override
             protected String suffix() {
-                return ".layout.xml";
+                return ".xml";
             }
         },
         FALLBACK {
             @Override
             protected String suffix() {
-                return ".layout.fallback.xml";
+                return ".fallback.xml";
             }
         };
 
-        private String resourceNameFor(final Class<?> domainClass) {
-            return domainClass.getSimpleName() + suffix();
+        private String resourceNameFor(final DomainClassAndLayout dcal) {
+            return dcal.domainClass.getSimpleName() + ".layout" + (dcal.layoutIfAny != null ? dcal
+                    .layoutIfAny + "." : "") + suffix();
         }
 
         protected abstract String suffix();
     }
 
-    private String resourceNameFor(
-            final Class<?> domainClass,
-            final Type type) {
-        return type.resourceNameFor(domainClass);
-    }
-
-
     //region > injected dependencies
 
     @javax.inject.Inject
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/grid/GridServiceDefault.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/grid/GridServiceDefault.java
index 90a984f..323c26b 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/grid/GridServiceDefault.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/grid/GridServiceDefault.java
@@ -32,15 +32,15 @@ import org.apache.isis.applib.annotation.DomainService;
 import org.apache.isis.applib.annotation.NatureOfService;
 import org.apache.isis.applib.annotation.Programmatic;
 import org.apache.isis.applib.layout.grid.Grid;
-import org.apache.isis.applib.services.grid.GridLoaderService;
-import org.apache.isis.applib.services.grid.GridService;
+import org.apache.isis.applib.services.grid.GridLoaderService2;
+import org.apache.isis.applib.services.grid.GridService2;
 import org.apache.isis.applib.services.grid.GridSystemService;
 
 @DomainService(
         nature = NatureOfService.DOMAIN,
         menuOrder = "" + Integer.MAX_VALUE
 )
-public class GridServiceDefault implements GridService {
+public class GridServiceDefault implements GridService2 {
 
     private static final Logger LOG = LoggerFactory.getLogger(GridServiceDefault.class);
 
@@ -74,6 +74,12 @@ public class GridServiceDefault implements GridService {
         return gridLoaderService.load(domainClass);
     }
 
+    @Override
+    @Programmatic
+    public Grid load(final Class<?> domainClass, final String layout) {
+        return gridLoaderService.load(domainClass, layout);
+    }
+
     // //////////////////////////////////////
 
     @Override
@@ -200,7 +206,7 @@ public class GridServiceDefault implements GridService {
 
 
     @javax.inject.Inject
-    GridLoaderService gridLoaderService;
+    GridLoaderService2 gridLoaderService;
 
     @javax.inject.Inject
     List<GridSystemService> gridSystemServices;
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/layout/LayoutServiceDefault.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/layout/LayoutServiceDefault.java
index 0ed1974..24bf62b 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/layout/LayoutServiceDefault.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/layout/LayoutServiceDefault.java
@@ -72,7 +72,7 @@ public class LayoutServiceDefault implements LayoutService2 {
         if (style == Style.CURRENT) {
             final ObjectSpecification objectSpec = specificationLoader.loadSpecification(domainClass);
             final GridFacet facet = objectSpec.getFacet(GridFacet.class);
-            return facet != null? facet.getGrid(): null;
+            return facet != null? facet.getGrid(null): null;
         }
 
         // don't use the grid from the facet, because it will be modified subsequently.
diff --git a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/services/grid/GridLoaderServiceDefault_resourceNameFor_Test.java b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/services/grid/GridLoaderServiceDefault_resourceNameFor_Test.java
index fca557a..90132bf 100644
--- a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/services/grid/GridLoaderServiceDefault_resourceNameFor_Test.java
+++ b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/services/grid/GridLoaderServiceDefault_resourceNameFor_Test.java
@@ -36,23 +36,23 @@ public class GridLoaderServiceDefault_resourceNameFor_Test {
 
     @Test
     public void when_default_exists() {
-        final String s = gridLoaderServiceDefault.resourceNameFor(Foo.class);
+        final String s = gridLoaderServiceDefault.resourceNameFor(new GridLoaderServiceDefault.DomainClassAndLayout(Foo.class, null));
         Assert.assertThat(s, is(equalTo("Foo.layout.xml")));
     }
 
     @Test
     public void when_fallback_exists() {
-        final String s = gridLoaderServiceDefault.resourceNameFor(Foo2.class);
+        final String s = gridLoaderServiceDefault.resourceNameFor(new GridLoaderServiceDefault.DomainClassAndLayout(Foo2.class, null));
         Assert.assertThat(s, is(equalTo("Foo2.layout.fallback.xml")));
     }
     @Test
     public void when_default_and_fallback_both_exist() {
-        final String s = gridLoaderServiceDefault.resourceNameFor(Foo3.class);
+        final String s = gridLoaderServiceDefault.resourceNameFor(new GridLoaderServiceDefault.DomainClassAndLayout(Foo3.class, null));
         Assert.assertThat(s, is(equalTo("Foo3.layout.xml")));
     }
     @Test
     public void when_neither_exist() {
-        final String s = gridLoaderServiceDefault.resourceNameFor(Foo4.class);
+        final String s = gridLoaderServiceDefault.resourceNameFor(new GridLoaderServiceDefault.DomainClassAndLayout(Foo4.class, null));
         Assert.assertNull(s);
     }
 }
\ No newline at end of file
diff --git a/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/resources/DomainObjectResourceServerside.java b/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/resources/DomainObjectResourceServerside.java
index 5c7ee86..ef50602 100644
--- a/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/resources/DomainObjectResourceServerside.java
+++ b/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/resources/DomainObjectResourceServerside.java
@@ -309,7 +309,8 @@ public class DomainObjectResourceServerside extends ResourceAbstract implements
         if(gridFacet == null) {
             builder = Responses.ofNotFound();
         } else {
-            Grid grid = gridFacet.getGrid();
+            final ObjectAdapter objectAdapter = getObjectAdapterElseThrowNotFound(domainType, instanceId);
+            Grid grid = gridFacet.getGrid(objectAdapter);
             addLinks(domainType, instanceId, grid);
             builder = Response.status(Response.Status.OK)
                     .entity(serializationStrategy.entity(grid))
diff --git a/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/resources/DomainTypeResourceServerside.java b/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/resources/DomainTypeResourceServerside.java
index 39065b9..3d2d458 100644
--- a/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/resources/DomainTypeResourceServerside.java
+++ b/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/resources/DomainTypeResourceServerside.java
@@ -126,7 +126,7 @@ public class DomainTypeResourceServerside extends ResourceAbstract implements Do
             builder = Responses.ofNotFound();
             return builder.build();
         } else {
-            Grid grid = gridFacet.getGrid();
+            Grid grid = gridFacet.getGrid(null);
             builder = Response.status(Response.Status.OK)
                     .entity(serializationStrategy.entity(grid))
                     .type(serializationStrategy.type(RepresentationType.LAYOUT));
diff --git a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/collectioncontents/ajaxtable/CollectionContentsAsAjaxTablePanel.java b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/collectioncontents/ajaxtable/CollectionContentsAsAjaxTablePanel.java
index 0ca73a4..d796a9e 100644
--- a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/collectioncontents/ajaxtable/CollectionContentsAsAjaxTablePanel.java
+++ b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/collectioncontents/ajaxtable/CollectionContentsAsAjaxTablePanel.java
@@ -48,6 +48,7 @@ import org.apache.isis.core.metamodel.spec.feature.ObjectAssociation;
 import org.apache.isis.viewer.wicket.model.common.OnConcurrencyExceptionHandler;
 import org.apache.isis.viewer.wicket.model.mementos.ObjectAdapterMemento;
 import org.apache.isis.viewer.wicket.model.models.EntityCollectionModel;
+import org.apache.isis.viewer.wicket.model.models.EntityModel;
 import org.apache.isis.viewer.wicket.ui.components.collection.bulk.BulkActionsProvider;
 import org.apache.isis.viewer.wicket.ui.components.collection.count.CollectionCountProvider;
 import org.apache.isis.viewer.wicket.ui.components.collectioncontents.ajaxtable.columns.ColumnAbstract;
@@ -167,7 +168,9 @@ public class CollectionContentsAsAjaxTablePanel
             // the facet should always exist, in fact
             // just enough to ask for the metadata.
             // This will cause the current ObjectSpec to be updated as a side effect.
-            final Grid unused = gridFacet.getGrid();
+            final EntityModel entityModel = getModel().getEntityModel();
+            final ObjectAdapter objectAdapterIfAny = entityModel != null ? entityModel.getObject() : null;
+            final Grid unused = gridFacet.getGrid(objectAdapterIfAny);
         }
 
         final Where whereContext =
diff --git a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/entity/selector/links/EntityLinksSelectorPanelFactory.java b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/entity/selector/links/EntityLinksSelectorPanelFactory.java
index 7aad81a..935542c 100644
--- a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/entity/selector/links/EntityLinksSelectorPanelFactory.java
+++ b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/entity/selector/links/EntityLinksSelectorPanelFactory.java
@@ -24,6 +24,7 @@ import org.apache.wicket.model.IModel;
 
 import org.apache.isis.applib.layout.grid.Grid;
 import org.apache.isis.applib.layout.grid.bootstrap3.BS3Grid;
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
 import org.apache.isis.core.metamodel.facets.object.grid.GridFacet;
 import org.apache.isis.core.metamodel.spec.ObjectSpecification;
 import org.apache.isis.viewer.wicket.model.models.EntityModel;
@@ -47,10 +48,11 @@ public class EntityLinksSelectorPanelFactory extends EntityComponentFactoryAbstr
     public Component createComponent(final String id, final IModel<?> model) {
         final EntityModel entityModel = (EntityModel) model;
 
+        final ObjectAdapter objectAdapter = entityModel.getObject();
         final ObjectSpecification specification = entityModel.getTypeOfSpecification();
         final GridFacet facet = specification.getFacet(GridFacet.class);
 
-        final Grid grid = facet.getGrid();
+        final Grid grid = facet.getGrid(objectAdapter);
         if (grid != null) {
             if(grid instanceof BS3Grid) {
                 final BS3Grid bs3Grid = (BS3Grid) grid;
diff --git a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/entity/EntityPage.java b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/entity/EntityPage.java
index d71b76b..6e52696 100644
--- a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/entity/EntityPage.java
+++ b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/entity/EntityPage.java
@@ -159,7 +159,7 @@ public class EntityPage extends PageAbstract {
             // the facet should always exist, in fact
             // just enough to ask for the metadata.
             // This will cause the current ObjectSpec to be updated as a side effect.
-            final Grid unused = gridFacet.getGrid();
+            final Grid unused = gridFacet.getGrid(objectAdapter);
         }
 
         if(titleString == null) {