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 2018/10/30 17:30:50 UTC

[isis] 01/02: Merge branch 'master' into v2

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

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

commit eb7310a04d1d046cf724ae43a544094f9315e96e
Merge: 3040c3f a3b13aa
Author: danhaywood <da...@haywood-associates.co.uk>
AuthorDate: Tue Oct 30 09:59:38 2018 +0000

    Merge branch 'master' into v2
    
    ISIS-2027 (health check)
    
    # Conflicts:
    #	core/applib/src/main/java/org/apache/isis/applib/services/wrapper/WrapperFactory.java
    #	example/application/simpleapp/webapp/src/main/webapp/WEB-INF/web.xml

 .../guides/rgsvc/_rgsvc_application-layer-spi.adoc |   8 ++
 ...c_application-layer-spi_HealthCheckService.adoc |  53 +++++++++++
 ...lication-layer-spi_HomePageProviderService.adoc |   4 +-
 .../main/asciidoc/guides/ugbtb/_ugbtb_web-xml.adoc |  79 +++++++++-------
 .../apache/isis/applib/services/health/Health.java |  39 ++++++++
 .../applib/services/health/HealthCheckService.java |  33 +++++++
 .../applib/services/wrapper/WrapperFactory.java    |  31 ++++++-
 .../commons/authentication/HealthAuthSession.java  |  44 +++++++++
 .../apache/isis/core/webapp/IsisSessionFilter.java |  22 ++++-
 .../core/webapp/modules/WebModule_RestEasy.java    |  18 ++--
 .../isis/core/wrapper/WrapperFactoryDefault.java   |   5 +
 .../handlers/DomainObjectInvocationHandler.java    |  63 +++++++++++--
 .../IsisSessionFilter_lookupPassThru_Test.java     |  80 ++++++++++++++++
 .../restfulobjects/applib/RepresentationType.java  |   6 +-
 .../restfulobjects/applib/RestfulMediaType.java    |   1 +
 .../applib/health/HealthRepresentation.java        |  40 ++++++++
 .../applib/health/HealthResource.java              |  44 +++++++++
 .../server/RestfulObjectsApplication.java          |   2 +
 .../server/resources/HealthReprRenderer.java       |  54 +++++++++++
 .../server/resources/HealthResourceServerside.java | 102 +++++++++++++++++++++
 .../application/isis-non-changing.properties       |   1 -
 .../src/main/webapp/WEB-INF/isis.properties        |   5 +-
 .../services/health/HealthCheckServiceImpl.java    |  29 ++++++
 .../manifest/persistor_datanucleus.properties      |   1 -
 ...tion.services.health.HealthCheckServiceImpl.xml |  45 +++++++++
 .../modules/simple/dom/impl/SimpleObjects.java     |   8 ++
 .../webapp/src/main/webapp/WEB-INF/isis.properties |  37 ++++----
 27 files changed, 770 insertions(+), 84 deletions(-)

diff --cc core/applib/src/main/java/org/apache/isis/applib/services/wrapper/WrapperFactory.java
index e38d253,2bd8a21..c7f1b26
--- a/core/applib/src/main/java/org/apache/isis/applib/services/wrapper/WrapperFactory.java
+++ b/core/applib/src/main/java/org/apache/isis/applib/services/wrapper/WrapperFactory.java
@@@ -112,24 -119,12 +121,29 @@@ public interface WrapperFactory 
              return domainObject;
          }
  
 +        @Override public <T> T w(final T domainObject) {
 +            return wrap(domainObject);
 +        }
 +
 +        @Inject
 +        FactoryService factoryService;
 +
 +        @Override
 +        public <T> T wm(final Class<T> mixinClass, final Object mixedIn) {
 +            return wrapMixin(mixinClass, mixedIn);
 +        }
 +
 +        @Override
 +        public <T> T wrapMixin(final Class<T> mixinClass, final Object mixedIn) {
 +            return wrap(factoryService.m(mixinClass, mixedIn));
 +        }
 +
          @Override
+         public <T> T wrapTry(T domainObject) {
+             return domainObject;
+         }
 -        
++
+         @Override
          public <T> T wrapNoExecute(T domainObject) {
              return domainObject;
          }
@@@ -185,24 -180,13 +199,31 @@@
      <T> T wrap(T domainObject);
  
      /**
 +     * Alias for {@link #wrap(Object)}.
 +     */
 +    @Programmatic
 +    <T> T w(T domainObject);
 +
 +    /**
 +     * Alias for {@link #wrapMixin(Class, Object)}
 +     */
 +    @Programmatic
 +    <T> T wm(Class<T> mixinClass, Object mixedIn);
 +
 +    /**
 +     * {@link #wrap(Object) wraps} a {@link FactoryService#mixin(Class, Object) mixin}.
 +     */
 +    @Programmatic
 +    <T> T wrapMixin(Class<T> mixinClass, Object mixedIn);
 +
 +    /**
+      * Convenience method for {@link #wrap(Object, ExecutionMode)} with {@link ExecutionMode#TRY},
+      * to make this feature more discoverable.
+      */
+     @Programmatic
+     <T> T wrapTry(T domainObject);
+     
+     /**
       * Convenience method for {@link #wrap(Object, ExecutionMode)} with {@link ExecutionMode#NO_EXECUTE},
       * to make this feature more discoverable.
       */
diff --cc core/runtime/src/main/java/org/apache/isis/core/webapp/modules/WebModule_RestEasy.java
index cf22749,0000000..c9465e4
mode 100644,000000..100644
--- a/core/runtime/src/main/java/org/apache/isis/core/webapp/modules/WebModule_RestEasy.java
+++ b/core/runtime/src/main/java/org/apache/isis/core/webapp/modules/WebModule_RestEasy.java
@@@ -1,163 -1,0 +1,163 @@@
 +/*
 + *  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.webapp.modules;
 +
- import static java.util.Objects.requireNonNull;
- import static org.apache.isis.commons.internal.base._Casts.uncheckedCast;
- import static org.apache.isis.commons.internal.base._Strings.prefix;
- import static org.apache.isis.commons.internal.base._Strings.suffix;
- import static org.apache.isis.commons.internal.context._Context.getDefaultClassLoader;
- import static org.apache.isis.commons.internal.exceptions._Exceptions.unexpectedCodeReach;
- import static org.apache.isis.commons.internal.resources._Resources.putRestfulPath;
- 
 +import javax.servlet.FilterRegistration.Dynamic;
 +import javax.servlet.ServletContext;
 +import javax.servlet.ServletContextListener;
 +import javax.servlet.ServletException;
 +
 +import org.apache.isis.core.webapp.IsisSessionFilter;
 +import org.apache.isis.core.webapp.IsisWebAppConfigProvider;
 +
++import static java.util.Objects.requireNonNull;
++import static org.apache.isis.commons.internal.base._Casts.uncheckedCast;
++import static org.apache.isis.commons.internal.base._Strings.prefix;
++import static org.apache.isis.commons.internal.base._Strings.suffix;
++import static org.apache.isis.commons.internal.context._Context.getDefaultClassLoader;
++import static org.apache.isis.commons.internal.exceptions._Exceptions.unexpectedCodeReach;
++import static org.apache.isis.commons.internal.resources._Resources.putRestfulPath;
++
 +/**
 + * Package private mixin for WebModule implementing WebModule.
 + * 
 + * @since 2.0.0
 + */
 +final class WebModule_RestEasy implements WebModule  {
 +    
 +    public static final String KEY_RESTFUL_BASE_PATH = "isis.viewer.restfulobjects.basePath";
 +    public static final String KEY_RESTFUL_BASE_PATH_DEFAULT = "/restful";
 +    
 +    private final static String RESTEASY_BOOTSTRAPPER = 
 +            "org.jboss.resteasy.plugins.server.servlet.ResteasyBootstrap";
 +    
 +    private final static String RESTEASY_DISPATCHER = "RestfulObjectsRestEasyDispatcher";
 +    
 +    String restfulPathConfigValue;
 +    
 +    @Override
 +    public String getName() {
 +        return "RestEasy";
 +    }
 +    
 +    @Override
 +    public void prepare(ServletContext ctx) {
 +        
 +        if(!isApplicable(ctx)) {
 +            return;
 +        }
 +        
 +        // try to fetch restfulPath from config else fallback to default
 +        final String restfulPath = 
 +                IsisWebAppConfigProvider.getInstance()
 +                .peekAtOrDefault(ctx, KEY_RESTFUL_BASE_PATH, KEY_RESTFUL_BASE_PATH_DEFAULT);
 +                
 +        putRestfulPath(restfulPath);
 +        
 +        this.restfulPathConfigValue = restfulPath; // store locally for reuse
 +        
 +        // register this module as a viewer
 +        ContextUtil.registerViewer(ctx, "restfulobjects");
 +        ContextUtil.registerProtectedPath(ctx, suffix(prefix(restfulPath, "/"), "/") + "*" );
 +    }
 +
 +    @Override
 +    public ServletContextListener init(ServletContext ctx) throws ServletException {
 +
 +        // add IsisSessionFilters
 +        
 +        {
 +            final Dynamic filter = ctx.addFilter("IsisSessionFilterForRestfulObjects", IsisSessionFilter.class);
 +
 +            // this is mapped to the entire application; 
 +            // however the IsisSessionFilter will 
 +            // "notice" if the session filter has already been
 +            // executed for the request pipeline, and if so will do nothing
 +            filter.addMappingForServletNames(null, true, RESTEASY_DISPATCHER); 
 +            
 +            filter.setInitParameter(
 +                    "authenticationSessionStrategy", 
 +                    "org.apache.isis.viewer.restfulobjects.server.authentication.AuthenticationSessionStrategyBasicAuth");
 +            filter.setInitParameter(
 +                    "whenNoSession", // what to do if no session was found ...
 +                    "auto"); // ... 401 and a basic authentication challenge if request originates from web browser
 +            filter.setInitParameter(
 +                    "passThru", 
-                     getRestfulPath()+"swagger"); 
++                    String.join(",", getRestfulPath()+"swagger", getRestfulPath()+"health"));
 +            
 +        }
 +        
 +        {
 +            final Dynamic filter = ctx.addFilter("RestfulObjectsRestEasyDispatcher", 
 +                    "org.apache.isis.viewer.restfulobjects.server.webapp.IsisTransactionFilterForRestfulObjects");
 +            filter.addMappingForServletNames(null, true, RESTEASY_DISPATCHER); 
 +        }
 +        
 +        
 +
 +        // add RestEasy
 +        
 +        // used by RestEasy to determine the JAX-RS resources and other related configuration
 +        ctx.setInitParameter(
 +                "javax.ws.rs.Application", 
 +                "org.apache.isis.viewer.restfulobjects.server.RestfulObjectsApplication");
 +        
 +        ctx.setInitParameter("resteasy.servlet.mapping.prefix", getRestfulPath());
 +        
 +        ctx.addServlet(RESTEASY_DISPATCHER, 
 +                "org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher");
 +        ctx.getServletRegistration(RESTEASY_DISPATCHER)
 +        .addMapping(getUrlPattern());
 +        
 +        try {
 +            final Class<?> listenerClass = getDefaultClassLoader().loadClass(RESTEASY_BOOTSTRAPPER);
 +            return ctx.createListener(uncheckedCast(listenerClass));
 +        } catch (ClassNotFoundException e) {
 +            // guarded against by isAvailable()
 +            throw unexpectedCodeReach();
 +        }
 +
 +    }
 +
 +    @Override
 +    public boolean isApplicable(ServletContext ctx) {
 +        try {
 +            getDefaultClassLoader().loadClass(RESTEASY_BOOTSTRAPPER);
 +            return true;
 +        } catch (Exception e) {
 +            return false;
 +        }
 +    }
 +    
 +    // -- HELPER
 +    
 +    private String getUrlPattern() {
 +        return getRestfulPath() + "*";
 +    }
 +
 +    private String getRestfulPath() {
 +        requireNonNull(restfulPathConfigValue, "This web-module needs to be prepared first.");
 +        final String restfulPathEnclosedWithSlashes = suffix(prefix(restfulPathConfigValue, "/"), "/");
 +        return restfulPathEnclosedWithSlashes;
 +    }
 +    
 +    
 +
 +    
 +}
