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 2013/05/20 13:34:48 UTC

[8/8] git commit: ISIS-409: fixing up build post moving wrapper service to core

ISIS-409: fixing up build post moving wrapper service to core

* remove DomainObjectContainerWrapperFactory
* update junit viewer tests to explicitly register WrapperFactoryDefault
* prevent WrapperFactory interface hierarchy from being introspected into the Isis metamodel
* change package of wrapper service to o.a.i.core.wrapper


Project: http://git-wip-us.apache.org/repos/asf/isis/repo
Commit: http://git-wip-us.apache.org/repos/asf/isis/commit/55f931ae
Tree: http://git-wip-us.apache.org/repos/asf/isis/tree/55f931ae
Diff: http://git-wip-us.apache.org/repos/asf/isis/diff/55f931ae

Branch: refs/heads/master
Commit: 55f931ae18086e372993d18f577eb69c66d4ec9f
Parents: cd0735d
Author: Dan Haywood <da...@apache.org>
Authored: Mon May 20 12:34:18 2013 +0100
Committer: Dan Haywood <da...@apache.org>
Committed: Mon May 20 12:34:18 2013 +0100

----------------------------------------------------------------------
 .../viewer/junit/internal/AnnotationInstaller.java |    3 +-
 ...ryPersistenceMechanismInstallerWithinJunit.java |   37 -
 .../org/apache/isis/viewer/junit/AbstractTest.java |    3 +-
 .../tck/src/test/java/junit/AbstractTest.java      |    3 +-
 .../applib/services/wrapper/WrapperFactory.java    |    9 +
 .../AuthenticationSessionProvider.java             |    1 +
 .../AuthenticationSessionProviderAware.java        |    3 +
 .../isis/core/commons/components/Injectable.java   |    2 +
 .../metamodel/adapter/ObjectPersistorAware.java    |    3 +
 .../metamodel/adapter/mgr/AdapterManagerAware.java |    3 +
 .../metamodel/spec/SpecificationLoaderAware.java   |    3 +
 .../isis/core/wrapper/WrapperFactoryDefault.java   |  280 +++++++
 .../AbstractCollectionInvocationHandler.java       |   89 ++
 .../wrapper/internal/CgLibClassProxyFactory.java   |   71 ++
 .../isis/core/wrapper/internal/CgLibProxy.java     |   74 ++
 .../internal/ClassInstantiatorFactoryCE.java       |   65 ++
 .../internal/CollectionInvocationHandler.java      |   54 ++
 .../internal/DelegatingInvocationHandler.java      |   32 +
 .../DelegatingInvocationHandlerDefault.java        |  131 +++
 .../internal/DomainObjectInvocationHandler.java    |  645 +++++++++++++++
 .../wrapper/internal/IClassInstantiatorCE.java     |   37 +
 .../isis/core/wrapper/internal/IProxyFactory.java  |   28 +
 .../internal/InteractionEventDispatcher.java       |   28 +
 .../InteractionEventDispatcherTypeSafe.java        |   34 +
 .../InvocationHandlerMethodInterceptor.java        |   39 +
 .../core/wrapper/internal/JavaProxyFactory.java    |   40 +
 .../wrapper/internal/MapInvocationHandler.java     |   49 ++
 .../internal/ObjenesisClassInstantiatorCE.java     |   31 +
 .../apache/isis/core/wrapper/internal/Proxy.java   |   80 ++
 .../wrapper/internal/RuntimeExceptionWrapper.java  |   33 +
 .../isis/core/wrapper/internal/util/Constants.java |   52 ++
 .../wrapper/internal/util/MethodPrefixFinder.java  |   48 ++
 .../DomainObjectContainerWrapperFactory.java       |  116 ---
 .../AbstractCollectionInvocationHandler.java       |   89 --
 .../metamodel/internal/CgLibClassProxyFactory.java |   71 --
 .../wrapper/metamodel/internal/CgLibProxy.java     |   74 --
 .../internal/ClassInstantiatorFactoryCE.java       |   65 --
 .../internal/CollectionInvocationHandler.java      |   54 --
 .../internal/DelegatingInvocationHandler.java      |   32 -
 .../DelegatingInvocationHandlerDefault.java        |  131 ---
 .../internal/DomainObjectInvocationHandler.java    |  645 ---------------
 .../metamodel/internal/IClassInstantiatorCE.java   |   37 -
 .../wrapper/metamodel/internal/IProxyFactory.java  |   28 -
 .../internal/InteractionEventDispatcher.java       |   28 -
 .../InteractionEventDispatcherTypeSafe.java        |   34 -
 .../InvocationHandlerMethodInterceptor.java        |   39 -
 .../metamodel/internal/JavaProxyFactory.java       |   40 -
 .../metamodel/internal/MapInvocationHandler.java   |   49 --
 .../internal/ObjenesisClassInstantiatorCE.java     |   31 -
 .../wrapper/metamodel/internal/Proxy.java          |   80 --
 .../internal/RuntimeExceptionWrapper.java          |   33 -
 .../metamodel/internal/WrapperFactoryDefault.java  |  271 ------
 .../wrapper/metamodel/internal/util/Constants.java |   52 --
 .../internal/util/MethodPrefixFinder.java          |   48 --
 .../WrappedFactoryDefaultTest_wrappedObject.java   |    2 +-
 ...FactoryDefaultTest_wrappedObject_transient.java |    2 +-
 .../isis/example/claims/junit/AbstractTest.java    |    3 +-
 .../src/test/java/junit/AbstractTest.java          |    3 +-
 58 files changed, 1976 insertions(+), 2091 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/isis/blob/55f931ae/component/viewer/junit/impl/src/main/java/org/apache/isis/viewer/junit/internal/AnnotationInstaller.java
----------------------------------------------------------------------
diff --git a/component/viewer/junit/impl/src/main/java/org/apache/isis/viewer/junit/internal/AnnotationInstaller.java b/component/viewer/junit/impl/src/main/java/org/apache/isis/viewer/junit/internal/AnnotationInstaller.java
index f61c20e..7c78163 100644
--- a/component/viewer/junit/impl/src/main/java/org/apache/isis/viewer/junit/internal/AnnotationInstaller.java
+++ b/component/viewer/junit/impl/src/main/java/org/apache/isis/viewer/junit/internal/AnnotationInstaller.java
@@ -18,6 +18,7 @@
  */
 package org.apache.isis.viewer.junit.internal;
 
+import org.apache.isis.core.objectstore.InMemoryPersistenceMechanismInstaller;
 import org.apache.isis.core.runtime.authentication.AuthenticationManagerInstaller;
 import org.apache.isis.core.runtime.authorization.AuthorizationManagerInstaller;
 import org.apache.isis.core.runtime.installerregistry.installerapi.PersistenceMechanismInstaller;
@@ -79,7 +80,7 @@ public class AnnotationInstaller {
         if (annotation != null) {
             return addPersistorRepresentedBy(annotation);
         } else {
-            return new InMemoryPersistenceMechanismInstallerWithinJunit();
+            return new InMemoryPersistenceMechanismInstaller();
         }
 
     }