diff --cc core/runtime/src/main/java/org/apache/isis/core/wrapper/WrapperFactoryDefault.java
index b56e470,0000000..75e15d2
mode 100644,000000..100644
--- a/core/runtime/src/main/java/org/apache/isis/core/wrapper/WrapperFactoryDefault.java
+++ b/core/runtime/src/main/java/org/apache/isis/core/wrapper/WrapperFactoryDefault.java
@@@ -1,333 -1,0 +1,338 @@@
 +/*
 + *  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.wrapper;
 +
 +
 +import java.util.ArrayList;
 +import java.util.HashMap;
 +import java.util.List;
 +import java.util.Map;
 +
 +import javax.inject.Inject;
 +
 +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.services.factory.FactoryService;
 +import org.apache.isis.applib.services.wrapper.events.ActionArgumentEvent;
 +import org.apache.isis.applib.services.wrapper.events.ActionInvocationEvent;
 +import org.apache.isis.applib.services.wrapper.events.ActionUsabilityEvent;
 +import org.apache.isis.applib.services.wrapper.events.ActionVisibilityEvent;
 +import org.apache.isis.applib.services.wrapper.events.CollectionAccessEvent;
 +import org.apache.isis.applib.services.wrapper.events.CollectionAddToEvent;
 +import org.apache.isis.applib.services.wrapper.events.CollectionMethodEvent;
 +import org.apache.isis.applib.services.wrapper.events.CollectionRemoveFromEvent;
 +import org.apache.isis.applib.services.wrapper.events.CollectionUsabilityEvent;
 +import org.apache.isis.applib.services.wrapper.events.CollectionVisibilityEvent;
 +import org.apache.isis.applib.services.wrapper.events.InteractionEvent;
 +import org.apache.isis.applib.services.wrapper.events.ObjectTitleEvent;
 +import org.apache.isis.applib.services.wrapper.events.ObjectValidityEvent;
 +import org.apache.isis.applib.services.wrapper.events.PropertyAccessEvent;
 +import org.apache.isis.applib.services.wrapper.events.PropertyModifyEvent;
 +import org.apache.isis.applib.services.wrapper.events.PropertyUsabilityEvent;
 +import org.apache.isis.applib.services.wrapper.events.PropertyVisibilityEvent;
 +import org.apache.isis.applib.services.wrapper.WrapperFactory;
 +import org.apache.isis.applib.services.wrapper.WrappingObject;
 +import org.apache.isis.applib.services.wrapper.listeners.InteractionListener;
 +import org.apache.isis.commons.internal.base._Casts;
 +import org.apache.isis.core.commons.authentication.AuthenticationSessionProvider;
 +import org.apache.isis.core.metamodel.services.persistsession.PersistenceSessionServiceInternal;
 +import org.apache.isis.core.runtime.system.session.IsisSessionFactory;
 +import org.apache.isis.core.wrapper.dispatchers.InteractionEventDispatcher;
 +import org.apache.isis.core.wrapper.dispatchers.InteractionEventDispatcherTypeSafe;
 +import org.apache.isis.core.wrapper.handlers.ProxyContextHandler;
 +import org.apache.isis.core.wrapper.proxy.ProxyCreator;
 +
 +/**
 + * This service provides the ability to &quot;wrap&quot; of a domain object such that it can
 + * be interacted with while enforcing the hide/disable/validate rules implies by
 + * the Isis programming model.
 + *
 + * <p>
 + * This implementation has no UI-visible actions and is the supported implementation, so it is annotated with
 + * {@link org.apache.isis.applib.annotation.DomainService}.   This means that by including
 + * <tt>o.a.i.core:isis-core-wrapper</tt> on the classpath, the service is automatically registered; no further
 + * configuration is required.
 + */
 +@DomainService(
 +        nature = NatureOfService.DOMAIN,
 +        menuOrder = "" + Integer.MAX_VALUE
 +        )
 +public class WrapperFactoryDefault implements WrapperFactory {
 +
 +    private final List<InteractionListener> listeners = new ArrayList<InteractionListener>();
 +    private final Map<Class<? extends InteractionEvent>, InteractionEventDispatcher> dispatchersByEventClass = new HashMap<Class<? extends InteractionEvent>, InteractionEventDispatcher>();
 +
 +
 +    private final ProxyContextHandler proxyContextHandler;
 +
 +    public WrapperFactoryDefault() {
 +        this(new ProxyCreator());
 +    }
 +    WrapperFactoryDefault(final ProxyCreator proxyCreator) {
 +
 +        proxyContextHandler = new ProxyContextHandler(proxyCreator);
 +
 +        dispatchersByEventClass.put(ObjectTitleEvent.class, new InteractionEventDispatcherTypeSafe<ObjectTitleEvent>() {
 +            @Override
 +            public void dispatchTypeSafe(final ObjectTitleEvent interactionEvent) {
 +                for (final InteractionListener l : getListeners()) {
 +                    l.objectTitleRead(interactionEvent);
 +                }
 +            }
 +        });
 +        dispatchersByEventClass.put(PropertyVisibilityEvent.class, new InteractionEventDispatcherTypeSafe<PropertyVisibilityEvent>() {
 +            @Override
 +            public void dispatchTypeSafe(final PropertyVisibilityEvent interactionEvent) {
 +                for (final InteractionListener l : getListeners()) {
 +                    l.propertyVisible(interactionEvent);
 +                }
 +            }
 +        });
 +        dispatchersByEventClass.put(PropertyUsabilityEvent.class, new InteractionEventDispatcherTypeSafe<PropertyUsabilityEvent>() {
 +            @Override
 +            public void dispatchTypeSafe(final PropertyUsabilityEvent interactionEvent) {
 +                for (final InteractionListener l : getListeners()) {
 +                    l.propertyUsable(interactionEvent);
 +                }
 +            }
 +        });
 +        dispatchersByEventClass.put(PropertyAccessEvent.class, new InteractionEventDispatcherTypeSafe<PropertyAccessEvent>() {
 +            @Override
 +            public void dispatchTypeSafe(final PropertyAccessEvent interactionEvent) {
 +                for (final InteractionListener l : getListeners()) {
 +                    l.propertyAccessed(interactionEvent);
 +                }
 +            }
 +        });
 +        dispatchersByEventClass.put(PropertyModifyEvent.class, new InteractionEventDispatcherTypeSafe<PropertyModifyEvent>() {
 +            @Override
 +            public void dispatchTypeSafe(final PropertyModifyEvent interactionEvent) {
 +                for (final InteractionListener l : getListeners()) {
 +                    l.propertyModified(interactionEvent);
 +                }
 +            }
 +        });
 +        dispatchersByEventClass.put(CollectionVisibilityEvent.class, new InteractionEventDispatcherTypeSafe<CollectionVisibilityEvent>() {
 +            @Override
 +            public void dispatchTypeSafe(final CollectionVisibilityEvent interactionEvent) {
 +                for (final InteractionListener l : getListeners()) {
 +                    l.collectionVisible(interactionEvent);
 +                }
 +            }
 +        });
 +        dispatchersByEventClass.put(CollectionUsabilityEvent.class, new InteractionEventDispatcherTypeSafe<CollectionUsabilityEvent>() {
 +            @Override
 +            public void dispatchTypeSafe(final CollectionUsabilityEvent interactionEvent) {
 +                for (final InteractionListener l : getListeners()) {
 +                    l.collectionUsable(interactionEvent);
 +                }
 +            }
 +        });
 +        dispatchersByEventClass.put(CollectionAccessEvent.class, new InteractionEventDispatcherTypeSafe<CollectionAccessEvent>() {
 +            @Override
 +            public void dispatchTypeSafe(final CollectionAccessEvent interactionEvent) {
 +                for (final InteractionListener l : getListeners()) {
 +                    l.collectionAccessed(interactionEvent);
 +                }
 +            }
 +        });
 +        dispatchersByEventClass.put(CollectionAddToEvent.class, new InteractionEventDispatcherTypeSafe<CollectionAddToEvent>() {
 +            @Override
 +            public void dispatchTypeSafe(final CollectionAddToEvent interactionEvent) {
 +                for (final InteractionListener l : getListeners()) {
 +                    l.collectionAddedTo(interactionEvent);
 +                }
 +            }
 +        });
 +        dispatchersByEventClass.put(CollectionRemoveFromEvent.class, new InteractionEventDispatcherTypeSafe<CollectionRemoveFromEvent>() {
 +            @Override
 +            public void dispatchTypeSafe(final CollectionRemoveFromEvent interactionEvent) {
 +                for (final InteractionListener l : getListeners()) {
 +                    l.collectionRemovedFrom(interactionEvent);
 +                }
 +            }
 +        });
 +        dispatchersByEventClass.put(ActionVisibilityEvent.class, new InteractionEventDispatcherTypeSafe<ActionVisibilityEvent>() {
 +            @Override
 +            public void dispatchTypeSafe(final ActionVisibilityEvent interactionEvent) {
 +                for (final InteractionListener l : getListeners()) {
 +                    l.actionVisible(interactionEvent);
 +                }
 +            }
 +        });
 +        dispatchersByEventClass.put(ActionUsabilityEvent.class, new InteractionEventDispatcherTypeSafe<ActionUsabilityEvent>() {
 +            @Override
 +            public void dispatchTypeSafe(final ActionUsabilityEvent interactionEvent) {
 +                for (final InteractionListener l : getListeners()) {
 +                    l.actionUsable(interactionEvent);
 +                }
 +            }
 +        });
 +        dispatchersByEventClass.put(ActionArgumentEvent.class, new InteractionEventDispatcherTypeSafe<ActionArgumentEvent>() {
 +            @Override
 +            public void dispatchTypeSafe(final ActionArgumentEvent interactionEvent) {
 +                for (final InteractionListener l : getListeners()) {
 +                    l.actionArgument(interactionEvent);
 +                }
 +            }
 +        });
 +        dispatchersByEventClass.put(ActionInvocationEvent.class, new InteractionEventDispatcherTypeSafe<ActionInvocationEvent>() {
 +            @Override
 +            public void dispatchTypeSafe(final ActionInvocationEvent interactionEvent) {
 +                for (final InteractionListener l : getListeners()) {
 +                    l.actionInvoked(interactionEvent);
 +                }
 +            }
 +        });
 +        dispatchersByEventClass.put(ObjectValidityEvent.class, new InteractionEventDispatcherTypeSafe<ObjectValidityEvent>() {
 +            @Override
 +            public void dispatchTypeSafe(final ObjectValidityEvent interactionEvent) {
 +                for (final InteractionListener l : getListeners()) {
 +                    l.objectPersisted(interactionEvent);
 +                }
 +            }
 +        });
 +        dispatchersByEventClass.put(CollectionMethodEvent.class, new InteractionEventDispatcherTypeSafe<CollectionMethodEvent>() {
 +            @Override
 +            public void dispatchTypeSafe(final CollectionMethodEvent interactionEvent) {
 +                for (final InteractionListener l : getListeners()) {
 +                    l.collectionMethodInvoked(interactionEvent);
 +                }
 +            }
 +        });
 +    }
 +
 +    // /////////////////////////////////////////////////////////////
 +    // wrap and unwrap
 +    // /////////////////////////////////////////////////////////////
 +
 +    public <T> T wm(final Class<T> mixinClass, final Object mixedIn) {
 +        return w(factoryService.m(mixinClass, mixedIn));
 +    }
 +
 +    public <T> T wrapMixin(final Class<T> mixinClass, final Object mixedIn) {
 +        return w(factoryService.m(mixinClass, mixedIn));
 +    }
 +
 +    @Override
 +    public <T> T w(final T domainObject) {
 +        return wrap(domainObject, ExecutionMode.EXECUTE);
 +    }
 +
 +    @Override
 +    public <T> T wrap(final T domainObject) {
 +        return wrap(domainObject, ExecutionMode.EXECUTE);
 +    }
 +
 +    @Override
++    public <T> T wrapTry(final T domainObject) {
++        return wrap(domainObject, ExecutionMode.TRY);
++    }
++
++    @Override
 +    public <T> T wrapNoExecute(final T domainObject) {
 +        return wrap(domainObject, ExecutionMode.NO_EXECUTE);
 +    }
 +
 +    @Override
 +    public <T> T wrapSkipRules(final T domainObject) {
 +        return wrap(domainObject, ExecutionMode.SKIP_RULES);
 +    }
 +
 +    @Override
 +    public <T> T wrap(final T domainObject, final ExecutionMode mode) {
 +        if (domainObject instanceof WrappingObject) {
 +            final WrappingObject wrapperObject = (WrappingObject) domainObject;
 +            final ExecutionMode wrapperMode = wrapperObject.__isis_executionMode();
 +            if(wrapperMode != mode) {
 +                final Object underlyingDomainObject = wrapperObject.__isis_wrapped();
 +                return _Casts.uncheckedCast(createProxy(underlyingDomainObject, mode, isisSessionFactory));
 +            }
 +            return domainObject;
 +        }
 +        return createProxy(domainObject, mode, isisSessionFactory);
 +    }
 +
 +    protected <T> T createProxy(
 +            final T domainObject,
 +            final ExecutionMode mode,
 +            final IsisSessionFactory isisSessionFactory) {
 +        return proxyContextHandler.proxy(domainObject, mode, isisSessionFactory);
 +    }
 +
 +    @Override
 +    public boolean isWrapper(final Object possibleWrappedDomainObject) {
 +        return possibleWrappedDomainObject instanceof WrappingObject;
 +    }
 +
 +    @Override
 +    @SuppressWarnings("unchecked")
 +    @Programmatic
 +    public <T> T unwrap(T possibleWrappedDomainObject) {
 +        if(isWrapper(possibleWrappedDomainObject)) {
 +            final WrappingObject wrappingObject = (WrappingObject) possibleWrappedDomainObject;
 +            return (T) wrappingObject.__isis_wrapped();
 +        }
 +        return possibleWrappedDomainObject;
 +    }
 +
 +    // /////////////////////////////////////////////////////////////
 +    // Listeners
 +    // /////////////////////////////////////////////////////////////
 +
 +    @Override
 +    public List<InteractionListener> getListeners() {
 +        return listeners;
 +    }
 +
 +    @Override
 +    public boolean addInteractionListener(final InteractionListener listener) {
 +        return listeners.add(listener);
 +    }
 +
 +    @Override
 +    public boolean removeInteractionListener(final InteractionListener listener) {
 +        return listeners.remove(listener);
 +    }
 +
 +    @Override
 +    public void notifyListeners(final InteractionEvent interactionEvent) {
 +        final InteractionEventDispatcher dispatcher = dispatchersByEventClass.get(interactionEvent.getClass());
 +        if (dispatcher == null) {
 +            throw new RuntimeException("Unknown InteractionEvent - register into dispatchers map");
 +        }
 +        dispatcher.dispatch(interactionEvent);
 +    }
 +
 +
 +    @Inject
 +    AuthenticationSessionProvider authenticationSessionProvider;
 +
 +    @Inject
 +    PersistenceSessionServiceInternal persistenceSessionServiceInternal;
 +
 +    @Inject
 +    IsisSessionFactory isisSessionFactory;
 +
 +    @Inject
 +    FactoryService factoryService;
 +
 +}
diff --cc core/runtime/src/main/java/org/apache/isis/core/wrapper/handlers/DomainObjectInvocationHandler.java
index 8666ded,0000000..5e6e808
mode 100644,000000..100644
--- a/core/runtime/src/main/java/org/apache/isis/core/wrapper/handlers/DomainObjectInvocationHandler.java
+++ b/core/runtime/src/main/java/org/apache/isis/core/wrapper/handlers/DomainObjectInvocationHandler.java
@@@ -1,848 -1,0 +1,897 @@@
 +/*
 + *  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.wrapper.handlers;
 +
 +import java.lang.reflect.InvocationTargetException;
 +import java.lang.reflect.Method;
 +import java.util.Collection;
 +import java.util.List;
 +import java.util.Map;
 +import java.util.Optional;
 +import java.util.Set;
 +import java.util.stream.Stream;
 +
 +import org.apache.isis.commons.internal.collections._Lists;
 +import org.apache.isis.commons.internal.collections._Sets;
 +
 +import org.apache.isis.applib.annotation.Where;
 +import org.apache.isis.applib.services.wrapper.DisabledException;
 +import org.apache.isis.applib.services.wrapper.HiddenException;
 +import org.apache.isis.applib.services.wrapper.InteractionException;
 +import org.apache.isis.applib.services.wrapper.InvalidException;
 +import org.apache.isis.applib.services.wrapper.WrapperFactory.ExecutionMode;
 +import org.apache.isis.applib.services.wrapper.WrappingObject;
 +import org.apache.isis.applib.services.wrapper.events.CollectionAccessEvent;
 +import org.apache.isis.applib.services.wrapper.events.InteractionEvent;
 +import org.apache.isis.applib.services.wrapper.events.ObjectTitleEvent;
 +import org.apache.isis.applib.services.wrapper.events.PropertyAccessEvent;
 +import org.apache.isis.applib.services.wrapper.events.UsabilityEvent;
 +import org.apache.isis.applib.services.wrapper.events.ValidityEvent;
 +import org.apache.isis.applib.services.wrapper.events.VisibilityEvent;
 +import org.apache.isis.commons.internal.base._NullSafe;
 +import org.apache.isis.core.commons.authentication.AuthenticationSession;
 +import org.apache.isis.core.commons.authentication.AuthenticationSessionProvider;
 +import org.apache.isis.core.metamodel.IsisJdoMetamodelPlugin;
 +import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
 +import org.apache.isis.core.metamodel.adapter.ObjectAdapterProvider;
 +import org.apache.isis.core.metamodel.consent.Consent;
 +import org.apache.isis.core.metamodel.consent.InteractionInitiatedBy;
 +import org.apache.isis.core.metamodel.consent.InteractionResult;
 +import org.apache.isis.core.metamodel.facets.ImperativeFacet;
 +import org.apache.isis.core.metamodel.facets.ImperativeFacet.Intent;
 +import org.apache.isis.core.metamodel.facets.object.mixin.MixinFacet;
 +import org.apache.isis.core.metamodel.interactions.ObjectTitleContext;
 +import org.apache.isis.core.metamodel.services.ServicesInjector;
 +import org.apache.isis.core.metamodel.services.persistsession.PersistenceSessionServiceInternal;
 +import org.apache.isis.core.metamodel.spec.ObjectSpecification;
 +import org.apache.isis.core.metamodel.spec.feature.Contributed;
 +import org.apache.isis.core.metamodel.spec.feature.ObjectAction;
 +import org.apache.isis.core.metamodel.spec.feature.ObjectAssociation;
 +import org.apache.isis.core.metamodel.spec.feature.ObjectMember;
 +import org.apache.isis.core.metamodel.spec.feature.OneToManyAssociation;
 +import org.apache.isis.core.metamodel.spec.feature.OneToOneAssociation;
 +import org.apache.isis.core.metamodel.specloader.SpecificationLoader;
 +import org.apache.isis.core.metamodel.specloader.specimpl.ContributeeMember;
 +import org.apache.isis.core.metamodel.specloader.specimpl.ObjectActionContributee;
 +import org.apache.isis.core.metamodel.specloader.specimpl.ObjectActionMixedIn;
 +import org.apache.isis.core.metamodel.specloader.specimpl.dflt.ObjectSpecificationDefault;
 +import org.apache.isis.core.runtime.system.session.IsisSessionFactory;
 +
 +public class DomainObjectInvocationHandler<T> extends DelegatingInvocationHandlerDefault<T> {
 +
 +    private final AuthenticationSessionProvider authenticationSessionProvider;
 +    private final PersistenceSessionServiceInternal persistenceSessionServiceInternal;
 +
 +    private final ProxyContextHandler proxy;
 +    private final ExecutionMode executionMode;
 +    private final IsisSessionFactory isisSessionFactory;
 +
 +    /**
 +     * The <tt>title()</tt> method; may be <tt>null</tt>.
 +     */
 +    protected Method titleMethod;
 +
 +    /**
 +     * The <tt>__isis_save()</tt> method from {@link WrappingObject#__isis_save()}.
 +     */
 +    protected Method __isis_saveMethod;
 +
 +    /**
 +     * The <tt>__isis_wrapped()</tt> method from {@link WrappingObject#__isis_wrapped()}.
 +     */
 +    protected Method __isis_wrappedMethod;
 +
 +    /**
 +     * The <tt>__isis_executionMode()</tt> method from {@link WrappingObject#__isis_executionMode()}.
 +     */
 +    protected Method __isis_executionMode;
 +
 +    protected final Set<String> jdoMethodsProvidedByEnhancement = _Sets.newHashSet();
 +
 +    public DomainObjectInvocationHandler(
 +            final T delegate,
 +            final ExecutionMode mode,
 +            final ProxyContextHandler proxy,
 +            final IsisSessionFactory isisSessionFactory) {
 +        super(delegate, mode, isisSessionFactory);
 +
 +        this.proxy = proxy;
 +        this.executionMode = mode;
 +        this.isisSessionFactory = isisSessionFactory;
 +
 +        final ServicesInjector servicesInjector = isisSessionFactory.getServicesInjector();
 +
 +        this.authenticationSessionProvider = servicesInjector.getAuthenticationSessionProvider();
 +        //this.specificationLoader = servicesInjector.getSpecificationLoader();
 +        this.persistenceSessionServiceInternal = servicesInjector.getPersistenceSessionServiceInternal();
 +
 +
 +        try {
 +            titleMethod = delegate.getClass().getMethod("title", new Class[]{});
 +        } catch (final NoSuchMethodException e) {
 +            // ignore
 +        }
 +        try {
 +            __isis_saveMethod = WrappingObject.class.getMethod("__isis_save", new Class[]{});
 +            __isis_wrappedMethod = WrappingObject.class.getMethod("__isis_wrapped", new Class[]{});
 +            __isis_executionMode = WrappingObject.class.getMethod("__isis_executionMode", new Class[]{});
 +
 +            _NullSafe.stream(IsisJdoMetamodelPlugin.get().getMethodsProvidedByEnhancement())
 +            .map(Method::getName)
 +            .forEach(jdoMethodsProvidedByEnhancement::add);
 +
 +            // legacy of ...
 +            //            dnPersistableMethods.addAll(
 +            //                    _Lists.newArrayList(
 +            //                            Iterables.transform(
 +            //                                    Arrays.asList(Persistable.class.getDeclaredMethods()),
 +            //                                    new Function<Method, String>() {
 +            //                                        @Override
 +            //                                        public String apply(final Method input) {
 +            //                                            return input.getName();
 +            //                                        }
 +            //                                    })));
 +
 +        } catch (final NoSuchMethodException nsme) {
 +            throw new IllegalStateException(
 +                    "Could not locate reserved declared methods in the WrappingObject and WrappedObject interfaces",
 +                    nsme);
 +        }
 +    }
 +
 +    @Override
 +    public Object invoke(final Object proxyObject, final Method method, final Object[] args) throws Throwable {
 +
 +        if (isObjectMethod(method)) {
 +            return delegate(method, args);
 +        }
 +
 +        if(isJdoMethod(method)) {
 +            return delegate(method, args);
 +        }
 +
 +        if(isInjectMethod(method)) {
 +            return delegate(method, args);
 +        }
 +
 +        final ObjectAdapter targetAdapter = adapterFor(getDelegate());
 +
 +        if (isTitleMethod(method)) {
 +            return handleTitleMethod(targetAdapter);
 +        }
 +
 +
 +        final ObjectSpecification targetNoSpec = targetAdapter.getSpecification();
 +
 +        // save method, through the proxy
 +        if (isSaveMethod(method)) {
 +            return handleSaveMethod(targetAdapter, targetNoSpec);
 +        }
 +
 +        if (isWrappedMethod(method)) {
 +            return getDelegate();
 +        }
 +
 +        if (isExecutionModeMethod(method)) {
 +            return executionMode;
 +        }
 +
 +        final ObjectMember objectMember = locateAndCheckMember(method);
 +        final ContributeeMember contributeeMember = determineIfContributed(args, objectMember);
 +
 +        final String memberName = objectMember.getName();
 +
 +        final Intent intent = ImperativeFacet.Util.getIntent(objectMember, method);
 +        if(intent == Intent.CHECK_IF_HIDDEN || intent == Intent.CHECK_IF_DISABLED) {
 +            throw new UnsupportedOperationException(String.format("Cannot invoke supporting method '%s'", memberName));
 +        }
 +
 +        if (intent == Intent.DEFAULTS || intent == Intent.CHOICES_OR_AUTOCOMPLETE) {
 +            return method.invoke(getDelegate(), args);
 +        }
 +
 +        if (objectMember.isOneToOneAssociation()) {
 +
 +            if (intent == Intent.CHECK_IF_VALID || intent == Intent.MODIFY_PROPERTY_SUPPORTING) {
 +                throw new UnsupportedOperationException(String.format("Cannot invoke supporting method for '%s'; use only property accessor/mutator", memberName));
 +            }
 +
 +            final OneToOneAssociation otoa = (OneToOneAssociation) objectMember;
 +
 +            if (intent == Intent.ACCESSOR) {
 +                return handleGetterMethodOnProperty(targetAdapter, args, otoa);
 +            }
 +
 +            if (intent == Intent.MODIFY_PROPERTY || intent == Intent.INITIALIZATION) {
 +                return handleSetterMethodOnProperty(targetAdapter, args, otoa);
 +            }
 +        }
 +        if (objectMember.isOneToManyAssociation()) {
 +
 +            if (intent == Intent.CHECK_IF_VALID) {
 +                throw new UnsupportedOperationException(String.format("Cannot invoke supporting method '%s'; use only collection accessor/mutator", memberName));
 +            }
 +
 +            final OneToManyAssociation otma = (OneToManyAssociation) objectMember;
 +            if (intent == Intent.ACCESSOR) {
 +                return handleGetterMethodOnCollection(targetAdapter, args, otma, method, memberName);
 +            }
 +            if (intent == Intent.MODIFY_COLLECTION_ADD) {
 +                return handleCollectionAddToMethod(targetAdapter, args, otma);
 +            }
 +            if (intent == Intent.MODIFY_COLLECTION_REMOVE) {
 +                return handleCollectionRemoveFromMethod(targetAdapter, args, otma);
 +            }
 +        }
 +
 +        if (objectMember instanceof ObjectAction) {
 +
 +            if (intent == Intent.CHECK_IF_VALID) {
 +                throw new UnsupportedOperationException(String.format("Cannot invoke supporting method '%s'; use only the 'invoke' method", memberName));
 +            }
 +
 +            final ObjectAction objectAction = (ObjectAction) objectMember;
 +
 +            ObjectAction actualObjectAction;
 +            ObjectAdapter actualTargetAdapter;
 +
 +            final MixinFacet mixinFacet = targetAdapter.getSpecification().getFacet(MixinFacet.class);
 +            if(mixinFacet != null) {
 +
 +                // rather than invoke on a (transient) mixin, instead try to
 +                // figure out the corresponding ObjectActionMixedIn
 +                actualTargetAdapter = mixinFacet.mixedIn(targetAdapter, MixinFacet.Policy.IGNORE_FAILURES);
 +                actualObjectAction = determineMixinAction(actualTargetAdapter, objectAction);
 +
 +                if(actualTargetAdapter == null || actualObjectAction == null) {
 +                    // revert to original behaviour
 +                    actualTargetAdapter = targetAdapter;
 +                    actualObjectAction = objectAction;
 +                }
 +            } else {
 +                actualTargetAdapter = targetAdapter;
 +                actualObjectAction = objectAction;
 +            }
 +
 +            return handleActionMethod(actualTargetAdapter, args, actualObjectAction, contributeeMember);
 +        }
 +
 +        throw new UnsupportedOperationException(String.format("Unknown member type '%s'", objectMember));
 +    }
 +
 +    private static ObjectAction determineMixinAction(
 +            final ObjectAdapter domainObjectAdapter,
 +            final ObjectAction objectAction) {
 +        if(domainObjectAdapter == null) {
 +            return null;
 +        }
 +        final ObjectSpecification specification = domainObjectAdapter.getSpecification();
 +        final Stream<ObjectAction> objectActions = specification.streamObjectActions(Contributed.INCLUDED);
 +        
 +        return objectActions
 +            .filter(action->action instanceof ObjectActionMixedIn)
 +            .map(action->(ObjectActionMixedIn) action)
 +            .filter(mixedInAction->mixedInAction.hasMixinAction(objectAction))
 +            .findFirst()
 +            .orElse(null);
 +        
 +        // throw new RuntimeException("Unable to find the mixed-in action corresponding to " + objectAction.getIdentifier().toFullIdentityString());
 +    }
 +
 +    public InteractionInitiatedBy getInteractionInitiatedBy() {
 +        return getExecutionMode().shouldEnforceRules()? InteractionInitiatedBy.USER: InteractionInitiatedBy.FRAMEWORK;
 +    }
 +
 +    // see if this is a contributed property/collection/action
 +    private ContributeeMember determineIfContributed(
 +            final Object[] args,
 +            final ObjectMember objectMember) {
 +
 +        if (!(objectMember instanceof ObjectAction)) {
 +            return null;
 +        }
 +
 +        final ObjectAction objectAction = (ObjectAction) objectMember;
 +
 +        for (final Object arg : args) {
 +            if (arg == null) {
 +                continue;
 +            }
 +            final ObjectSpecificationDefault objectSpec = getJavaSpecification(arg.getClass());
 +
 +            if (args.length == 1) {
 +                // is this a contributed property/collection?
 +                final Stream<ObjectAssociation> associations =
 +                        objectSpec.streamAssociations(Contributed.INCLUDED);
 +                
 +                
 +                final Optional<ContributeeMember> contributeeMember = associations
 +                .filter(association->association instanceof ContributeeMember)
 +                .map(association->(ContributeeMember) association)
 +                .filter(contributeeMember1->contributeeMember1.isContributedBy(objectAction))
 +                .findAny();
 +                
 +                if(contributeeMember.isPresent()) {
 +                    return contributeeMember.get();
 +                }
 +            }
 +
 +            // is this a contributed action?
 +            {
 +                final Stream<ObjectAction> actions =
 +                        objectSpec.streamObjectActions(Contributed.INCLUDED);
 +                
 +                final Optional<ContributeeMember> contributeeMember = actions
 +                        .filter(action->action instanceof ContributeeMember)
 +                        .map(action->(ContributeeMember) action)
 +                        .filter(contributeeMember1->contributeeMember1.isContributedBy(objectAction))
 +                        .findAny();
 +                
 +                if(contributeeMember.isPresent()) {
 +                    return contributeeMember.get();
 +                }
 +            }
 +            
 +        }
 +
 +        return null;
 +    }
 +
 +    private boolean isJdoMethod(final Method method) {
 +        return methodStartsWith(method, "jdo") || jdoMethodsProvidedByEnhancement.contains(method.getName());
 +    }
 +
 +    private static boolean isInjectMethod(final Method method) {
 +        return methodStartsWith(method, "inject");
 +    }
 +
 +    private static boolean methodStartsWith(final Method method, final String prefix) {
 +        return method.getName().startsWith(prefix);
 +    }
 +
 +    // /////////////////////////////////////////////////////////////////
 +    // title
 +    // /////////////////////////////////////////////////////////////////
 +
 +    private Object handleTitleMethod(final ObjectAdapter targetAdapter)
 +            throws IllegalAccessException, InvocationTargetException {
 +
 +        resolveIfRequired(targetAdapter);
 +
 +        final ObjectSpecification targetNoSpec = targetAdapter.getSpecification();
 +        final ObjectTitleContext titleContext = targetNoSpec.createTitleInteractionContext(getAuthenticationSession(), InteractionInitiatedBy.FRAMEWORK, targetAdapter);
 +        final ObjectTitleEvent titleEvent = titleContext.createInteractionEvent();
 +        notifyListeners(titleEvent);
 +        return titleEvent.getTitle();
 +    }
 +
 +    // /////////////////////////////////////////////////////////////////
 +    // save
 +    // /////////////////////////////////////////////////////////////////
 +
 +    private Object handleSaveMethod(
 +            final ObjectAdapter targetAdapter, final ObjectSpecification targetNoSpec) {
 +
 +        if(getExecutionMode().shouldEnforceRules()) {
 +            final InteractionResult interactionResult =
 +                    targetNoSpec.isValidResult(targetAdapter, getInteractionInitiatedBy());
 +            notifyListenersAndVetoIfRequired(interactionResult);
 +        }
 +
 +        if (getExecutionMode().shouldExecute()) {
 +            if (targetAdapter.isTransient()) {
-                 getPersistenceSessionService().makePersistent(targetAdapter);
++                if(getExecutionMode().shouldFailFast()) {
++                    getPersistenceSessionService().makePersistent(targetAdapter);
++                } else {
++                    try {
++                        getPersistenceSessionService().makePersistent(targetAdapter);
++                    } catch(Exception ignore) {
++                        // ignore
++                    }
++                }
 +            }
 +        }
 +        return null;
 +    }
 +
 +    // /////////////////////////////////////////////////////////////////
 +    // property - access
 +    // /////////////////////////////////////////////////////////////////
 +
 +    private Object handleGetterMethodOnProperty(
 +            final ObjectAdapter targetAdapter,
 +            final Object[] args,
 +            final OneToOneAssociation property) {
 +
 +        if (args.length != 0) {
 +            throw new IllegalArgumentException("Invoking a 'get' should have no arguments");
 +        }
 +
 +        if(getExecutionMode().shouldEnforceRules()) {
 +            checkVisibility(targetAdapter, property);
 +        }
 +
 +        resolveIfRequired(targetAdapter);
 +
 +        final InteractionInitiatedBy interactionInitiatedBy = getInteractionInitiatedBy();
 +        final ObjectAdapter currentReferencedAdapter = property.get(targetAdapter, interactionInitiatedBy);
 +
 +        final Object currentReferencedObj = ObjectAdapter.Util.unwrapPojo(currentReferencedAdapter);
 +
 +        final PropertyAccessEvent ev = new PropertyAccessEvent(getDelegate(), property.getIdentifier(), currentReferencedObj);
 +        notifyListeners(ev);
 +        return currentReferencedObj;
 +    }
 +
 +
 +    // /////////////////////////////////////////////////////////////////
 +    // property - modify
 +    // /////////////////////////////////////////////////////////////////
 +
 +    private Object handleSetterMethodOnProperty(
 +            final ObjectAdapter targetAdapter, final Object[] args,
 +            final OneToOneAssociation property) {
 +        if (args.length != 1) {
 +            throw new IllegalArgumentException("Invoking a setter should only have a single argument");
 +        }
 +
 +        final Object argumentObj = underlying(args[0]);
 +
 +        if(getExecutionMode().shouldEnforceRules()) {
 +            checkVisibility(targetAdapter, property);
 +            checkUsability(targetAdapter, property);
 +        }
 +
 +        final ObjectAdapter argumentAdapter = argumentObj != null ? adapterFor(argumentObj) : null;
 +
 +        resolveIfRequired(targetAdapter);
 +
 +
 +        if(getExecutionMode().shouldEnforceRules()) {
 +            final InteractionResult interactionResult = property.isAssociationValid(targetAdapter, argumentAdapter, getInteractionInitiatedBy()).getInteractionResult();
 +            notifyListenersAndVetoIfRequired(interactionResult);
 +        }
 +
 +        if (getExecutionMode().shouldExecute()) {
-             property.set(targetAdapter, argumentAdapter, getInteractionInitiatedBy());
++            if(getExecutionMode().shouldFailFast()) {
++                property.set(targetAdapter, argumentAdapter, getInteractionInitiatedBy());
++            } else {
++                try {
++                    property.set(targetAdapter, argumentAdapter, getInteractionInitiatedBy());
++                } catch(Exception ignore) {
++                    // ignore
++                }
++            }
++
 +        }
 +
 +        return null;
 +    }
 +
 +
 +    // /////////////////////////////////////////////////////////////////
 +    // collection - access
 +    // /////////////////////////////////////////////////////////////////
 +
 +    private Object handleGetterMethodOnCollection(
 +            final ObjectAdapter targetAdapter,
 +            final Object[] args,
 +            final OneToManyAssociation collection,
 +            final Method method,
 +            final String memberName) {
 +
 +        if (args.length != 0) {
 +            throw new IllegalArgumentException("Invoking a 'get' should have no arguments");
 +        }
 +
 +        if(getExecutionMode().shouldEnforceRules()) {
 +            checkVisibility(targetAdapter, collection);
 +        }
 +
 +        resolveIfRequired(targetAdapter);
 +
 +        final InteractionInitiatedBy interactionInitiatedBy = getInteractionInitiatedBy();
 +        final ObjectAdapter currentReferencedAdapter = collection.get(targetAdapter, interactionInitiatedBy);
 +
 +        final Object currentReferencedObj = ObjectAdapter.Util.unwrapPojo(currentReferencedAdapter);
 +
 +        final CollectionAccessEvent ev = new CollectionAccessEvent(getDelegate(), collection.getIdentifier());
 +
 +        if (currentReferencedObj instanceof Collection) {
 +            final Collection<?> collectionViewObject = lookupWrappingObject(method, memberName,
 +                    (Collection<?>) currentReferencedObj, collection);
 +            notifyListeners(ev);
 +            return collectionViewObject;
 +        } else if (currentReferencedObj instanceof Map) {
 +            final Map<?, ?> mapViewObject = lookupWrappingObject(memberName, (Map<?, ?>) currentReferencedObj,
 +                    collection);
 +            notifyListeners(ev);
 +            return mapViewObject;
 +        }
 +        throw new IllegalArgumentException(String.format("Collection type '%s' not supported by framework", currentReferencedObj.getClass().getName()));
 +    }
 +
 +    private Collection<?> lookupWrappingObject(
 +            final Method method,
 +            final String memberName,
 +            final Collection<?> collectionToLookup,
 +            final OneToManyAssociation otma) {
 +        return collectionToLookup instanceof WrappingObject
 +                ? collectionToLookup
 +                        : proxy.proxy(collectionToLookup, memberName, this, otma);
 +    }
 +
 +    private Map<?, ?> lookupWrappingObject(
 +            final String memberName,
 +            final Map<?, ?> mapToLookup,
 +            final OneToManyAssociation otma) {
 +        return mapToLookup instanceof WrappingObject
 +                ? mapToLookup
 +                        : proxy.proxy(mapToLookup, memberName, this, otma);
 +    }
 +
 +    // /////////////////////////////////////////////////////////////////
 +    // collection - add to
 +    // /////////////////////////////////////////////////////////////////
 +
 +    private Object handleCollectionAddToMethod(
 +            final ObjectAdapter targetAdapter,
 +            final Object[] args,
 +            final OneToManyAssociation otma) {
 +
 +        if (args.length != 1) {
 +            throw new IllegalArgumentException("Invoking a addTo should only have a single argument");
 +        }
 +
 +        if(getExecutionMode().shouldEnforceRules()) {
 +            checkVisibility(targetAdapter, otma);
 +            checkUsability(targetAdapter, otma);
 +        }
 +
 +        resolveIfRequired(targetAdapter);
 +
 +        final Object argumentObj = underlying(args[0]);
 +        if (argumentObj == null) {
 +            throw new IllegalArgumentException("Must provide a non-null object to add");
 +        }
 +        final ObjectAdapter argumentNO = adapterFor(argumentObj);
 +
 +        if(getExecutionMode().shouldEnforceRules()) {
 +            final InteractionResult interactionResult = otma.isValidToAdd(targetAdapter, argumentNO,
 +                    getInteractionInitiatedBy()).getInteractionResult();
 +            notifyListenersAndVetoIfRequired(interactionResult);
 +        }
 +
 +        if (getExecutionMode().shouldExecute()) {
-             otma.addElement(targetAdapter, argumentNO, getInteractionInitiatedBy());
++            if(getExecutionMode().shouldFailFast()) {
++                otma.addElement(targetAdapter, argumentNO, getInteractionInitiatedBy());
++            } else {
++                try {
++                    otma.addElement(targetAdapter, argumentNO, getInteractionInitiatedBy());
++                } catch(Exception ignore) {
++                    // ignore
++                }
++            }
 +        }
 +
 +        return null;
 +    }
 +
 +
 +    // /////////////////////////////////////////////////////////////////
 +    // collection - remove from
 +    // /////////////////////////////////////////////////////////////////
 +
 +    private Object handleCollectionRemoveFromMethod(
 +            final ObjectAdapter targetAdapter,
 +            final Object[] args,
 +            final OneToManyAssociation collection) {
 +        if (args.length != 1) {
 +            throw new IllegalArgumentException("Invoking a removeFrom should only have a single argument");
 +        }
 +
 +        if(getExecutionMode().shouldEnforceRules()) {
 +            checkVisibility(targetAdapter, collection);
 +            checkUsability(targetAdapter, collection);
 +        }
 +
 +
 +        resolveIfRequired(targetAdapter);
 +
 +        final Object argumentObj = underlying(args[0]);
 +        if (argumentObj == null) {
 +            throw new IllegalArgumentException("Must provide a non-null object to remove");
 +        }
 +        final ObjectAdapter argumentAdapter = adapterFor(argumentObj);
 +
 +        if(getExecutionMode().shouldEnforceRules()) {
 +            final InteractionResult interactionResult = collection.isValidToRemove(targetAdapter, argumentAdapter,
 +                    getInteractionInitiatedBy()).getInteractionResult();
 +            notifyListenersAndVetoIfRequired(interactionResult);
 +        }
 +
 +        if (getExecutionMode().shouldExecute()) {
-             collection.removeElement(targetAdapter, argumentAdapter, getInteractionInitiatedBy());
++            if(getExecutionMode().shouldFailFast()) {
++                collection.removeElement(targetAdapter, argumentAdapter, getInteractionInitiatedBy());
++            } else {
++                try {
++                    collection.removeElement(targetAdapter, argumentAdapter, getInteractionInitiatedBy());
++                } catch(Exception ignore) {
++                    // ignore
++                }
++            }
 +        }
 +
 +        return null;
 +    }
 +
 +    // /////////////////////////////////////////////////////////////////
 +    // action
 +    // /////////////////////////////////////////////////////////////////
 +
 +    private Object handleActionMethod(
 +            final ObjectAdapter targetAdapter, final Object[] args,
 +            final ObjectAction objectAction,
 +            final ContributeeMember contributeeMember) {
 +
 +        final ObjectAdapter contributeeAdapter;
 +        final Object[] contributeeArgs;
 +        if(contributeeMember != null) {
 +            final int contributeeParamPosition = contributeeMember.getContributeeParamPosition();
 +            final Object contributee = args[contributeeParamPosition];
 +            contributeeAdapter = adapterFor(contributee);
 +
 +            final List<Object> argCopy = _Lists.of(args);
 +            argCopy.remove(contributeeParamPosition);
 +            contributeeArgs = argCopy.toArray();
 +        } else {
 +            contributeeAdapter = null;
 +            contributeeArgs = null;
 +        }
 +
 +        if(getExecutionMode().shouldEnforceRules()) {
 +            if(contributeeMember != null) {
 +                checkVisibility(contributeeAdapter, contributeeMember);
 +                checkUsability(contributeeAdapter, contributeeMember);
 +            } else {
 +                checkVisibility(targetAdapter, objectAction);
 +                checkUsability(targetAdapter, objectAction);
 +            }
 +        }
 +
 +        final ObjectAdapter[] argAdapters = asObjectAdaptersUnderlying(args);
 +
 +        if(getExecutionMode().shouldEnforceRules()) {
 +            if(contributeeMember != null) {
 +                if(contributeeMember instanceof ObjectActionContributee) {
 +                    final ObjectActionContributee objectActionContributee = (ObjectActionContributee) contributeeMember;
 +                    final ObjectAdapter[] contributeeArgAdapters = asObjectAdaptersUnderlying(contributeeArgs);
 +
 +                    checkValidity(contributeeAdapter, objectActionContributee, contributeeArgAdapters);
 +                }
 +                // nothing to do for contributed properties or collections
 +            } else {
 +                checkValidity(targetAdapter, objectAction, argAdapters);
 +            }
 +        }
 +
 +        if (getExecutionMode().shouldExecute()) {
 +            final InteractionInitiatedBy interactionInitiatedBy = getInteractionInitiatedBy();
 +
 +            final ObjectAdapter mixedInAdapter = null; // if a mixin action, then it will automatically fill in.
 +
-             final ObjectAdapter returnedAdapter = objectAction.execute(
-                     targetAdapter, mixedInAdapter, argAdapters,
-                     interactionInitiatedBy);
++
++            ObjectAdapter returnedAdapter;
++
++            if(getExecutionMode().shouldFailFast()) {
++                returnedAdapter = objectAction.execute(
++                        targetAdapter, mixedInAdapter, argAdapters,
++                        interactionInitiatedBy);
++            } else {
++                try {
++                    returnedAdapter = objectAction.execute(
++                            targetAdapter, mixedInAdapter, argAdapters,
++                            interactionInitiatedBy);
++                } catch(Exception ignore) {
++                    // ignore
++                    returnedAdapter = null;
++                }
++
++            }
++
 +
 +            return ObjectAdapter.Util.unwrapPojo(returnedAdapter);
 +        }
 +
 +        return null;
 +    }
 +
 +    private void checkValidity(final ObjectAdapter targetAdapter, final ObjectAction objectAction, final ObjectAdapter[] argAdapters) {
 +        final InteractionResult interactionResult = objectAction.isProposedArgumentSetValid(targetAdapter, argAdapters,
 +                getInteractionInitiatedBy()).getInteractionResult();
 +        notifyListenersAndVetoIfRequired(interactionResult);
 +    }
 +
 +    private ObjectAdapter[] asObjectAdaptersUnderlying(final Object[] args) {
 +
 +        final ObjectAdapter[] argAdapters = new ObjectAdapter[args.length];
 +        int i = 0;
 +        for (final Object arg : args) {
 +            argAdapters[i++] = adapterFor(underlying(arg));
 +        }
 +
 +        return argAdapters;
 +    }
 +
 +    private ObjectAdapter adapterFor(final Object obj) {
 +        return obj != null ? getObjectAdapterProvider().adapterFor(obj) : null;
 +    }
 +
 +    private Object underlying(final Object arg) {
 +        if (arg instanceof WrappingObject) {
 +            final WrappingObject argViewObject = (WrappingObject) arg;
 +            return argViewObject.__isis_wrapped();
 +        } else {
 +            return arg;
 +        }
 +    }
 +
 +    // /////////////////////////////////////////////////////////////////
 +    // visibility and usability checks (common to all members)
 +    // /////////////////////////////////////////////////////////////////
 +
 +    /**
 +     * REVIEW: ideally should provide some way to allow to caller to indicate the 'where' context.  Having
 +     * a hard-coded value like this is an approximation.
 +     */
 +    private final Where where = Where.ANYWHERE;
 +
 +    private void checkVisibility(
 +            final ObjectAdapter targetObjectAdapter,
 +            final ObjectMember objectMember) {
 +        final Consent visibleConsent = objectMember.isVisible(targetObjectAdapter, getInteractionInitiatedBy(), where);
 +        final InteractionResult interactionResult = visibleConsent.getInteractionResult();
 +        notifyListenersAndVetoIfRequired(interactionResult);
 +    }
 +
 +    private void checkUsability(
 +            final ObjectAdapter targetObjectAdapter,
 +            final ObjectMember objectMember) {
 +        final InteractionResult interactionResult = objectMember.isUsable(targetObjectAdapter,
 +                getInteractionInitiatedBy(), where
 +                ).getInteractionResult();
 +        notifyListenersAndVetoIfRequired(interactionResult);
 +    }
 +
 +    // /////////////////////////////////////////////////////////////////
 +    // notify listeners
 +    // /////////////////////////////////////////////////////////////////
 +
 +    private void notifyListenersAndVetoIfRequired(final InteractionResult interactionResult) {
 +        final InteractionEvent interactionEvent = interactionResult.getInteractionEvent();
 +        notifyListeners(interactionEvent);
 +        if (interactionEvent.isVeto()) {
 +            throw toException(interactionEvent);
 +        }
 +    }
 +
 +    /**
 +     * Wraps a {@link InteractionEvent#isVeto() vetoing}
 +     * {@link InteractionEvent} in a corresponding {@link InteractionException},
 +     * and returns it.
 +     */
 +    private InteractionException toException(final InteractionEvent interactionEvent) {
 +        if (!interactionEvent.isVeto()) {
 +            throw new IllegalArgumentException("Provided interactionEvent must be a veto");
 +        }
 +        if (interactionEvent instanceof ValidityEvent) {
 +            final ValidityEvent validityEvent = (ValidityEvent) interactionEvent;
 +            return new InvalidException(validityEvent);
 +        }
 +        if (interactionEvent instanceof VisibilityEvent) {
 +            final VisibilityEvent visibilityEvent = (VisibilityEvent) interactionEvent;
 +            return new HiddenException(visibilityEvent);
 +        }
 +        if (interactionEvent instanceof UsabilityEvent) {
 +            final UsabilityEvent usabilityEvent = (UsabilityEvent) interactionEvent;
 +            return new DisabledException(usabilityEvent);
 +        }
 +        throw new IllegalArgumentException("Provided interactionEvent must be a VisibilityEvent, UsabilityEvent or a ValidityEvent");
 +    }
 +
 +    // /////////////////////////////////////////////////////////////////
 +    // switching
 +    // /////////////////////////////////////////////////////////////////
 +
 +    private ObjectMember locateAndCheckMember(final Method method) {
 +        final ObjectSpecificationDefault objectSpecificationDefault = getJavaSpecificationOfOwningClass(method);
 +        final ObjectMember member = objectSpecificationDefault.getMember(method);
 +
 +        if (member == null) {
 +            final String methodName = method.getName();
 +            throw new UnsupportedOperationException("Method '" + methodName + "' being invoked does not correspond to any of the object's fields or actions.");
 +        }
 +        return member;
 +    }
 +
 +    protected boolean isTitleMethod(final Method method) {
 +        return method.equals(titleMethod);
 +    }
 +
 +    protected boolean isSaveMethod(final Method method) {
 +        return method.equals(__isis_saveMethod);
 +    }
 +
 +    protected boolean isWrappedMethod(final Method method) {
 +        return method.equals(__isis_wrappedMethod);
 +    }
 +
 +    protected boolean isExecutionModeMethod(final Method method) {
 +        return method.equals(__isis_executionMode);
 +    }
 +
 +    // /////////////////////////////////////////////////////////////////
 +    // Specification lookup
 +    // /////////////////////////////////////////////////////////////////
 +
 +    private ObjectSpecificationDefault getJavaSpecificationOfOwningClass(final Method method) {
 +        return getJavaSpecification(method.getDeclaringClass());
 +    }
 +
 +    private ObjectSpecificationDefault getJavaSpecification(final Class<?> clazz) {
 +        final ObjectSpecification objectSpec = getSpecification(clazz);
 +        if (!(objectSpec instanceof ObjectSpecificationDefault)) {
 +            throw new UnsupportedOperationException("Only Java is supported (specification is '" + objectSpec.getClass().getCanonicalName() + "')");
 +        }
 +        return (ObjectSpecificationDefault) objectSpec;
 +    }
 +
 +    private ObjectSpecification getSpecification(final Class<?> type) {
 +        return getSpecificationLoader().loadSpecification(type);
 +    }
 +
 +    // /////////////////////////////////////////////////////////////////
 +    // Dependencies
 +    // /////////////////////////////////////////////////////////////////
 +
 +    protected SpecificationLoader getSpecificationLoader() {
 +        return isisSessionFactory.getSpecificationLoader();
 +    }
 +
 +    public AuthenticationSessionProvider getAuthenticationSessionProvider() {
 +        return authenticationSessionProvider;
 +    }
 +
 +    protected AuthenticationSession getAuthenticationSession() {
 +        return getAuthenticationSessionProvider().getAuthenticationSession();
 +    }
 +
 +    protected ObjectAdapterProvider getObjectAdapterProvider() {
 +        return persistenceSessionServiceInternal;
 +    }
 +    
 +    protected PersistenceSessionServiceInternal getPersistenceSessionService() {
 +        return persistenceSessionServiceInternal;
 +    }
 +
 +    public IsisSessionFactory getIsisSessionFactory() {
 +        return isisSessionFactory;
 +    }
 +}