http://git-wip-us.apache.org/repos/asf/isis/blob/55f931ae/component/viewer/junit/impl/src/main/java/org/apache/isis/viewer/junit/internal/InMemoryPersistenceMechanismInstallerWithinJunit.java
----------------------------------------------------------------------
diff --git a/component/viewer/junit/impl/src/main/java/org/apache/isis/viewer/junit/internal/InMemoryPersistenceMechanismInstallerWithinJunit.java b/component/viewer/junit/impl/src/main/java/org/apache/isis/viewer/junit/internal/InMemoryPersistenceMechanismInstallerWithinJunit.java
deleted file mode 100644
index 6436f18..0000000
--- a/component/viewer/junit/impl/src/main/java/org/apache/isis/viewer/junit/internal/InMemoryPersistenceMechanismInstallerWithinJunit.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- *  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.junit.internal;
-
-import org.apache.isis.applib.DomainObjectContainer;
-import org.apache.isis.core.commons.config.IsisConfiguration;
-import org.apache.isis.core.objectstore.InMemoryPersistenceMechanismInstaller;
-import org.apache.isis.progmodel.wrapper.metamodel.DomainObjectContainerWrapperFactory;
-
-public class InMemoryPersistenceMechanismInstallerWithinJunit extends InMemoryPersistenceMechanismInstaller {
-
-    /**
-     * Returns a {@link DomainObjectContainerHeadlessViewer}.
-     */
-    @Override
-    public DomainObjectContainer createContainer(final IsisConfiguration configuration) {
-        return new DomainObjectContainerWrapperFactory();
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/isis/blob/55f931ae/component/viewer/junit/impl/src/test/java/org/apache/isis/viewer/junit/AbstractTest.java
----------------------------------------------------------------------
diff --git a/component/viewer/junit/impl/src/test/java/org/apache/isis/viewer/junit/AbstractTest.java b/component/viewer/junit/impl/src/test/java/org/apache/isis/viewer/junit/AbstractTest.java
index bf87851..85a0027 100644
--- a/component/viewer/junit/impl/src/test/java/org/apache/isis/viewer/junit/AbstractTest.java
+++ b/component/viewer/junit/impl/src/test/java/org/apache/isis/viewer/junit/AbstractTest.java
@@ -25,6 +25,7 @@ import org.junit.runner.RunWith;
 
 import org.apache.isis.applib.DomainObjectContainer;
 import org.apache.isis.applib.services.wrapper.WrapperFactory;
+import org.apache.isis.core.wrapper.WrapperFactoryDefault;
 import org.apache.isis.viewer.junit.sample.domain.Country;
 import org.apache.isis.viewer.junit.sample.domain.Customer;
 import org.apache.isis.viewer.junit.sample.domain.Product;
@@ -39,7 +40,7 @@ import org.apache.isis.viewer.junit.sample.service.ProductRepository;
 
 @RunWith(IsisTestRunner.class)
 @Fixtures({ @Fixture(CountriesFixture.class), @Fixture(ProductsFixture.class), @Fixture(CustomersFixture.class), @Fixture(CustomerOrdersFixture.class) })
-@Services({ @Service(CountryRepository.class), @Service(ProductRepository.class), @Service(CustomerRepository.class), @Service(OrderRepository.class) })
+@Services({ @Service(CountryRepository.class), @Service(ProductRepository.class), @Service(CustomerRepository.class), @Service(OrderRepository.class), @Service(WrapperFactoryDefault.class) })
 public abstract class AbstractTest {
 
     protected Customer custJsDO;

http://git-wip-us.apache.org/repos/asf/isis/blob/55f931ae/component/viewer/junit/tck/src/test/java/junit/AbstractTest.java
----------------------------------------------------------------------
diff --git a/component/viewer/junit/tck/src/test/java/junit/AbstractTest.java b/component/viewer/junit/tck/src/test/java/junit/AbstractTest.java
index 9924061..bf15176 100644
--- a/component/viewer/junit/tck/src/test/java/junit/AbstractTest.java
+++ b/component/viewer/junit/tck/src/test/java/junit/AbstractTest.java
@@ -27,6 +27,7 @@ import org.apache.isis.applib.DomainObjectContainer;
 import org.apache.isis.applib.services.wrapper.WrapperFactory;
 import org.apache.isis.applib.services.wrapper.WrapperObject;
 import org.apache.isis.core.tck.dom.scalars.PrimitiveValuedEntityRepository;
+import org.apache.isis.core.wrapper.WrapperFactoryDefault;
 import org.apache.isis.viewer.junit.ConfigDir;
 import org.apache.isis.viewer.junit.IsisTestRunner;
 import org.apache.isis.viewer.junit.Service;
@@ -34,7 +35,7 @@ import org.apache.isis.viewer.junit.Services;
 
 @RunWith(IsisTestRunner.class)
 @ConfigDir("../../viewer/dnd-tck/src/main/resources")
-@Services({ @Service(PrimitiveValuedEntityRepository.class) })
+@Services({ @Service(PrimitiveValuedEntityRepository.class), @Service(WrapperFactoryDefault.class) })
 public abstract class AbstractTest {
 
     private DomainObjectContainer domainObjectContainer;

http://git-wip-us.apache.org/repos/asf/isis/blob/55f931ae/core/applib/src/main/java/org/apache/isis/applib/services/wrapper/WrapperFactory.java
----------------------------------------------------------------------
diff --git 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
index 317bbaa..e347c82 100644
--- 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
@@ -22,6 +22,7 @@ package org.apache.isis.applib.services.wrapper;
 import java.util.List;
 
 import org.apache.isis.applib.annotation.Hidden;
+import org.apache.isis.applib.annotation.Programmatic;
 import org.apache.isis.applib.events.InteractionEvent;
 import org.apache.isis.applib.services.wrapper.listeners.InteractionListener;
 
@@ -58,6 +59,7 @@ import org.apache.isis.applib.services.wrapper.listeners.InteractionListener;
  * <p>
  * An exception will be thrown if any other methods are thrown.
  */
+@Hidden
 public interface WrapperFactory {
 
     /**
@@ -79,6 +81,7 @@ public interface WrapperFactory {
      * 
      * @see #addInteractionListener(InteractionListener)
      */
+    @Programmatic
     <T> T wrap(T domainObject);
 
     /**
@@ -89,6 +92,7 @@ public interface WrapperFactory {
      * Otherwise, will do all the validations (raise exceptions as required
      * etc.), but doesn't modify the model.
      */
+    @Programmatic
     <T> T wrap(T domainObject, ExecutionMode mode);
 
     /**
@@ -99,12 +103,14 @@ public interface WrapperFactory {
      *            - object that might or might not be a wrapper.
      * @return
      */
+    @Programmatic
     <T> boolean isWrapper(T possibleWrapper);
 
     /**
      * All {@link InteractionListener}s that have been registered using
      * {@link #addInteractionListener(InteractionListener)}.
      */
+    @Programmatic
     List<InteractionListener> getListeners();
 
     /**
@@ -120,6 +126,7 @@ public interface WrapperFactory {
      * @param listener
      * @return
      */
+    @Programmatic
     public boolean addInteractionListener(InteractionListener listener);
 
     /**
@@ -135,8 +142,10 @@ public interface WrapperFactory {
      * @param listener
      * @return
      */
+    @Programmatic
     public boolean removeInteractionListener(InteractionListener listener);
 
+    @Programmatic
     public void notifyListeners(InteractionEvent ev);
 
 }

http://git-wip-us.apache.org/repos/asf/isis/blob/55f931ae/core/metamodel/src/main/java/org/apache/isis/core/commons/authentication/AuthenticationSessionProvider.java
----------------------------------------------------------------------
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/commons/authentication/AuthenticationSessionProvider.java b/core/metamodel/src/main/java/org/apache/isis/core/commons/authentication/AuthenticationSessionProvider.java
index d7cb01c..e302119 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/commons/authentication/AuthenticationSessionProvider.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/commons/authentication/AuthenticationSessionProvider.java
@@ -16,6 +16,7 @@
  */
 package org.apache.isis.core.commons.authentication;
 
+import org.apache.isis.applib.annotation.Programmatic;
 import org.apache.isis.core.commons.components.Injectable;
 
 public interface AuthenticationSessionProvider extends Injectable {

http://git-wip-us.apache.org/repos/asf/isis/blob/55f931ae/core/metamodel/src/main/java/org/apache/isis/core/commons/authentication/AuthenticationSessionProviderAware.java
----------------------------------------------------------------------
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/commons/authentication/AuthenticationSessionProviderAware.java b/core/metamodel/src/main/java/org/apache/isis/core/commons/authentication/AuthenticationSessionProviderAware.java
index 9794d9b..39bd5a4 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/commons/authentication/AuthenticationSessionProviderAware.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/commons/authentication/AuthenticationSessionProviderAware.java
@@ -19,8 +19,11 @@
 
 package org.apache.isis.core.commons.authentication;
 
+import org.apache.isis.applib.annotation.Programmatic;
+
 public interface AuthenticationSessionProviderAware {
 
+    @Programmatic
     public void setAuthenticationSessionProvider(AuthenticationSessionProvider authenticationSessionProvider);
 
 }

http://git-wip-us.apache.org/repos/asf/isis/blob/55f931ae/core/metamodel/src/main/java/org/apache/isis/core/commons/components/Injectable.java
----------------------------------------------------------------------
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/commons/components/Injectable.java b/core/metamodel/src/main/java/org/apache/isis/core/commons/components/Injectable.java
index 225d77d..8356cbc 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/commons/components/Injectable.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/commons/components/Injectable.java
@@ -19,6 +19,8 @@
 
 package org.apache.isis.core.commons.components;
 
+import org.apache.isis.applib.annotation.Programmatic;
+
 public interface Injectable {
 
     /**

http://git-wip-us.apache.org/repos/asf/isis/blob/55f931ae/core/metamodel/src/main/java/org/apache/isis/core/metamodel/adapter/ObjectPersistorAware.java
----------------------------------------------------------------------
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/adapter/ObjectPersistorAware.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/adapter/ObjectPersistorAware.java
index 59e79d2..1a6cce3 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/adapter/ObjectPersistorAware.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/adapter/ObjectPersistorAware.java
@@ -19,8 +19,11 @@
 
 package org.apache.isis.core.metamodel.adapter;
 
+import org.apache.isis.applib.annotation.Programmatic;
+
 public interface ObjectPersistorAware {
 
+    @Programmatic
     public void setObjectPersistor(final ObjectPersistor objectPersistor);
 
 }

http://git-wip-us.apache.org/repos/asf/isis/blob/55f931ae/core/metamodel/src/main/java/org/apache/isis/core/metamodel/adapter/mgr/AdapterManagerAware.java
----------------------------------------------------------------------
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/adapter/mgr/AdapterManagerAware.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/adapter/mgr/AdapterManagerAware.java
index d33c75e..ee52b48 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/adapter/mgr/AdapterManagerAware.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/adapter/mgr/AdapterManagerAware.java
@@ -19,9 +19,12 @@
 
 package org.apache.isis.core.metamodel.adapter.mgr;
 
+import org.apache.isis.applib.annotation.Programmatic;
+
 
 public interface AdapterManagerAware {
 
+    @Programmatic
     public void setAdapterManager(final AdapterManager adapterManager);
 
 }

http://git-wip-us.apache.org/repos/asf/isis/blob/55f931ae/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/SpecificationLoaderAware.java
----------------------------------------------------------------------
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/SpecificationLoaderAware.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/SpecificationLoaderAware.java
index 9132f8c..5d925b2 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/SpecificationLoaderAware.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/SpecificationLoaderAware.java
@@ -19,8 +19,11 @@
 
 package org.apache.isis.core.metamodel.spec;
 
+import org.apache.isis.applib.annotation.Programmatic;
+
 public interface SpecificationLoaderAware {
 
+    @Programmatic
     public void setSpecificationLookup(final SpecificationLoader specificationLookup);
 
 }

http://git-wip-us.apache.org/repos/asf/isis/blob/55f931ae/core/wrapper/src/main/java/org/apache/isis/core/wrapper/WrapperFactoryDefault.java
----------------------------------------------------------------------
diff --git a/core/wrapper/src/main/java/org/apache/isis/core/wrapper/WrapperFactoryDefault.java b/core/wrapper/src/main/java/org/apache/isis/core/wrapper/WrapperFactoryDefault.java
new file mode 100644
index 0000000..b0cc78a
--- /dev/null
+++ b/core/wrapper/src/main/java/org/apache/isis/core/wrapper/WrapperFactoryDefault.java
@@ -0,0 +1,280 @@
+/*
+ *  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 org.apache.isis.applib.annotation.Hidden;
+import org.apache.isis.applib.annotation.Programmatic;
+import org.apache.isis.applib.events.ActionArgumentEvent;
+import org.apache.isis.applib.events.ActionInvocationEvent;
+import org.apache.isis.applib.events.ActionUsabilityEvent;
+import org.apache.isis.applib.events.ActionVisibilityEvent;
+import org.apache.isis.applib.events.CollectionAccessEvent;
+import org.apache.isis.applib.events.CollectionAddToEvent;
+import org.apache.isis.applib.events.CollectionMethodEvent;
+import org.apache.isis.applib.events.CollectionRemoveFromEvent;
+import org.apache.isis.applib.events.CollectionUsabilityEvent;
+import org.apache.isis.applib.events.CollectionVisibilityEvent;
+import org.apache.isis.applib.events.InteractionEvent;
+import org.apache.isis.applib.events.ObjectTitleEvent;
+import org.apache.isis.applib.events.ObjectValidityEvent;
+import org.apache.isis.applib.events.PropertyAccessEvent;
+import org.apache.isis.applib.events.PropertyModifyEvent;
+import org.apache.isis.applib.events.PropertyUsabilityEvent;
+import org.apache.isis.applib.events.PropertyVisibilityEvent;
+import org.apache.isis.applib.services.wrapper.WrapperFactory;
+import org.apache.isis.applib.services.wrapper.WrapperObject;
+import org.apache.isis.applib.services.wrapper.listeners.InteractionListener;
+import org.apache.isis.core.commons.authentication.AuthenticationSessionProvider;
+import org.apache.isis.core.commons.authentication.AuthenticationSessionProviderAware;
+import org.apache.isis.core.metamodel.adapter.ObjectPersistor;
+import org.apache.isis.core.metamodel.adapter.ObjectPersistorAware;
+import org.apache.isis.core.metamodel.adapter.mgr.AdapterManager;
+import org.apache.isis.core.metamodel.adapter.mgr.AdapterManagerAware;
+import org.apache.isis.core.metamodel.spec.SpecificationLoader;
+import org.apache.isis.core.metamodel.spec.SpecificationLoaderAware;
+import org.apache.isis.core.wrapper.internal.InteractionEventDispatcher;
+import org.apache.isis.core.wrapper.internal.InteractionEventDispatcherTypeSafe;
+import org.apache.isis.core.wrapper.internal.Proxy;
+
+public class WrapperFactoryDefault implements WrapperFactory, AuthenticationSessionProviderAware, SpecificationLoaderAware, AdapterManagerAware, ObjectPersistorAware {
+
+    private final List<InteractionListener> listeners = new ArrayList<InteractionListener>();
+    private final Map<Class<? extends InteractionEvent>, InteractionEventDispatcher> dispatchersByEventClass = new HashMap<Class<? extends InteractionEvent>, InteractionEventDispatcher>();
+
+    private AuthenticationSessionProvider authenticationSessionProvider;
+    private SpecificationLoader specificationLookup;
+    private AdapterManager adapterManager;
+    private ObjectPersistor objectPersistor;
+
+    public WrapperFactoryDefault() {
+        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);
+                }
+            }
+        });
+    }
+
+    // /////////////////////////////////////////////////////////////
+    // Wrapped
+    // /////////////////////////////////////////////////////////////
+
+    @Override
+    public <T> T wrap(final T domainObject) {
+        return wrap(domainObject, ExecutionMode.EXECUTE);
+    }
+
+    @Override
+    public <T> T wrap(final T domainObject, final ExecutionMode mode) {
+        if (isWrapper(domainObject)) {
+            return domainObject;
+        }
+        return Proxy.proxy(domainObject, this, mode, authenticationSessionProvider, specificationLookup, adapterManager, objectPersistor);
+    }
+
+    @Override
+    public boolean isWrapper(final Object possibleWrapper) {
+        return possibleWrapper instanceof WrapperObject;
+    }
+
+    // /////////////////////////////////////////////////////////////
+    // 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);
+    }
+
+    // /////////////////////////////////////////////////////////////
+    // Listeners
+    // /////////////////////////////////////////////////////////////
+
+    @Programmatic
+    @Override
+    public void setAuthenticationSessionProvider(final AuthenticationSessionProvider authenticationSessionProvider) {
+        this.authenticationSessionProvider = authenticationSessionProvider;
+    }
+
+    @Programmatic
+    @Override
+    public void setAdapterManager(final AdapterManager adapterManager) {
+        this.adapterManager = adapterManager;
+    }
+
+    @Programmatic
+    @Override
+    public void setSpecificationLookup(final SpecificationLoader specificationLookup) {
+        this.specificationLookup = specificationLookup;
+    }
+
+    @Programmatic
+    @Override
+    public void setObjectPersistor(final ObjectPersistor objectPersistor) {
+        this.objectPersistor = objectPersistor;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/55f931ae/core/wrapper/src/main/java/org/apache/isis/core/wrapper/internal/AbstractCollectionInvocationHandler.java
----------------------------------------------------------------------
diff --git a/core/wrapper/src/main/java/org/apache/isis/core/wrapper/internal/AbstractCollectionInvocationHandler.java b/core/wrapper/src/main/java/org/apache/isis/core/wrapper/internal/AbstractCollectionInvocationHandler.java
new file mode 100644
index 0000000..e5581d4
--- /dev/null
+++ b/core/wrapper/src/main/java/org/apache/isis/core/wrapper/internal/AbstractCollectionInvocationHandler.java
@@ -0,0 +1,89 @@
+/*
+ *  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.internal;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.isis.applib.events.CollectionMethodEvent;
+import org.apache.isis.applib.events.InteractionEvent;
+import org.apache.isis.core.metamodel.spec.feature.OneToManyAssociation;
+
+abstract class AbstractCollectionInvocationHandler<T, C> extends DelegatingInvocationHandlerDefault<C> {
+
+    private final List<Method> interceptedMethods = new ArrayList<Method>();
+    private final List<Method> vetoedMethods = new ArrayList<Method>();
+    private final String collectionName;
+    private final OneToManyAssociation oneToManyAssociation;
+    private final T domainObject;
+
+    public AbstractCollectionInvocationHandler(final C collectionOrMapToProxy, final String collectionName, final DomainObjectInvocationHandler<T> handler, final OneToManyAssociation otma) {
+        super(collectionOrMapToProxy, handler.getHeadlessViewer(), handler.getExecutionMode());
+        this.collectionName = collectionName;
+        this.oneToManyAssociation = otma;
+        this.domainObject = handler.getDelegate();
+    }
+
+    protected Method intercept(final Method method) {
+        this.interceptedMethods.add(method);
+        return method;
+    }
+
+    protected Method veto(final Method method) {
+        this.vetoedMethods.add(method);
+        return method;
+    }
+
+    public String getCollectionName() {
+        return collectionName;
+    }
+
+    public OneToManyAssociation getCollection() {
+        return oneToManyAssociation;
+    }
+
+    public T getDomainObject() {
+        return domainObject;
+    }
+
+    @Override
+    public Object invoke(final Object collectionObject, final Method method, final Object[] args) throws Throwable {
+
+        // delegate
+        final Object returnValueObj = delegate(method, args);
+
+        if (interceptedMethods.contains(method)) {
+
+            resolveIfRequired(domainObject);
+
+            final InteractionEvent ev = new CollectionMethodEvent(getDelegate(), getCollection().getIdentifier(), getDomainObject(), method.getName(), args, returnValueObj);
+            notifyListeners(ev);
+            return returnValueObj;
+        }
+
+        if (vetoedMethods.contains(method)) {
+            throw new UnsupportedOperationException(String.format("Method '%s' may not be called directly.", method.getName()));
+        }
+
+        return returnValueObj;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/55f931ae/core/wrapper/src/main/java/org/apache/isis/core/wrapper/internal/CgLibClassProxyFactory.java
----------------------------------------------------------------------
diff --git a/core/wrapper/src/main/java/org/apache/isis/core/wrapper/internal/CgLibClassProxyFactory.java b/core/wrapper/src/main/java/org/apache/isis/core/wrapper/internal/CgLibClassProxyFactory.java
new file mode 100644
index 0000000..8fb2b66
--- /dev/null
+++ b/core/wrapper/src/main/java/org/apache/isis/core/wrapper/internal/CgLibClassProxyFactory.java
@@ -0,0 +1,71 @@
+/*
+ *  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.internal;
+
+import java.lang.reflect.InvocationHandler;
+
+import net.sf.cglib.proxy.Callback;
+import net.sf.cglib.proxy.Enhancer;
+import net.sf.cglib.proxy.Factory;
+import net.sf.cglib.proxy.MethodInterceptor;
+
+import org.apache.isis.applib.services.wrapper.WrapperObject;
+
+/**
+ * Factory generating a mock for a class.
+ * <p>
+ * Note that this class is stateful
+ */
+public class CgLibClassProxyFactory<T> implements IProxyFactory<T> {
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public T createProxy(final T toProxy, final InvocationHandler handler) {
+        final Class<T> proxyClass = (Class<T>) toProxy.getClass();
+        return createProxy(proxyClass, handler);
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public T createProxy(final Class<T> toProxyClass, final InvocationHandler handler) {
+
+        final MethodInterceptor interceptor = new InvocationHandlerMethodInterceptor(handler);
+
+        // Create the proxy
+        final Enhancer enhancer = new Enhancer();
+        enhancer.setSuperclass(toProxyClass);
+        enhancer.setInterfaces(new Class[] { WrapperObject.class });
+        enhancer.setCallbackType(interceptor.getClass());
+
+        final Class<?> enhancedClass = enhancer.createClass();
+
+        Enhancer.registerCallbacks(enhancedClass, new Callback[] { interceptor });
+
+        Factory factory;
+        try {
+            factory = (Factory) ClassInstantiatorFactoryCE.getInstantiator().newInstance(enhancedClass);
+        } catch (final InstantiationException e) {
+            throw new RuntimeException("Fail to instantiate mock for " + toProxyClass + " on " + ClassInstantiatorFactoryCE.getJVM() + " JVM");
+        }
+
+        return (T) factory;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/55f931ae/core/wrapper/src/main/java/org/apache/isis/core/wrapper/internal/CgLibProxy.java
----------------------------------------------------------------------
diff --git a/core/wrapper/src/main/java/org/apache/isis/core/wrapper/internal/CgLibProxy.java b/core/wrapper/src/main/java/org/apache/isis/core/wrapper/internal/CgLibProxy.java
new file mode 100644
index 0000000..ec432cc
--- /dev/null
+++ b/core/wrapper/src/main/java/org/apache/isis/core/wrapper/internal/CgLibProxy.java
@@ -0,0 +1,74 @@
+/*
+ *  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.internal;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import net.sf.cglib.proxy.Enhancer;
+import net.sf.cglib.proxy.Factory;
+
+import org.apache.isis.applib.services.wrapper.WrapperObject;
+import org.apache.isis.core.metamodel.specloader.classsubstitutor.CglibEnhanced;
+
+public class CgLibProxy<T> {
+
+    private final DelegatingInvocationHandler<T> handler;
+
+    public CgLibProxy(final DelegatingInvocationHandler<T> handler) {
+        this.handler = handler;
+    }
+
+    @SuppressWarnings("unchecked")
+    public T proxy() {
+
+        final T toProxy = handler.getDelegate();
+
+        // handle if already proxied using cglib.
+        if (CglibEnhanced.class.isAssignableFrom(toProxy.getClass())) {
+
+            handler.setResolveObjectChangedEnabled(true);
+
+            final Class<? extends Object> enhancedClass = toProxy.getClass();
+            final Class<? extends Object> origSuperclass = toProxy.getClass().getSuperclass();
+
+            final List<Class> interfaces = new ArrayList<Class>();
+            interfaces.addAll(Arrays.asList(enhancedClass.getInterfaces()));
+            interfaces.remove(Factory.class); // if there.
+            interfaces.add(WrapperObject.class);
+
+            InvocationHandlerMethodInterceptor interceptor = new InvocationHandlerMethodInterceptor(handler);
+            return (T) Enhancer.create(origSuperclass, interfaces.toArray(new Class[] {}), interceptor);
+        }
+
+        final Class<T> clazz = (Class<T>) toProxy.getClass();
+
+        T proxy = null;
+        try {
+            final IProxyFactory<T> proxyFactory = clazz.isInterface() ? new JavaProxyFactory<T>() : new CgLibClassProxyFactory<T>();
+            proxy = proxyFactory.createProxy(clazz, handler);
+        } catch (final RuntimeExceptionWrapper e) {
+            throw (RuntimeException) e.getRuntimeException().fillInStackTrace();
+        }
+        return proxy;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/55f931ae/core/wrapper/src/main/java/org/apache/isis/core/wrapper/internal/ClassInstantiatorFactoryCE.java
----------------------------------------------------------------------
diff --git a/core/wrapper/src/main/java/org/apache/isis/core/wrapper/internal/ClassInstantiatorFactoryCE.java b/core/wrapper/src/main/java/org/apache/isis/core/wrapper/internal/ClassInstantiatorFactoryCE.java
new file mode 100644
index 0000000..465daf2
--- /dev/null
+++ b/core/wrapper/src/main/java/org/apache/isis/core/wrapper/internal/ClassInstantiatorFactoryCE.java
@@ -0,0 +1,65 @@
+/*
+ *  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.internal;
+
+/**
+ * Factory returning a {@link IClassInstantiatorCE}for the current JVM
+ */
+class ClassInstantiatorFactoryCE {
+
+    private static IClassInstantiatorCE instantiator = new ObjenesisClassInstantiatorCE();
+
+    // ///CLOVER:OFF
+    private ClassInstantiatorFactoryCE() {
+    }
+
+    // ///CLOVER:ON
+
+    /**
+     * Returns the current JVM as specified in the Systtem properties
+     * 
+     * @return current JVM
+     */
+    public static String getJVM() {
+        return System.getProperty("java.vm.vendor");
+    }
+
+    /**
+     * Returns the current JVM specification version (1.5, 1.4, 1.3)
+     * 
+     * @return current JVM specification version
+     */
+    public static String getJVMSpecificationVersion() {
+        return System.getProperty("java.specification.version");
+    }
+
+    public static boolean is1_3Specifications() {
+        return getJVMSpecificationVersion().equals("1.3");
+    }
+
+    /**
+     * Returns a class instantiator suitable for the current JVM
+     * 
+     * @return a class instantiator usable on the current JVM
+     */
+    public static IClassInstantiatorCE getInstantiator() {
+        return instantiator;
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/55f931ae/core/wrapper/src/main/java/org/apache/isis/core/wrapper/internal/CollectionInvocationHandler.java
----------------------------------------------------------------------
diff --git a/core/wrapper/src/main/java/org/apache/isis/core/wrapper/internal/CollectionInvocationHandler.java b/core/wrapper/src/main/java/org/apache/isis/core/wrapper/internal/CollectionInvocationHandler.java
new file mode 100644
index 0000000..979df43
--- /dev/null
+++ b/core/wrapper/src/main/java/org/apache/isis/core/wrapper/internal/CollectionInvocationHandler.java
@@ -0,0 +1,54 @@
+/*
+ *  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.internal;
+
+import static org.apache.isis.core.commons.lang.MethodUtils.getMethod;
+
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.isis.core.metamodel.spec.feature.OneToManyAssociation;
+
+class CollectionInvocationHandler<T, R> extends AbstractCollectionInvocationHandler<T, R> {
+
+    public CollectionInvocationHandler(final R collectionToProxy, final String collectionName, final DomainObjectInvocationHandler<T> handler, final OneToManyAssociation otma) {
+        super(collectionToProxy, collectionName, handler, otma);
+
+        try {
+            intercept(getMethod(collectionToProxy, "contains", Object.class));
+            intercept(getMethod(collectionToProxy, "size"));
+            intercept(getMethod(collectionToProxy, "isEmpty"));
+            if (collectionToProxy instanceof List) {
+                intercept(getMethod(collectionToProxy, "get", int.class));
+            }
+            veto(getMethod(collectionToProxy, "add", Object.class));
+            veto(getMethod(collectionToProxy, "remove", Object.class));
+            veto(getMethod(collectionToProxy, "addAll", Collection.class));
+            veto(getMethod(collectionToProxy, "removeAll", Collection.class));
+            veto(getMethod(collectionToProxy, "retainAll", Collection.class));
+            veto(getMethod(collectionToProxy, "clear"));
+        } catch (final NoSuchMethodException e) {
+            // ///CLOVER:OFF
+            throw new RuntimeException("A Collection method could not be found: " + e.getMessage());
+            // ///CLOVER:ON
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/55f931ae/core/wrapper/src/main/java/org/apache/isis/core/wrapper/internal/DelegatingInvocationHandler.java
----------------------------------------------------------------------
diff --git a/core/wrapper/src/main/java/org/apache/isis/core/wrapper/internal/DelegatingInvocationHandler.java b/core/wrapper/src/main/java/org/apache/isis/core/wrapper/internal/DelegatingInvocationHandler.java
new file mode 100644
index 0000000..44b3204
--- /dev/null
+++ b/core/wrapper/src/main/java/org/apache/isis/core/wrapper/internal/DelegatingInvocationHandler.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.core.wrapper.internal;
+
+import java.lang.reflect.InvocationHandler;
+
+public interface DelegatingInvocationHandler<T> extends InvocationHandler {
+
+    T getDelegate();
+
+    public boolean isResolveObjectChangedEnabled();
+
+    public void setResolveObjectChangedEnabled(boolean resolveObjectChangedEnabled);
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/55f931ae/core/wrapper/src/main/java/org/apache/isis/core/wrapper/internal/DelegatingInvocationHandlerDefault.java
----------------------------------------------------------------------
diff --git a/core/wrapper/src/main/java/org/apache/isis/core/wrapper/internal/DelegatingInvocationHandlerDefault.java b/core/wrapper/src/main/java/org/apache/isis/core/wrapper/internal/DelegatingInvocationHandlerDefault.java
new file mode 100644
index 0000000..337853e
--- /dev/null
+++ b/core/wrapper/src/main/java/org/apache/isis/core/wrapper/internal/DelegatingInvocationHandlerDefault.java
@@ -0,0 +1,131 @@
+/*
+ *  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.internal;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+import org.apache.isis.applib.events.InteractionEvent;
+import org.apache.isis.applib.services.wrapper.WrapperFactory;
+import org.apache.isis.applib.services.wrapper.WrapperFactory.ExecutionMode;
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+import org.apache.isis.core.runtime.persistence.container.DomainObjectContainerObjectChanged;
+import org.apache.isis.core.runtime.persistence.container.DomainObjectContainerResolve;
+
+public class DelegatingInvocationHandlerDefault<T> implements DelegatingInvocationHandler<T> {
+
+    private final T delegate;
+    protected final WrapperFactory wrapperFactory;
+    private final ExecutionMode executionMode;
+
+    protected final Method equalsMethod;
+    protected final Method hashCodeMethod;
+    protected final Method toStringMethod;
+
+    private final DomainObjectContainerObjectChanged domainObjectContainerObjectChanged;
+    private final DomainObjectContainerResolve domainObjectContainerResolve;
+
+    private boolean resolveObjectChangedEnabled;
+
+    public DelegatingInvocationHandlerDefault(final T delegate, final WrapperFactory headlessViewer, final ExecutionMode executionMode) {
+        if (delegate == null) {
+            throw new IllegalArgumentException("delegate must not be null");
+        }
+        this.delegate = delegate;
+        this.wrapperFactory = headlessViewer;
+        this.executionMode = executionMode;
+
+        this.domainObjectContainerResolve = new DomainObjectContainerResolve();
+        this.domainObjectContainerObjectChanged = new DomainObjectContainerObjectChanged();
+
+        try {
+            equalsMethod = delegate.getClass().getMethod("equals", new Class[] { Object.class });
+            hashCodeMethod = delegate.getClass().getMethod("hashCode", new Class[] {});
+            toStringMethod = delegate.getClass().getMethod("toString", new Class[] {});
+        } catch (final NoSuchMethodException e) {
+            // ///CLOVER:OFF
+            throw new RuntimeException("An Object method could not be found: " + e.getMessage());
+            // ///CLOVER:ON
+        }
+    }
+
+    @Override
+    public boolean isResolveObjectChangedEnabled() {
+        return resolveObjectChangedEnabled;
+    }
+
+    @Override
+    public void setResolveObjectChangedEnabled(final boolean resolveObjectChangedEnabled) {
+        this.resolveObjectChangedEnabled = resolveObjectChangedEnabled;
+    }
+
+    protected void resolveIfRequired(final ObjectAdapter targetAdapter) {
+        resolveIfRequired(targetAdapter.getObject());
+    }
+
+    protected void resolveIfRequired(final Object domainObject) {
+        if (resolveObjectChangedEnabled) {
+            domainObjectContainerResolve.resolve(domainObject);
+        }
+    }
+
+    protected void objectChangedIfRequired(final ObjectAdapter targetAdapter) {
+        objectChangedIfRequired(targetAdapter.getObject());
+    }
+
+    protected void objectChangedIfRequired(final Object domainObject) {
+        if (resolveObjectChangedEnabled) {
+            domainObjectContainerObjectChanged.objectChanged(domainObject);
+        }
+    }
+
+    public WrapperFactory getHeadlessViewer() {
+        return wrapperFactory;
+    }
+
+    @Override
+    public T getDelegate() {
+        return delegate;
+    }
+
+    public ExecutionMode getExecutionMode() {
+        return executionMode;
+    }
+
+    protected Object delegate(final Method method, final Object[] args) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
+
+        return method.invoke(getDelegate(), args);
+    }
+
+    protected boolean isObjectMethod(final Method method) {
+        return toStringMethod.equals(method) || hashCodeMethod.equals(method) || equalsMethod.equals(method);
+    }
+
+    @Override
+    public Object invoke(final Object object, final Method method, final Object[] args) throws Throwable {
+        return method.invoke(object, args);
+    }
+
+    protected InteractionEvent notifyListeners(final InteractionEvent interactionEvent) {
+        wrapperFactory.notifyListeners(interactionEvent);
+        return interactionEvent;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/55f931ae/core/wrapper/src/main/java/org/apache/isis/core/wrapper/internal/DomainObjectInvocationHandler.java
----------------------------------------------------------------------
diff --git a/core/wrapper/src/main/java/org/apache/isis/core/wrapper/internal/DomainObjectInvocationHandler.java b/core/wrapper/src/main/java/org/apache/isis/core/wrapper/internal/DomainObjectInvocationHandler.java
new file mode 100644
index 0000000..6caf76e
--- /dev/null
+++ b/core/wrapper/src/main/java/org/apache/isis/core/wrapper/internal/DomainObjectInvocationHandler.java
@@ -0,0 +1,645 @@
+/*
+ *  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.internal;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.isis.applib.annotation.Where;
+import org.apache.isis.applib.events.CollectionAccessEvent;
+import org.apache.isis.applib.events.InteractionEvent;
+import org.apache.isis.applib.events.ObjectTitleEvent;
+import org.apache.isis.applib.events.PropertyAccessEvent;
+import org.apache.isis.applib.events.UsabilityEvent;
+import org.apache.isis.applib.events.ValidityEvent;
+import org.apache.isis.applib.events.VisibilityEvent;
+import org.apache.isis.applib.filter.Filter;
+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;
+import org.apache.isis.applib.services.wrapper.WrapperObject;
+import org.apache.isis.applib.services.wrapper.WrapperFactory.ExecutionMode;
+import org.apache.isis.core.commons.authentication.AuthenticationSession;
+import org.apache.isis.core.commons.authentication.AuthenticationSessionProvider;
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+import org.apache.isis.core.metamodel.adapter.ObjectPersistor;
+import org.apache.isis.core.metamodel.adapter.mgr.AdapterManager;
+import org.apache.isis.core.metamodel.adapter.util.AdapterUtils;
+import org.apache.isis.core.metamodel.consent.Consent;
+import org.apache.isis.core.metamodel.consent.InteractionInvocationMethod;
+import org.apache.isis.core.metamodel.consent.InteractionResult;
+import org.apache.isis.core.metamodel.facetapi.Facet;
+import org.apache.isis.core.metamodel.facets.ImperativeFacet;
+import org.apache.isis.core.metamodel.facets.accessor.PropertyOrCollectionAccessorFacet;
+import org.apache.isis.core.metamodel.facets.actions.choices.ActionChoicesFacet;
+import org.apache.isis.core.metamodel.facets.actions.defaults.ActionDefaultsFacet;
+import org.apache.isis.core.metamodel.facets.collections.modify.CollectionAddToFacet;
+import org.apache.isis.core.metamodel.facets.collections.modify.CollectionRemoveFromFacet;
+import org.apache.isis.core.metamodel.facets.param.choices.ActionParameterChoicesFacet;
+import org.apache.isis.core.metamodel.facets.properties.choices.PropertyChoicesFacet;
+import org.apache.isis.core.metamodel.facets.properties.defaults.PropertyDefaultFacet;
+import org.apache.isis.core.metamodel.facets.properties.modify.PropertyInitializationFacet;
+import org.apache.isis.core.metamodel.facets.properties.modify.PropertySetterFacet;
+import org.apache.isis.core.metamodel.interactions.ObjectTitleContext;
+import org.apache.isis.core.metamodel.spec.ObjectSpecification;
+import org.apache.isis.core.metamodel.spec.SpecificationLoader;
+import org.apache.isis.core.metamodel.spec.feature.ObjectAction;
+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.specimpl.dflt.ObjectSpecificationDefault;
+import org.apache.isis.core.progmodel.facets.actions.validate.method.ActionValidationFacetViaMethod;
+import org.apache.isis.core.progmodel.facets.collections.validate.CollectionValidateAddToFacetViaMethod;
+import org.apache.isis.core.progmodel.facets.collections.validate.CollectionValidateRemoveFromFacetViaMethod;
+import org.apache.isis.core.progmodel.facets.members.disabled.method.DisableForContextFacetViaMethod;
+import org.apache.isis.core.progmodel.facets.members.hidden.method.HideForContextFacetViaMethod;
+import org.apache.isis.core.progmodel.facets.properties.modify.PropertyClearFacetViaClearMethod;
+import org.apache.isis.core.progmodel.facets.properties.modify.PropertySetterFacetViaModifyMethod;
+import org.apache.isis.core.progmodel.facets.properties.validate.PropertyValidateFacetViaMethod;
+
+public class DomainObjectInvocationHandler<T> extends DelegatingInvocationHandlerDefault<T> {
+
+    private final Map<Method, Collection<?>> collectionViewObjectsByMethod = new HashMap<Method, Collection<?>>();
+    private final Map<Method, Map<?, ?>> mapViewObjectsByMethod = new HashMap<Method, Map<?, ?>>();
+
+    private final AuthenticationSessionProvider authenticationSessionProvider;
+    private final SpecificationLoader specificationLookup;
+    private final AdapterManager adapterManager;
+    private final ObjectPersistor objectPersistor;
+
+    /**
+     * The <tt>title()</tt> method; may be <tt>null</tt>.
+     */
+    protected Method titleMethod;
+
+    /**
+     * The <tt>save()</tt> method from {@link WrapperObject#save()}.
+     */
+    protected Method saveMethod;
+
+    /**
+     * The <tt>underlying()</tt> method from {@link WrapperObject#wrapped()}.
+     */
+    protected Method wrappedMethod;
+
+    public DomainObjectInvocationHandler(final T delegate, final WrapperFactory embeddedViewer, final ExecutionMode mode, final AuthenticationSessionProvider authenticationSessionProvider, final SpecificationLoader specificationLookup, final AdapterManager adapterManager,
+            final ObjectPersistor objectPersistor) {
+        super(delegate, embeddedViewer, mode);
+
+        this.authenticationSessionProvider = authenticationSessionProvider;
+        this.specificationLookup = specificationLookup;
+        this.adapterManager = adapterManager;
+        this.objectPersistor = objectPersistor;
+
+        try {
+            titleMethod = delegate.getClass().getMethod("title", new Class[] {});
+            saveMethod = WrapperObject.class.getMethod("save", new Class[] {});
+            wrappedMethod = WrapperObject.class.getMethod("wrapped", new Class[] {});
+        } catch (final NoSuchMethodException e) {
+        }
+    }
+
+    @Override
+    public Object invoke(final Object proxyObject, final Method method, final Object[] args) throws Throwable {
+
+        if (isObjectMethod(method)) {
+            return delegate(method, args);
+        }
+
+        final ObjectAdapter targetAdapter = getAdapterManager().getAdapterFor(getDelegate());
+
+        if (isTitleMethod(method)) {
+            return handleTitleMethod(method, args, targetAdapter);
+        }
+
+        final ObjectSpecification targetNoSpec = targetAdapter.getSpecification();
+
+        // save method, through the proxy
+        if (isSaveMethod(method)) {
+            return handleSaveMethod(getAuthenticationSession(), targetAdapter, targetNoSpec);
+        }
+
+        if (isUnderlyingMethod(method)) {
+            return getDelegate();
+        }
+
+        final ObjectMember objectMember = locateAndCheckMember(method);
+        final List<Facet> imperativeFacets = getImperativeFacets(objectMember, method);
+
+        final String memberName = objectMember.getName();
+
+        if (instanceOf(imperativeFacets, DisableForContextFacetViaMethod.class, HideForContextFacetViaMethod.class)) {
+            throw new UnsupportedOperationException(String.format("Cannot invoke supporting method '%s'", memberName));
+        }
+
+        final String methodName = method.getName();
+
+        if (instanceOf(imperativeFacets, ActionDefaultsFacet.class, PropertyDefaultFacet.class, ActionChoicesFacet.class, ActionParameterChoicesFacet.class, PropertyChoicesFacet.class)) {
+            return method.invoke(getDelegate(), args);
+        }
+
+        // for all members, check visibility and usability
+        checkVisibility(getAuthenticationSession(), targetAdapter, objectMember);
+
+        if (objectMember.isOneToOneAssociation()) {
+
+            if (instanceOf(imperativeFacets, PropertyValidateFacetViaMethod.class, PropertySetterFacetViaModifyMethod.class, PropertyClearFacetViaClearMethod.class)) {
+                throw new UnsupportedOperationException(String.format("Cannot invoke supporting method '%s'; use only property accessor/mutator", memberName));
+            }
+
+            final OneToOneAssociation otoa = (OneToOneAssociation) objectMember;
+            if (instanceOf(imperativeFacets, PropertyOrCollectionAccessorFacet.class)) {
+                return handleGetterMethodOnProperty(args, targetAdapter, otoa, methodName);
+            }
+            if (instanceOf(imperativeFacets, PropertySetterFacet.class, PropertyInitializationFacet.class)) {
+                checkUsability(getAuthenticationSession(), targetAdapter, objectMember);
+                return handleSetterMethodOnProperty(args, getAuthenticationSession(), targetAdapter, otoa, methodName);
+            }
+        }
+        if (objectMember.isOneToManyAssociation()) {
+
+            if (instanceOf(imperativeFacets, CollectionValidateAddToFacetViaMethod.class, CollectionValidateRemoveFromFacetViaMethod.class)) {
+                throw new UnsupportedOperationException(String.format("Cannot invoke supporting method '%s'; use only collection accessor/mutator", memberName));
+            }
+
+            final OneToManyAssociation otma = (OneToManyAssociation) objectMember;
+            if (instanceOf(imperativeFacets, PropertyOrCollectionAccessorFacet.class)) {
+                return handleGetterMethodOnCollection(method, args, targetAdapter, otma, memberName);
+            }
+            if (instanceOf(imperativeFacets, CollectionAddToFacet.class)) {
+                checkUsability(getAuthenticationSession(), targetAdapter, objectMember);
+                return handleCollectionAddToMethod(args, targetAdapter, otma, methodName);
+            }
+            if (instanceOf(imperativeFacets, CollectionRemoveFromFacet.class)) {
+                checkUsability(getAuthenticationSession(), targetAdapter, objectMember);
+                return handleCollectionRemoveFromMethod(args, targetAdapter, otma, methodName);
+            }
+        }
+
+        // filter out
+        if (instanceOf(imperativeFacets, PropertyOrCollectionAccessorFacet.class)) {
+            throw new UnsupportedOperationException(String.format("Can only invoke accessor on properties or collections; '%s' represents %s", methodName, decode(objectMember)));
+        }
+        if (instanceOf(imperativeFacets, PropertySetterFacet.class)) {
+            throw new UnsupportedOperationException(String.format("Can only invoke mutator on properties; '%s' represents %s", methodName, decode(objectMember)));
+        }
+        if (instanceOf(imperativeFacets, CollectionAddToFacet.class)) {
+            throw new UnsupportedOperationException(String.format("Can only invoke 'adder' on collections; '%s' represents %s", methodName, decode(objectMember)));
+        }
+        if (instanceOf(imperativeFacets, CollectionRemoveFromFacet.class)) {
+            throw new UnsupportedOperationException(String.format("Can only invoke 'remover' on collections; '%s' represents %s", methodName, decode(objectMember)));
+        }
+
+        if (objectMember instanceof ObjectAction) {
+
+            if (instanceOf(imperativeFacets, ActionValidationFacetViaMethod.class)) {
+                throw new UnsupportedOperationException(String.format("Cannot invoke supporting method '%s'; use only the 'invoke' method", memberName));
+            }
+
+            checkUsability(getAuthenticationSession(), targetAdapter, objectMember);
+
+            final ObjectAction noa = (ObjectAction) objectMember;
+            return handleActionMethod(args, getAuthenticationSession(), targetAdapter, noa, memberName);
+        }
+
+        throw new UnsupportedOperationException(String.format("Unknown member type '%s'", objectMember));
+    }
+
+    public List<Facet> getImperativeFacets(final ObjectMember objectMember, final Method method) {
+        final List<Facet> imperativeFacets = objectMember.getFacets(new Filter<Facet>() {
+            @Override
+            public boolean accept(final Facet facet) {
+                final ImperativeFacet imperativeFacet = asImperativeFacet(facet);
+                if (imperativeFacet == null) {
+                    return false;
+                }
+                return imperativeFacet.getMethods().contains(method);
+            }
+
+            private ImperativeFacet asImperativeFacet(final Facet facet) {
+                if (facet == null) {
+                    return null;
+                }
+                if (facet instanceof ImperativeFacet) {
+                    return (ImperativeFacet) facet;
+                }
+                return asImperativeFacet(facet.getUnderlyingFacet());
+            }
+        });
+
+        // there will be at least one
+        if (imperativeFacets.isEmpty()) {
+            throw new IllegalStateException("should be at least one imperative facet");
+        }
+        return imperativeFacets;
+    }
+
+    private static boolean instanceOf(final List<?> objects, final Class<?>... superTypes) {
+        for (final Class<?> superType : superTypes) {
+            for (final Object obj : objects) {
+                if (superType.isAssignableFrom(obj.getClass())) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    // /////////////////////////////////////////////////////////////////
+    // title
+    // /////////////////////////////////////////////////////////////////
+
+    private Object handleTitleMethod(final Method method, final Object[] args, final ObjectAdapter targetAdapter) throws IllegalAccessException, InvocationTargetException {
+
+        resolveIfRequired(targetAdapter);
+
+        final ObjectSpecification targetNoSpec = targetAdapter.getSpecification();
+        final ObjectTitleContext titleContext = targetNoSpec.createTitleInteractionContext(getAuthenticationSession(), InteractionInvocationMethod.BY_USER, targetAdapter);
+        final ObjectTitleEvent titleEvent = titleContext.createInteractionEvent();
+        notifyListeners(titleEvent);
+        return titleEvent.getTitle();
+    }
+
+    // /////////////////////////////////////////////////////////////////
+    // save
+    // /////////////////////////////////////////////////////////////////
+
+    private Object handleSaveMethod(final AuthenticationSession session, final ObjectAdapter targetAdapter, final ObjectSpecification targetNoSpec) {
+
+        final InteractionResult interactionResult = targetNoSpec.isValidResult(targetAdapter);
+        notifyListenersAndVetoIfRequired(interactionResult);
+
+        if (getExecutionMode() == ExecutionMode.EXECUTE) {
+            if (targetAdapter.isTransient()) {
+                getObjectPersistor().makePersistent(targetAdapter);
+            }
+        }
+        return null;
+    }
+
+    // /////////////////////////////////////////////////////////////////
+    // property - access
+    // /////////////////////////////////////////////////////////////////
+
+    private Object handleGetterMethodOnProperty(final Object[] args, final ObjectAdapter targetAdapter, final OneToOneAssociation otoa, final String methodName) {
+        if (args.length != 0) {
+            throw new IllegalArgumentException("Invoking a 'get' should have no arguments");
+        }
+
+        resolveIfRequired(targetAdapter);
+
+        final ObjectAdapter currentReferencedAdapter = otoa.get(targetAdapter);
+        final Object currentReferencedObj = AdapterUtils.unwrap(currentReferencedAdapter);
+
+        final PropertyAccessEvent ev = new PropertyAccessEvent(getDelegate(), otoa.getIdentifier(), currentReferencedObj);
+        notifyListeners(ev);
+        return currentReferencedObj;
+    }
+
+    // /////////////////////////////////////////////////////////////////
+    // property - modify
+    // /////////////////////////////////////////////////////////////////
+
+    private Object handleSetterMethodOnProperty(final Object[] args, final AuthenticationSession session, final ObjectAdapter targetAdapter, final OneToOneAssociation otoa, final String methodName) {
+        if (args.length != 1) {
+            throw new IllegalArgumentException("Invoking a setter should only have a single argument");
+        }
+
+        resolveIfRequired(targetAdapter);
+
+        final Object argumentObj = underlying(args[0]);
+        final ObjectAdapter argumentAdapter = argumentObj != null ? getAdapterManager().adapterFor(argumentObj) : null;
+
+        final InteractionResult interactionResult = otoa.isAssociationValid(targetAdapter, argumentAdapter).getInteractionResult();
+        notifyListenersAndVetoIfRequired(interactionResult);
+
+        if (getExecutionMode() == ExecutionMode.EXECUTE) {
+            otoa.set(targetAdapter, argumentAdapter);
+        }
+
+        objectChangedIfRequired(targetAdapter);
+
+        return null;
+    }
+
+    // /////////////////////////////////////////////////////////////////
+    // collection - access
+    // /////////////////////////////////////////////////////////////////
+
+    private Object handleGetterMethodOnCollection(final Method method, final Object[] args, final ObjectAdapter targetAdapter, final OneToManyAssociation otma, final String memberName) {
+        if (args.length != 0) {
+            throw new IllegalArgumentException("Invoking a 'get' should have no arguments");
+        }
+
+        resolveIfRequired(targetAdapter);
+
+        final ObjectAdapter currentReferencedAdapter = otma.get(targetAdapter);
+        final Object currentReferencedObj = AdapterUtils.unwrap(currentReferencedAdapter);
+
+        final CollectionAccessEvent ev = new CollectionAccessEvent(getDelegate(), otma.getIdentifier());
+
+        if (currentReferencedObj instanceof Collection) {
+            final Collection<?> collectionViewObject = lookupViewObject(method, memberName, (Collection<?>) currentReferencedObj, otma);
+            notifyListeners(ev);
+            return collectionViewObject;
+        } else if (currentReferencedObj instanceof Map) {
+            final Map<?, ?> mapViewObject = lookupViewObject(method, memberName, (Map<?, ?>) currentReferencedObj, otma);
+            notifyListeners(ev);
+            return mapViewObject;
+        }
+        throw new IllegalArgumentException(String.format("Collection type '%s' not supported by framework", currentReferencedObj.getClass().getName()));
+    }
+
+    /**
+     * Looks up (or creates) a proxy for this object.
+     */
+    private Collection<?> lookupViewObject(final Method method, final String memberName, final Collection<?> collectionToLookup, final OneToManyAssociation otma) {
+        Collection<?> collectionViewObject = collectionViewObjectsByMethod.get(method);
+        if (collectionViewObject == null) {
+            if (collectionToLookup instanceof WrapperObject) {
+                collectionViewObject = collectionToLookup;
+            } else {
+                collectionViewObject = Proxy.proxy(collectionToLookup, memberName, this, otma);
+            }
+            collectionViewObjectsByMethod.put(method, collectionViewObject);
+        }
+        return collectionViewObject;
+    }
+
+    private Map<?, ?> lookupViewObject(final Method method, final String memberName, final Map<?, ?> mapToLookup, final OneToManyAssociation otma) {
+        Map<?, ?> mapViewObject = mapViewObjectsByMethod.get(method);
+        if (mapViewObject == null) {
+            if (mapToLookup instanceof WrapperObject) {
+                mapViewObject = mapToLookup;
+            } else {
+                mapViewObject = Proxy.proxy(mapToLookup, memberName, this, otma);
+            }
+            mapViewObjectsByMethod.put(method, mapViewObject);
+        }
+        return mapViewObject;
+    }
+
+    // /////////////////////////////////////////////////////////////////
+    // collection - add to
+    // /////////////////////////////////////////////////////////////////
+
+    private Object handleCollectionAddToMethod(final Object[] args, final ObjectAdapter targetAdapter, final OneToManyAssociation otma, final String methodName) {
+
+        if (args.length != 1) {
+            throw new IllegalArgumentException("Invoking a addTo should only have a single argument");
+        }
+
+        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 = getAdapterManager().adapterFor(argumentObj);
+
+        final InteractionResult interactionResult = otma.isValidToAdd(targetAdapter, argumentNO).getInteractionResult();
+        notifyListenersAndVetoIfRequired(interactionResult);
+
+        if (getExecutionMode() == ExecutionMode.EXECUTE) {
+            otma.addElement(targetAdapter, argumentNO);
+        }
+
+        objectChangedIfRequired(targetAdapter);
+
+        return null;
+    }
+
+    // /////////////////////////////////////////////////////////////////
+    // collection - remove from
+    // /////////////////////////////////////////////////////////////////
+
+    private Object handleCollectionRemoveFromMethod(final Object[] args, final ObjectAdapter targetAdapter, final OneToManyAssociation otma, final String methodName) {
+        if (args.length != 1) {
+            throw new IllegalArgumentException("Invoking a removeFrom should only have a single argument");
+        }
+
+        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 = getAdapterManager().adapterFor(argumentObj);
+
+        final InteractionResult interactionResult = otma.isValidToRemove(targetAdapter, argumentAdapter).getInteractionResult();
+        notifyListenersAndVetoIfRequired(interactionResult);
+
+        if (getExecutionMode() == ExecutionMode.EXECUTE) {
+            otma.removeElement(targetAdapter, argumentAdapter);
+        }
+
+        objectChangedIfRequired(targetAdapter);
+
+        return null;
+    }
+
+    // /////////////////////////////////////////////////////////////////
+    // action
+    // /////////////////////////////////////////////////////////////////
+
+    private Object handleActionMethod(final Object[] args, final AuthenticationSession session, final ObjectAdapter targetAdapter, final ObjectAction noa, final String memberName) {
+
+        final Object[] underlyingArgs = new Object[args.length];
+        int i = 0;
+        for (final Object arg : args) {
+            underlyingArgs[i++] = underlying(arg);
+        }
+
+        final ObjectAdapter[] argAdapters = new ObjectAdapter[underlyingArgs.length];
+        int j = 0;
+        for (final Object underlyingArg : underlyingArgs) {
+            argAdapters[j++] = underlyingArg != null ? getAdapterManager().adapterFor(underlyingArg) : null;
+        }
+
+        final InteractionResult interactionResult = noa.isProposedArgumentSetValid(targetAdapter, argAdapters).getInteractionResult();
+        notifyListenersAndVetoIfRequired(interactionResult);
+
+        if (getExecutionMode() == ExecutionMode.EXECUTE) {
+            final ObjectAdapter actionReturnNO = noa.execute(targetAdapter, argAdapters);
+            return AdapterUtils.unwrap(actionReturnNO);
+        }
+
+        objectChangedIfRequired(targetAdapter);
+
+        return null;
+    }
+
+    private Object underlying(final Object arg) {
+        if (arg instanceof WrapperObject) {
+            final WrapperObject argViewObject = (WrapperObject) arg;
+            return argViewObject.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 AuthenticationSession session, final ObjectAdapter targetObjectAdapter, final ObjectMember objectMember) {
+        final Consent visibleConsent = objectMember.isVisible(getAuthenticationSession(), targetObjectAdapter, where);
+        final InteractionResult interactionResult = visibleConsent.getInteractionResult();
+        notifyListenersAndVetoIfRequired(interactionResult);
+    }
+
+    private void checkUsability(final AuthenticationSession session, final ObjectAdapter targetObjectAdapter, final ObjectMember objectMember) {
+        final InteractionResult interactionResult = objectMember.isUsable(getAuthenticationSession(), targetObjectAdapter, 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);
+        }
+    }
+
+    private String decode(final ObjectMember objectMember) {
+        if (objectMember instanceof OneToOneAssociation) {
+            return "a property";
+        }
+        if (objectMember instanceof OneToManyAssociation) {
+            return "a collection";
+        }
+        if (objectMember instanceof ObjectAction) {
+            return "an action";
+        }
+        return "an UNKNOWN member type";
+    }
+
+    /**
+     * 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(saveMethod);
+    }
+
+    protected boolean isUnderlyingMethod(final Method method) {
+        return method.equals(wrappedMethod);
+    }
+
+    // /////////////////////////////////////////////////////////////////
+    // Specification lookup
+    // /////////////////////////////////////////////////////////////////
+
+    private ObjectSpecificationDefault getJavaSpecificationOfOwningClass(final Method method) {
+        return getJavaSpecification(method.getDeclaringClass());
+    }
+
+    private ObjectSpecificationDefault getJavaSpecification(final Class<?> clazz) {
+        final ObjectSpecification nos = getSpecification(clazz);
+        if (!(nos instanceof ObjectSpecificationDefault)) {
+            throw new UnsupportedOperationException("Only Java is supported (specification is '" + nos.getClass().getCanonicalName() + "')");
+        }
+        return (ObjectSpecificationDefault) nos;
+    }
+
+    private ObjectSpecification getSpecification(final Class<?> type) {
+        final ObjectSpecification nos = getSpecificationLookup().loadSpecification(type);
+        return nos;
+    }
+
+    // /////////////////////////////////////////////////////////////////
+    // Dependencies
+    // /////////////////////////////////////////////////////////////////
+
+    protected SpecificationLoader getSpecificationLookup() {
+        return specificationLookup;
+    }
+
+    public AuthenticationSessionProvider getAuthenticationSessionProvider() {
+        return authenticationSessionProvider;
+    }
+
+    protected AuthenticationSession getAuthenticationSession() {
+        return getAuthenticationSessionProvider().getAuthenticationSession();
+    }
+
+    protected AdapterManager getAdapterManager() {
+        return adapterManager;
+    }
+
+    protected ObjectPersistor getObjectPersistor() {
+        return objectPersistor;
+    }
+
+}