diff --cc core/viewer-restfulobjects-applib/src/main/java/org/apache/isis/viewer/restfulobjects/applib/RepresentationType.java
index 059630f,70c2794..4075ffe
--- a/core/viewer-restfulobjects-applib/src/main/java/org/apache/isis/viewer/restfulobjects/applib/RepresentationType.java
+++ b/core/viewer-restfulobjects-applib/src/main/java/org/apache/isis/viewer/restfulobjects/applib/RepresentationType.java
@@@ -18,9 -18,7 +18,8 @@@
   */
  package org.apache.isis.viewer.restfulobjects.applib;
  
- 
  import java.util.Collections;
 +import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  
@@@ -41,9 -41,9 +40,10 @@@ import org.apache.isis.viewer.restfulob
  import org.apache.isis.viewer.restfulobjects.applib.domaintypes.TypeActionResultRepresentation;
  import org.apache.isis.viewer.restfulobjects.applib.domaintypes.TypeListRepresentation;
  import org.apache.isis.viewer.restfulobjects.applib.errors.ErrorRepresentation;
+ import org.apache.isis.viewer.restfulobjects.applib.health.HealthRepresentation;
  import org.apache.isis.viewer.restfulobjects.applib.homepage.HomePageRepresentation;
  import org.apache.isis.viewer.restfulobjects.applib.user.UserRepresentation;
 +import org.apache.isis.viewer.restfulobjects.applib.util.MediaTypes;
  import org.apache.isis.viewer.restfulobjects.applib.util.Parser;
  import org.apache.isis.viewer.restfulobjects.applib.version.VersionRepresentation;
  
diff --cc core/viewer-restfulobjects-applib/src/main/java/org/apache/isis/viewer/restfulobjects/applib/health/HealthResource.java
index 0000000,c00aeca..f624932
mode 000000,100644..100644
--- a/core/viewer-restfulobjects-applib/src/main/java/org/apache/isis/viewer/restfulobjects/applib/health/HealthResource.java
+++ b/core/viewer-restfulobjects-applib/src/main/java/org/apache/isis/viewer/restfulobjects/applib/health/HealthResource.java
@@@ -1,0 -1,51 +1,44 @@@
+ /*
+  *  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.viewer.restfulobjects.applib.health;
+ 
 -import javax.ws.rs.DELETE;
 -import javax.ws.rs.GET;
 -import javax.ws.rs.POST;
 -import javax.ws.rs.PUT;
 -import javax.ws.rs.Path;
 -import javax.ws.rs.Produces;
++import javax.ws.rs.*;
+ import javax.ws.rs.core.MediaType;
+ import javax.ws.rs.core.Response;
+ 
 -import org.jboss.resteasy.annotations.ClientResponseType;
 -
+ import org.apache.isis.viewer.restfulobjects.applib.RestfulMediaType;
+ 
+ @Path("/health")
+ public interface HealthResource {
+ 
+     @GET
+     @Produces({ MediaType.APPLICATION_JSON, RestfulMediaType.APPLICATION_JSON_HEALTH })
 -    @ClientResponseType(entityType = String.class)
++    //TODO @ClientResponseType(entityType = String.class)
+     public Response health();
+ 
+     @DELETE
+     public Response deleteHealthNotAllowed();
+ 
+     @PUT
+     public Response putHealthNotAllowed();
+ 
+     @POST
+     public Response postHealthNotAllowed();
+ 
+ }
diff --cc example/application/simpleapp/application/src/test/java/domainapp/application/integtests/mml/approved/domainapp.application.services.health.HealthCheckServiceImpl.xml
index 0000000,a2ed203..b2eaa9d
mode 000000,100644..100644
--- a/example/application/simpleapp/application/src/test/java/domainapp/application/integtests/mml/approved/domainapp.application.services.health.HealthCheckServiceImpl.xml
+++ b/example/application/simpleapp/application/src/test/java/domainapp/application/integtests/mml/approved/domainapp.application.services.health.HealthCheckServiceImpl.xml
@@@ -1,0 -1,50 +1,45 @@@
+ <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+ <mml:domainClassDto id="domainapp.application.services.health.HealthCheckServiceImpl" service="true" xmlns:mml="http://isis.apache.org/schema/metamodel">
+     <mml:facets>
+         <mml:facet id="org.apache.isis.core.metamodel.facets.all.named.NamedFacet" fqcn="org.apache.isis.core.metamodel.facets.all.named.NamedFacetInferred">
+             <mml:attr name="escaped">true</mml:attr>
+             <mml:attr name="value">Health Check Service Impl</mml:attr>
+         </mml:facet>
++        <mml:facet id="org.apache.isis.core.metamodel.facets.object.audit.AuditableFacet" fqcn="org.apache.isis.core.metamodel.facets.object.domainobject.auditing.AuditableFacetFromConfiguration">
++            <mml:attr name="enablement">ENABLED</mml:attr>
++        </mml:facet>
+         <mml:facet id="org.apache.isis.core.metamodel.facets.object.bookmarkpolicy.BookmarkPolicyFacet" fqcn="org.apache.isis.core.metamodel.facets.object.bookmarkpolicy.BookmarkPolicyFacetFallback">
+             <mml:attr name="bookmarkPolicy">NEVER</mml:attr>
+         </mml:facet>
+         <mml:facet id="org.apache.isis.core.metamodel.facets.object.domainservice.DomainServiceFacet" fqcn="org.apache.isis.core.metamodel.facets.object.domainservice.annotation.DomainServiceFacetAnnotation">
+             <mml:attr name="natureOfService">DOMAIN</mml:attr>
+             <mml:attr name="repositoryFor">java.lang.Object</mml:attr>
+         </mml:facet>
+         <mml:facet id="org.apache.isis.core.metamodel.facets.object.domainservicelayout.DomainServiceLayoutFacet" fqcn="org.apache.isis.core.metamodel.facets.object.domainservicelayout.annotation.DomainServiceLayoutFacetAnnotation">
+             <mml:attr name="menuBar">PRIMARY</mml:attr>
 -            <mml:attr name="menuOrder">2147483547</mml:attr>
+         </mml:facet>
+         <mml:facet id="org.apache.isis.core.metamodel.facets.object.grid.GridFacet" fqcn="org.apache.isis.core.metamodel.facets.object.grid.GridFacetDefault"/>
+         <mml:facet id="org.apache.isis.core.metamodel.facets.object.icon.IconFacet" fqcn="org.apache.isis.core.metamodel.facets.object.domainservice.annotation.IconFacetDerivedFromDomainServiceAnnotation">
+             <mml:attr name="repositoryFor">java.lang.Object</mml:attr>
+         </mml:facet>
 -        <mml:facet id="org.apache.isis.core.metamodel.facets.object.membergroups.MemberGroupLayoutFacet" fqcn="org.apache.isis.core.metamodel.facets.object.membergroups.annotprop.MemberGroupLayoutFacetFallback">
 -            <mml:attr name="columns">[4,0,0,8]</mml:attr>
 -            <mml:attr name="left">General</mml:attr>
 -        </mml:facet>
 -        <mml:facet id="org.apache.isis.core.metamodel.facets.object.notpersistable.NotPersistableFacet" fqcn="org.apache.isis.core.metamodel.facets.fallback.NotPersistableFacetNull">
 -            <mml:attr name="disabling">true</mml:attr>
 -        </mml:facet>
+         <mml:facet id="org.apache.isis.core.metamodel.facets.object.objectspecid.ObjectSpecIdFacet" fqcn="org.apache.isis.core.metamodel.facets.object.objectspecid.classname.ObjectSpecIdFacetDerivedFromClassName">
+             <mml:attr name="derived">true</mml:attr>
+             <mml:attr name="value">domainapp.application.services.health.HealthCheckServiceImpl</mml:attr>
+         </mml:facet>
+         <mml:facet id="org.apache.isis.core.metamodel.facets.object.objectvalidprops.ObjectValidPropertiesFacet" fqcn="org.apache.isis.core.metamodel.facets.object.objectvalidprops.impl.ObjectValidPropertiesFacetImpl">
+             <mml:attr name="validating">true</mml:attr>
+         </mml:facet>
+         <mml:facet id="org.apache.isis.core.metamodel.facets.object.paged.PagedFacet" fqcn="org.apache.isis.core.metamodel.facets.fallback.PagedFacetFromConfiguration">
+             <mml:attr name="derived">true</mml:attr>
+             <mml:attr name="value">25</mml:attr>
+         </mml:facet>
+         <mml:facet id="org.apache.isis.core.metamodel.facets.object.plural.PluralFacet" fqcn="org.apache.isis.core.metamodel.facets.object.plural.inferred.PluralFacetInferred">
+             <mml:attr name="derived">true</mml:attr>
+             <mml:attr name="value">Health Check Service Impls</mml:attr>
+         </mml:facet>
+         <mml:facet id="org.apache.isis.core.runtime.authorization.standard.AuthorizationFacet" fqcn="org.apache.isis.core.runtime.authorization.standard.AuthorizationFacetImpl">
+             <mml:attr name="disabling">true</mml:attr>
+             <mml:attr name="hiding">true</mml:attr>
+         </mml:facet>
+     </mml:facets>
+ </mml:domainClassDto>
diff --cc example/application/simpleapp/module-simple/src/main/java/domainapp/modules/simple/dom/impl/SimpleObjects.java
index d62b3e6,5aa77cf..30771d1
--- a/example/application/simpleapp/module-simple/src/main/java/domainapp/modules/simple/dom/impl/SimpleObjects.java
+++ b/example/application/simpleapp/module-simple/src/main/java/domainapp/modules/simple/dom/impl/SimpleObjects.java
@@@ -82,6 -82,14 +82,14 @@@ public class SimpleObjects 
                  .executeUnique();
      }
  
+     @Programmatic
+     public void ping() {
 -        TypesafeQuery<SimpleObject> q = isisJdoSupport.newTypesafeQuery(SimpleObject.class);
++        JDOQLTypedQuery<SimpleObject> q = isisJdoSupport.newTypesafeQuery(SimpleObject.class);
+         final QSimpleObject candidate = QSimpleObject.candidate();
+         q.range(0,2);
+         q.orderBy(candidate.name.asc());
+         q.executeList();
+     }
  
      public static class CreateDomainEvent extends ActionDomainEvent<SimpleObjects> {}
      @Action(domainEvent = CreateDomainEvent.class)