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 2014/07/25 00:09:32 UTC

[02/10] ISIS-833: reverting isis-module-wrapper -> isis-core-wrapper.

http://git-wip-us.apache.org/repos/asf/isis/blob/dec67056/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..041083e
--- /dev/null
+++ b/core/wrapper/src/main/java/org/apache/isis/core/wrapper/WrapperFactoryDefault.java
@@ -0,0 +1,39 @@
+/*
+ *  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 org.apache.isis.applib.annotation.DomainService;
+
+/**
+ * This service provides the ability to "wrap" 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
+public class WrapperFactoryDefault extends WrapperFactoryJavassist {
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/dec67056/core/wrapper/src/main/java/org/apache/isis/core/wrapper/WrapperFactoryJavassist.java
----------------------------------------------------------------------
diff --git a/core/wrapper/src/main/java/org/apache/isis/core/wrapper/WrapperFactoryJavassist.java b/core/wrapper/src/main/java/org/apache/isis/core/wrapper/WrapperFactoryJavassist.java
new file mode 100644
index 0000000..e71580f
--- /dev/null
+++ b/core/wrapper/src/main/java/org/apache/isis/core/wrapper/WrapperFactoryJavassist.java
@@ -0,0 +1,30 @@
+/*
+ *  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 org.apache.isis.core.wrapper.proxy.ProxyInstantiatorForJavassist;
+
+public class WrapperFactoryJavassist extends WrapperFactoryAbstract  {
+
+    public WrapperFactoryJavassist() {
+        super(new ProxyInstantiatorForJavassist());
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/dec67056/core/wrapper/src/main/java/org/apache/isis/core/wrapper/dispatchers/InteractionEventDispatcher.java
----------------------------------------------------------------------
diff --git a/core/wrapper/src/main/java/org/apache/isis/core/wrapper/dispatchers/InteractionEventDispatcher.java b/core/wrapper/src/main/java/org/apache/isis/core/wrapper/dispatchers/InteractionEventDispatcher.java
new file mode 100644
index 0000000..2dfa70c
--- /dev/null
+++ b/core/wrapper/src/main/java/org/apache/isis/core/wrapper/dispatchers/InteractionEventDispatcher.java
@@ -0,0 +1,28 @@
+/*
+ *  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.dispatchers;
+
+import org.apache.isis.applib.events.InteractionEvent;
+
+public interface InteractionEventDispatcher {
+
+    void dispatch(InteractionEvent interactionEvent);
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/dec67056/core/wrapper/src/main/java/org/apache/isis/core/wrapper/dispatchers/InteractionEventDispatcherTypeSafe.java
----------------------------------------------------------------------
diff --git a/core/wrapper/src/main/java/org/apache/isis/core/wrapper/dispatchers/InteractionEventDispatcherTypeSafe.java b/core/wrapper/src/main/java/org/apache/isis/core/wrapper/dispatchers/InteractionEventDispatcherTypeSafe.java
new file mode 100644
index 0000000..c568633
--- /dev/null
+++ b/core/wrapper/src/main/java/org/apache/isis/core/wrapper/dispatchers/InteractionEventDispatcherTypeSafe.java
@@ -0,0 +1,34 @@
+/*
+ *  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.dispatchers;
+
+import org.apache.isis.applib.events.InteractionEvent;
+
+public abstract class InteractionEventDispatcherTypeSafe<T extends InteractionEvent> implements InteractionEventDispatcher {
+
+    public abstract void dispatchTypeSafe(T interactionEvent);
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public void dispatch(final InteractionEvent interactionEvent) {
+        dispatchTypeSafe((T) interactionEvent);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/dec67056/core/wrapper/src/main/java/org/apache/isis/core/wrapper/handlers/AbstractCollectionInvocationHandler.java
----------------------------------------------------------------------
diff --git a/core/wrapper/src/main/java/org/apache/isis/core/wrapper/handlers/AbstractCollectionInvocationHandler.java b/core/wrapper/src/main/java/org/apache/isis/core/wrapper/handlers/AbstractCollectionInvocationHandler.java
new file mode 100644
index 0000000..b8a4464
--- /dev/null
+++ b/core/wrapper/src/main/java/org/apache/isis/core/wrapper/handlers/AbstractCollectionInvocationHandler.java
@@ -0,0 +1,92 @@
+/*
+ *  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.Method;
+import java.util.ArrayList;
+import java.util.List;
+
+import com.google.common.collect.Lists;
+
+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 = Lists.newArrayList();
+    private final List<Method> vetoedMethods = Lists.newArrayList();
+
+    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/dec67056/core/wrapper/src/main/java/org/apache/isis/core/wrapper/handlers/CollectionInvocationHandler.java
----------------------------------------------------------------------
diff --git a/core/wrapper/src/main/java/org/apache/isis/core/wrapper/handlers/CollectionInvocationHandler.java b/core/wrapper/src/main/java/org/apache/isis/core/wrapper/handlers/CollectionInvocationHandler.java
new file mode 100644
index 0000000..bcf2973
--- /dev/null
+++ b/core/wrapper/src/main/java/org/apache/isis/core/wrapper/handlers/CollectionInvocationHandler.java
@@ -0,0 +1,53 @@
+/*
+ *  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.util.Collection;
+import java.util.List;
+
+import org.apache.isis.core.commons.lang.ObjectExtensions;
+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(ObjectExtensions.getMethod(collectionToProxy, "contains", Object.class));
+            intercept(ObjectExtensions.getMethod(collectionToProxy, "size"));
+            intercept(ObjectExtensions.getMethod(collectionToProxy, "isEmpty"));
+            if (collectionToProxy instanceof List) {
+                intercept(ObjectExtensions.getMethod(collectionToProxy, "get", int.class));
+            }
+            veto(ObjectExtensions.getMethod(collectionToProxy, "add", Object.class));
+            veto(ObjectExtensions.getMethod(collectionToProxy, "remove", Object.class));
+            veto(ObjectExtensions.getMethod(collectionToProxy, "addAll", Collection.class));
+            veto(ObjectExtensions.getMethod(collectionToProxy, "removeAll", Collection.class));
+            veto(ObjectExtensions.getMethod(collectionToProxy, "retainAll", Collection.class));
+            veto(ObjectExtensions.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/dec67056/core/wrapper/src/main/java/org/apache/isis/core/wrapper/handlers/DelegatingInvocationHandler.java
----------------------------------------------------------------------
diff --git a/core/wrapper/src/main/java/org/apache/isis/core/wrapper/handlers/DelegatingInvocationHandler.java b/core/wrapper/src/main/java/org/apache/isis/core/wrapper/handlers/DelegatingInvocationHandler.java
new file mode 100644
index 0000000..d424f64
--- /dev/null
+++ b/core/wrapper/src/main/java/org/apache/isis/core/wrapper/handlers/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.handlers;
+
+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/dec67056/core/wrapper/src/main/java/org/apache/isis/core/wrapper/handlers/DelegatingInvocationHandlerDefault.java
----------------------------------------------------------------------
diff --git a/core/wrapper/src/main/java/org/apache/isis/core/wrapper/handlers/DelegatingInvocationHandlerDefault.java b/core/wrapper/src/main/java/org/apache/isis/core/wrapper/handlers/DelegatingInvocationHandlerDefault.java
new file mode 100644
index 0000000..e1b7cdd
--- /dev/null
+++ b/core/wrapper/src/main/java/org/apache/isis/core/wrapper/handlers/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.handlers;
+
+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/dec67056/core/wrapper/src/main/java/org/apache/isis/core/wrapper/handlers/DomainObjectInvocationHandler.java
----------------------------------------------------------------------
diff --git a/core/wrapper/src/main/java/org/apache/isis/core/wrapper/handlers/DomainObjectInvocationHandler.java b/core/wrapper/src/main/java/org/apache/isis/core/wrapper/handlers/DomainObjectInvocationHandler.java
new file mode 100644
index 0000000..d505add
--- /dev/null
+++ b/core/wrapper/src/main/java/org/apache/isis/core/wrapper/handlers/DomainObjectInvocationHandler.java
@@ -0,0 +1,720 @@
+/*
+ *  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.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.apache.isis.applib.annotation.Where;
+import org.apache.isis.applib.events.*;
+import org.apache.isis.applib.filter.Filter;
+import org.apache.isis.applib.services.wrapper.*;
+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.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.DecoratingFacet;
+import org.apache.isis.core.metamodel.facetapi.Facet;
+import org.apache.isis.core.metamodel.facets.ImperativeFacet;
+import org.apache.isis.core.metamodel.facets.ImperativeFacet.Intent;
+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;
+
+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;
+
+    private final ProxyContextHandler proxy;
+    private final ExecutionMode executionMode;
+
+    /**
+     * The <tt>title()</tt> method; may be <tt>null</tt>.
+     */
+    protected Method titleMethod;
+
+    /**
+     * The <tt>__isis_save()</tt> method from {@link WrapperObject#__isis_save()}.
+     */
+    protected Method __isis_saveMethod;
+
+    /**
+     * The <tt>save()</tt> method from {@link WrapperObject#save()}.
+     */
+    @Deprecated
+    protected Method saveMethod;
+
+    /**
+     * The <tt>__isis_wrapped()</tt> method from {@link WrapperObject#__isis_wrapped()}.
+     */
+    protected Method __isis_wrappedMethod;
+
+    /**
+     * The <tt>wrapped()</tt> method from {@link WrapperObject#wrapped()}.
+     */
+    @Deprecated
+    protected Method wrappedMethod;
+
+    /**
+     * The <tt>__isis_executionMode()</tt> method from {@link WrapperObject#__isis_executionMode()}.
+     */
+    protected Method __isis_executionMode;
+
+    public DomainObjectInvocationHandler(
+            final T delegate,
+            final WrapperFactory wrapperFactory,
+            final ExecutionMode mode,
+            final AuthenticationSessionProvider authenticationSessionProvider,
+            final SpecificationLoader specificationLookup,
+            final AdapterManager adapterManager,
+            final ObjectPersistor objectPersistor,
+            final ProxyContextHandler proxy) {
+        super(delegate, wrapperFactory, mode);
+
+        this.proxy = proxy;
+        this.authenticationSessionProvider = authenticationSessionProvider;
+        this.specificationLookup = specificationLookup;
+        this.adapterManager = adapterManager;
+        this.objectPersistor = objectPersistor;
+        this.executionMode = mode;
+
+        try {
+            titleMethod = delegate.getClass().getMethod("title", new Class[]{});
+            __isis_saveMethod = WrapperObject.class.getMethod("__isis_save", new Class[]{});
+            __isis_wrappedMethod = WrapperObject.class.getMethod("__isis_wrapped", new Class[]{});
+            __isis_executionMode = WrapperObject.class.getMethod("__isis_executionMode", 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);
+        }
+
+        // workaround for JDO-enhanced..
+        if(isJdoMethod(method)) {
+            return delegate(method, args);
+        }
+
+        if(isInjectMethod(method)) {
+            return delegate(method, args);
+        }
+
+        final ObjectAdapter targetAdapter = getAdapterManager().adapterFor(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 (isWrappedMethod(method)) {
+            return getDelegate();
+        }
+
+        if (isExecutionModeMethod(method)) {
+            return executionMode;
+        }
+
+        final ObjectMember objectMember = locateAndCheckMember(method);
+
+        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));
+        }
+
+        final String methodName = method.getName();
+
+        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(args, targetAdapter, otoa, methodName);
+            }
+            
+            if (intent == Intent.MODIFY_PROPERTY || intent == Intent.INITIALIZATION) {
+                return handleSetterMethodOnProperty(args, getAuthenticationSession(), targetAdapter, otoa, methodName);
+            }
+        }
+        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(method, args, targetAdapter, otma, memberName);
+            }
+            if (intent == Intent.MODIFY_COLLECTION_ADD) {
+                return handleCollectionAddToMethod(args, targetAdapter, otma, methodName);
+            }
+            if (intent == Intent.MODIFY_COLLECTION_REMOVE) {
+                return handleCollectionRemoveFromMethod(args, targetAdapter, otma, methodName);
+            }
+        }
+
+        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 noa = (ObjectAction) objectMember;
+            return handleActionMethod(args, getAuthenticationSession(), targetAdapter, noa, memberName);
+        }
+
+        throw new UnsupportedOperationException(String.format("Unknown member type '%s'", objectMember));
+    }
+
+    private boolean isJdoMethod(final Method method) {
+        return methodStartsWith(method, "jdo");
+    }
+
+    private boolean isInjectMethod(final Method method) {
+        return methodStartsWith(method, "inject");
+    }
+
+    private boolean methodStartsWith(final Method method, final String prefix) {
+        return method.getName().startsWith(prefix);
+    }
+    
+    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;
+                }
+                Facet underlyingFacet = facet.getUnderlyingFacet();
+                return asImperativeFacet(underlyingFacet);
+            }
+        });
+
+        // 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) {
+            // REVIEW: this is all a bit hacky...
+            for (Object obj : objects) {
+                // handle the *decorators
+                if(obj instanceof DecoratingFacet) {
+                    DecoratingFacet<?> decoratingFacet = (DecoratingFacet<?>) obj;
+                    obj = ((DecoratingFacet<?>) obj).getDecoratedFacet();
+                }
+                if (superType.isAssignableFrom(obj.getClass())) {
+                    return true;
+                }
+                if(obj instanceof Facet) {
+                    Facet facet = (Facet) obj;
+                    // handle any wrapping (eg PostPropertyChangedSetterEventFacet)
+                    Facet underlyingFacet = facet.getUnderlyingFacet();
+                    if(underlyingFacet != null && 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) {
+
+        if(getExecutionMode().shouldEnforceRules()) {
+            final InteractionResult interactionResult = targetNoSpec.isValidResult(targetAdapter);
+            notifyListenersAndVetoIfRequired(interactionResult);
+        }
+
+        if (getExecutionMode().shouldExecute()) {
+            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");
+        }
+
+        if(getExecutionMode().shouldEnforceRules()) {
+            checkVisibility(getAuthenticationSession(), targetAdapter, otoa);
+        }
+
+        resolveIfRequired(targetAdapter);
+
+        final ObjectAdapter currentReferencedAdapter = otoa.get(targetAdapter);
+        final Object currentReferencedObj = ObjectAdapter.Util.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");
+        }
+
+        final Object argumentObj = underlying(args[0]);
+
+        if(getExecutionMode().shouldEnforceRules()) {
+            checkVisibility(getAuthenticationSession(), targetAdapter, otoa);
+            checkUsability(getAuthenticationSession(), targetAdapter, otoa);
+        }
+
+        final ObjectAdapter argumentAdapter = argumentObj != null ? getAdapterManager().adapterFor(argumentObj) : null;
+
+        resolveIfRequired(targetAdapter);
+
+
+        if(getExecutionMode().shouldEnforceRules()) {
+            final InteractionResult interactionResult = otoa.isAssociationValid(targetAdapter, argumentAdapter).getInteractionResult();
+            notifyListenersAndVetoIfRequired(interactionResult);
+        }
+
+        if (getExecutionMode().shouldExecute()) {
+            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");
+        }
+
+        if(getExecutionMode().shouldEnforceRules()) {
+            checkVisibility(getAuthenticationSession(), targetAdapter, otma);
+        }
+
+        resolveIfRequired(targetAdapter);
+
+        final ObjectAdapter currentReferencedAdapter = otma.get(targetAdapter);
+        final Object currentReferencedObj = ObjectAdapter.Util.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");
+        }
+
+        if(getExecutionMode().shouldEnforceRules()) {
+            checkVisibility(getAuthenticationSession(), targetAdapter, otma);
+            checkUsability(getAuthenticationSession(), 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 = getAdapterManager().adapterFor(argumentObj);
+
+        if(getExecutionMode().shouldEnforceRules()) {
+            final InteractionResult interactionResult = otma.isValidToAdd(targetAdapter, argumentNO).getInteractionResult();
+            notifyListenersAndVetoIfRequired(interactionResult);
+        }
+
+        if (getExecutionMode().shouldExecute()) {
+            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");
+        }
+
+        if(getExecutionMode().shouldEnforceRules()) {
+            checkVisibility(getAuthenticationSession(), targetAdapter, otma);
+            checkUsability(getAuthenticationSession(), targetAdapter, otma);
+        }
+
+
+        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);
+
+        if(getExecutionMode().shouldEnforceRules()) {
+            final InteractionResult interactionResult = otma.isValidToRemove(targetAdapter, argumentAdapter).getInteractionResult();
+            notifyListenersAndVetoIfRequired(interactionResult);
+        }
+
+        if (getExecutionMode().shouldExecute()) {
+            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) {
+
+        if(getExecutionMode().shouldEnforceRules()) {
+            checkVisibility(getAuthenticationSession(), targetAdapter, noa);
+            checkUsability(getAuthenticationSession(), targetAdapter, noa);
+        }
+
+        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;
+        }
+
+        if(getExecutionMode().shouldEnforceRules()) {
+            final InteractionResult interactionResult = noa.isProposedArgumentSetValid(targetAdapter, argAdapters).getInteractionResult();
+            notifyListenersAndVetoIfRequired(interactionResult);
+        }
+
+        if (getExecutionMode().shouldExecute()) {
+            final ObjectAdapter actionReturnNO = noa.execute(targetAdapter, argAdapters);
+            return ObjectAdapter.Util.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) || method.equals(__isis_saveMethod);
+    }
+
+    protected boolean isWrappedMethod(final Method method) {
+        return method.equals(wrappedMethod) || 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 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;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/dec67056/core/wrapper/src/main/java/org/apache/isis/core/wrapper/handlers/MapInvocationHandler.java
----------------------------------------------------------------------
diff --git a/core/wrapper/src/main/java/org/apache/isis/core/wrapper/handlers/MapInvocationHandler.java b/core/wrapper/src/main/java/org/apache/isis/core/wrapper/handlers/MapInvocationHandler.java
new file mode 100644
index 0000000..ed6cbc1
--- /dev/null
+++ b/core/wrapper/src/main/java/org/apache/isis/core/wrapper/handlers/MapInvocationHandler.java
@@ -0,0 +1,48 @@
+/*
+ *  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.util.Map;
+
+import org.apache.isis.core.commons.lang.ObjectExtensions;
+import org.apache.isis.core.metamodel.spec.feature.OneToManyAssociation;
+
+class MapInvocationHandler<T, C> extends AbstractCollectionInvocationHandler<T, C> {
+
+    public MapInvocationHandler(final C collectionToProxy, final String collectionName, final DomainObjectInvocationHandler<T> handler, final OneToManyAssociation otma) {
+        super(collectionToProxy, collectionName, handler, otma);
+
+        try {
+            intercept(ObjectExtensions.getMethod(collectionToProxy, "containsKey", Object.class));
+            intercept(ObjectExtensions.getMethod(collectionToProxy, "containsValue", Object.class));
+            intercept(ObjectExtensions.getMethod(collectionToProxy, "size"));
+            intercept(ObjectExtensions.getMethod(collectionToProxy, "isEmpty"));
+            veto(ObjectExtensions.getMethod(collectionToProxy, "put", Object.class, Object.class));
+            veto(ObjectExtensions.getMethod(collectionToProxy, "remove", Object.class));
+            veto(ObjectExtensions.getMethod(collectionToProxy, "putAll", Map.class));
+            veto(ObjectExtensions.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/dec67056/core/wrapper/src/main/java/org/apache/isis/core/wrapper/handlers/ProxyContextHandler.java
----------------------------------------------------------------------
diff --git a/core/wrapper/src/main/java/org/apache/isis/core/wrapper/handlers/ProxyContextHandler.java b/core/wrapper/src/main/java/org/apache/isis/core/wrapper/handlers/ProxyContextHandler.java
new file mode 100644
index 0000000..5f942bc
--- /dev/null
+++ b/core/wrapper/src/main/java/org/apache/isis/core/wrapper/handlers/ProxyContextHandler.java
@@ -0,0 +1,84 @@
+/*
+ *  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 static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.CoreMatchers.nullValue;
+
+import java.util.Collection;
+import java.util.Map;
+
+import org.apache.isis.applib.services.wrapper.WrapperFactory;
+import org.apache.isis.applib.services.wrapper.WrapperFactory.ExecutionMode;
+import org.apache.isis.core.commons.authentication.AuthenticationSessionProvider;
+import org.apache.isis.core.commons.ensure.Ensure;
+import org.apache.isis.core.metamodel.adapter.ObjectPersistor;
+import org.apache.isis.core.metamodel.adapter.mgr.AdapterManager;
+import org.apache.isis.core.metamodel.spec.SpecificationLoader;
+import org.apache.isis.core.metamodel.spec.feature.OneToManyAssociation;
+import org.apache.isis.core.wrapper.proxy.ProxyInstantiator;
+
+public class ProxyContextHandler {
+
+    private final ProxyInstantiator proxyInstantiator;
+    
+    public ProxyContextHandler(final ProxyInstantiator proxyInstantiator) {
+        this.proxyInstantiator = proxyInstantiator;
+    }
+    
+    public <T> T proxy(final T domainObject, final WrapperFactory wrapperFactory, final ExecutionMode mode, final AuthenticationSessionProvider authenticationSessionProvider, final SpecificationLoader specificationLookup, final AdapterManager adapterManager, final ObjectPersistor objectPersistor) {
+
+        Ensure.ensureThatArg(wrapperFactory, is(not(nullValue())));
+        Ensure.ensureThatArg(authenticationSessionProvider, is(not(nullValue())));
+        Ensure.ensureThatArg(specificationLookup, is(not(nullValue())));
+        Ensure.ensureThatArg(adapterManager, is(not(nullValue())));
+        Ensure.ensureThatArg(objectPersistor, is(not(nullValue())));
+
+        final DomainObjectInvocationHandler<T> invocationHandler = new DomainObjectInvocationHandler<T>(domainObject, wrapperFactory, mode, authenticationSessionProvider, specificationLookup, adapterManager, objectPersistor, this);
+
+        return proxyInstantiator.instantiateProxy(invocationHandler);
+    }
+
+    /**
+     * Whether to execute or not will be picked up from the supplied parent
+     * handler.
+     */
+    public <T, E> Collection<E> proxy(final Collection<E> collectionToProxy, final String collectionName, final DomainObjectInvocationHandler<T> handler, final OneToManyAssociation otma) {
+
+        final CollectionInvocationHandler<T, Collection<E>> collectionInvocationHandler = new CollectionInvocationHandler<T, Collection<E>>(collectionToProxy, collectionName, handler, otma);
+        collectionInvocationHandler.setResolveObjectChangedEnabled(handler.isResolveObjectChangedEnabled());
+
+        return proxyInstantiator.instantiateProxy(collectionInvocationHandler);
+    }
+
+    /**
+     * Whether to execute or not will be picked up from the supplied parent
+     * handler.
+     */
+    public <T, P, Q> Map<P, Q> proxy(final Map<P, Q> collectionToProxy, final String collectionName, final DomainObjectInvocationHandler<T> handler, final OneToManyAssociation otma) {
+
+        final MapInvocationHandler<T, Map<P, Q>> mapInvocationHandler = new MapInvocationHandler<T, Map<P, Q>>(collectionToProxy, collectionName, handler, otma);
+        mapInvocationHandler.setResolveObjectChangedEnabled(handler.isResolveObjectChangedEnabled());
+
+        return proxyInstantiator.instantiateProxy(mapInvocationHandler);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/dec67056/core/wrapper/src/main/java/org/apache/isis/core/wrapper/internal/util/Constants.java
----------------------------------------------------------------------
diff --git a/core/wrapper/src/main/java/org/apache/isis/core/wrapper/internal/util/Constants.java b/core/wrapper/src/main/java/org/apache/isis/core/wrapper/internal/util/Constants.java
new file mode 100644
index 0000000..0021038
--- /dev/null
+++ b/core/wrapper/src/main/java/org/apache/isis/core/wrapper/internal/util/Constants.java
@@ -0,0 +1,52 @@
+/*
+ *  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.util;
+
+public final class Constants {
+    private Constants() {
+    }
+
+    public static final String PREFIX_CHOICES = "choices";
+    public static final String PREFIX_DEFAULT = "default";
+    public static final String PREFIX_HIDE = "hide";
+    public static final String PREFIX_DISABLE = "disable";
+    public static final String PREFIX_VALIDATE_REMOVE_FROM = "validateRemoveFrom";
+    public static final String PREFIX_VALIDATE_ADD_TO = "validateAddTo";
+    public static final String PREFIX_VALIDATE = "validate";
+    public static final String PREFIX_REMOVE_FROM = "removeFrom";
+    public static final String PREFIX_ADD_TO = "addTo";
+    public static final String PREFIX_MODIFY = "modify";
+    public static final String PREFIX_CLEAR = "clear";
+    public static final String PREFIX_SET = "set";
+    public static final String PREFIX_GET = "get";
+
+    public final static String TITLE_METHOD_NAME = "title";
+    public final static String TO_STRING_METHOD_NAME = "toString";
+
+    /**
+     * Cannot invoke methods with these prefixes.
+     */
+    public final static String[] INVALID_PREFIXES = { PREFIX_MODIFY, PREFIX_CLEAR, PREFIX_DISABLE, PREFIX_VALIDATE, PREFIX_VALIDATE_ADD_TO, PREFIX_VALIDATE_REMOVE_FROM, PREFIX_HIDE, };
+
+    public final static String[] PROPERTY_PREFIXES = { PREFIX_GET, PREFIX_SET, PREFIX_MODIFY, PREFIX_CLEAR, PREFIX_DISABLE, PREFIX_VALIDATE, PREFIX_HIDE, PREFIX_DEFAULT, PREFIX_CHOICES };
+    public final static String[] COLLECTION_PREFIXES = { PREFIX_GET, PREFIX_SET, PREFIX_ADD_TO, PREFIX_REMOVE_FROM, PREFIX_DISABLE, PREFIX_VALIDATE_ADD_TO, PREFIX_VALIDATE_REMOVE_FROM, PREFIX_HIDE, PREFIX_DEFAULT, PREFIX_CHOICES };
+    public final static String[] ACTION_PREFIXES = { PREFIX_VALIDATE, PREFIX_DISABLE, PREFIX_HIDE, PREFIX_DEFAULT, PREFIX_CHOICES, };
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/dec67056/core/wrapper/src/main/java/org/apache/isis/core/wrapper/internal/util/MethodPrefixFinder.java
----------------------------------------------------------------------
diff --git a/core/wrapper/src/main/java/org/apache/isis/core/wrapper/internal/util/MethodPrefixFinder.java b/core/wrapper/src/main/java/org/apache/isis/core/wrapper/internal/util/MethodPrefixFinder.java
new file mode 100644
index 0000000..dd78056
--- /dev/null
+++ b/core/wrapper/src/main/java/org/apache/isis/core/wrapper/internal/util/MethodPrefixFinder.java
@@ -0,0 +1,48 @@
+/*
+ *  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.util;
+
+import java.util.Arrays;
+import java.util.LinkedHashSet;
+
+public final class MethodPrefixFinder {
+
+    // a Linked Hash Set is used to ensure that the ordering is preserved.
+    public final static LinkedHashSet<String> ALL_PREFIXES = new LinkedHashSet<String>() {
+        private static final long serialVersionUID = 1L;
+        {
+            // collection prefixes are added first because we want to
+            // test validateAddTo and validateRemoveFrom before validate
+            addAll(Arrays.asList(Constants.COLLECTION_PREFIXES));
+            addAll(Arrays.asList(Constants.PROPERTY_PREFIXES));
+            addAll(Arrays.asList(Constants.ACTION_PREFIXES));
+        }
+    };
+
+    public String findPrefix(final String methodName) {
+        for (final String prefix : ALL_PREFIXES) {
+            if (methodName.startsWith(prefix)) {
+                return prefix;
+            }
+        }
+        return "";
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/dec67056/core/wrapper/src/main/java/org/apache/isis/core/wrapper/internal/util/Util.java
----------------------------------------------------------------------
diff --git a/core/wrapper/src/main/java/org/apache/isis/core/wrapper/internal/util/Util.java b/core/wrapper/src/main/java/org/apache/isis/core/wrapper/internal/util/Util.java
new file mode 100644
index 0000000..7cfecbd
--- /dev/null
+++ b/core/wrapper/src/main/java/org/apache/isis/core/wrapper/internal/util/Util.java
@@ -0,0 +1,53 @@
+/*
+ *  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.util;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Proxy;
+
+import org.objenesis.ObjenesisHelper;
+
+public final class Util {
+
+    private Util(){}
+    
+    @SuppressWarnings("unchecked")
+    public static <T> T createInstance(final Class<T> toProxy, final InvocationHandler handler, final Class<?>... auxiliaryTypes) {
+        return (T) Proxy.newProxyInstance(toProxy.getClassLoader(), combine(toProxy, auxiliaryTypes) , handler);
+    }
+ 
+    /**
+     * Return a new instance of the specified class. The recommended way is
+     * without calling any constructor. This is usually done by doing like
+     * <code>ObjectInputStream.readObject()</code> which is JVM specific.
+     */
+    @SuppressWarnings("unchecked")
+    public static <T> T createInstance(final Class<T> enhancedClass) {
+        return (T) ObjenesisHelper.newInstance(enhancedClass);
+    }
+    
+    private static Class<?>[] combine(Class<?> first, Class<?>... rest) {
+        Class<?>[] all = new Class<?>[rest.length+1];
+        all[0] = first;
+        System.arraycopy(rest, 0, all, 1, rest.length);
+        return all;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/dec67056/core/wrapper/src/main/java/org/apache/isis/core/wrapper/proxy/ProxyInstantiator.java
----------------------------------------------------------------------
diff --git a/core/wrapper/src/main/java/org/apache/isis/core/wrapper/proxy/ProxyInstantiator.java b/core/wrapper/src/main/java/org/apache/isis/core/wrapper/proxy/ProxyInstantiator.java
new file mode 100644
index 0000000..255cbfa
--- /dev/null
+++ b/core/wrapper/src/main/java/org/apache/isis/core/wrapper/proxy/ProxyInstantiator.java
@@ -0,0 +1,26 @@
+/*
+ *  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.proxy;
+
+import org.apache.isis.core.wrapper.handlers.DelegatingInvocationHandler;
+
+public interface ProxyInstantiator {
+    public <T> T instantiateProxy(final DelegatingInvocationHandler<T> invocationHandler);
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/isis/blob/dec67056/core/wrapper/src/main/java/org/apache/isis/core/wrapper/proxy/ProxyInstantiatorForCglib.java
----------------------------------------------------------------------
diff --git a/core/wrapper/src/main/java/org/apache/isis/core/wrapper/proxy/ProxyInstantiatorForCglib.java b/core/wrapper/src/main/java/org/apache/isis/core/wrapper/proxy/ProxyInstantiatorForCglib.java
new file mode 100644
index 0000000..65241e9
--- /dev/null
+++ b/core/wrapper/src/main/java/org/apache/isis/core/wrapper/proxy/ProxyInstantiatorForCglib.java
@@ -0,0 +1,101 @@
+/*
+ *  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.proxy;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.List;
+
+import com.google.common.collect.Lists;
+
+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 net.sf.cglib.proxy.MethodProxy;
+
+import org.apache.isis.applib.services.wrapper.WrapperObject;
+import org.apache.isis.core.metamodel.specloader.classsubstitutor.CglibEnhanced;
+import org.apache.isis.core.wrapper.handlers.DelegatingInvocationHandler;
+import org.apache.isis.core.wrapper.internal.util.Util;
+
+public class ProxyInstantiatorForCglib implements ProxyInstantiator {
+
+    @SuppressWarnings("unchecked")
+    public <T> T instantiateProxy(final DelegatingInvocationHandler<T> handler) {
+
+        final T toProxy = handler.getDelegate();
+
+        // handle if already proxied using cglib
+        // (this is in case the CglibObjectFactory is configured, to handle lazy loading/object dirtying.  
+        // Have decided not to port this over to the Javassist version, in anticipation of the CglibObjectFactory being dropped from Isis in v2.0.0.
+        // (If that isn't the case, then this class is still available).
+        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 = Lists.newArrayList();
+            interfaces.addAll(Arrays.asList(enhancedClass.getInterfaces()));
+            interfaces.remove(Factory.class); // if there.
+            interfaces.add(WrapperObject.class);
+
+            return (T) Enhancer.create(origSuperclass, interfaces.toArray(new Class[] {}), newMethodInterceptor(handler));
+        }
+
+        final Class<T> clazz = (Class<T>) toProxy.getClass();
+
+        if (clazz.isInterface()) {
+            return Util.createInstance(clazz, handler, WrapperObject.class);
+        } else {
+            final Class<T> enhancedClass = createEnhancedClass(clazz, handler, WrapperObject.class);
+            return Util.createInstance(enhancedClass);
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    private static <T> Class<T> createEnhancedClass(final Class<T> toProxyClass, final InvocationHandler handler, final Class<?>... auxiliaryTypes) {
+
+        // Create the proxy
+        final Enhancer enhancer = new Enhancer();
+        enhancer.setSuperclass(toProxyClass);
+        enhancer.setInterfaces(auxiliaryTypes);
+        enhancer.setCallbackType(newMethodInterceptor(handler).getClass());
+
+        final Class<?> enhancedClass = enhancer.createClass();
+
+        Enhancer.registerCallbacks(enhancedClass, new Callback[] { newMethodInterceptor(handler) });
+        return (Class<T>) enhancedClass;
+    }
+
+    private static <T> MethodInterceptor newMethodInterceptor(final InvocationHandler handler) {
+        return new MethodInterceptor() {
+            @Override
+            public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
+                return handler.invoke(obj, method, args);
+            }
+        };
+    }
+    
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/dec67056/core/wrapper/src/main/java/org/apache/isis/core/wrapper/proxy/ProxyInstantiatorForJavassist.java
----------------------------------------------------------------------
diff --git a/core/wrapper/src/main/java/org/apache/isis/core/wrapper/proxy/ProxyInstantiatorForJavassist.java b/core/wrapper/src/main/java/org/apache/isis/core/wrapper/proxy/ProxyInstantiatorForJavassist.java
new file mode 100644
index 0000000..3d2b838
--- /dev/null
+++ b/core/wrapper/src/main/java/org/apache/isis/core/wrapper/proxy/ProxyInstantiatorForJavassist.java
@@ -0,0 +1,77 @@
+/*
+ *  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.proxy;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+
+import javassist.util.proxy.MethodFilter;
+import javassist.util.proxy.MethodHandler;
+import javassist.util.proxy.ProxyFactory;
+import javassist.util.proxy.ProxyObject;
+
+import org.apache.isis.applib.services.wrapper.WrapperObject;
+import org.apache.isis.core.commons.lang.ArrayExtensions;
+import org.apache.isis.core.wrapper.handlers.DelegatingInvocationHandler;
+import org.apache.isis.core.wrapper.internal.util.Util;
+
+public class ProxyInstantiatorForJavassist implements ProxyInstantiator {
+
+    @SuppressWarnings("unchecked")
+    public <T> T instantiateProxy(final DelegatingInvocationHandler<T> handler) {
+
+        final T toProxy = handler.getDelegate();
+
+        final Class<T> clazz = (Class<T>) toProxy.getClass();
+
+        if (clazz.isInterface()) {
+            return Util.createInstance(clazz, handler, WrapperObject.class);
+        } else {
+            final Class<T> enhancedClass = createEnhancedClass(clazz, handler, WrapperObject.class);
+            ProxyObject proxyObject = (ProxyObject) Util.createInstance(enhancedClass);
+            proxyObject.setHandler(new MethodHandler() {
+                @Override
+                public Object invoke(Object self, Method thisMethod, Method proceed, Object[] args) throws Throwable {
+                    return handler.invoke(self, thisMethod, args);
+                }
+            });
+            return (T) proxyObject;
+        }
+    }
+    
+    @SuppressWarnings("unchecked")
+    private static <T> Class<T> createEnhancedClass(final Class<T> toProxyClass, final InvocationHandler handler, final Class<?>... auxiliaryTypes) {
+        
+        final ProxyFactory proxyFactory = new ProxyFactory();
+        proxyFactory.setSuperclass(toProxyClass);
+        proxyFactory.setInterfaces(ArrayExtensions.combine(toProxyClass.getInterfaces(), new Class<?>[]{org.apache.isis.core.metamodel.specloader.classsubstitutor.JavassistEnhanced.class}, auxiliaryTypes));
+
+        proxyFactory.setFilter(new MethodFilter() {
+            @Override
+            public boolean isHandled(final Method m) {
+                // ignore finalize()
+                return !m.getName().equals("finalize") || m.isBridge();
+            }
+        });
+        
+        return proxyFactory.createClass();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/dec67056/core/wrapper/src/test/java/org/apache/isis/core/wrapper/WrapperFactoryAbstractTest.java
----------------------------------------------------------------------
diff --git a/core/wrapper/src/test/java/org/apache/isis/core/wrapper/WrapperFactoryAbstractTest.java b/core/wrapper/src/test/java/org/apache/isis/core/wrapper/WrapperFactoryAbstractTest.java
new file mode 100644
index 0000000..6bc2e8e
--- /dev/null
+++ b/core/wrapper/src/test/java/org/apache/isis/core/wrapper/WrapperFactoryAbstractTest.java
@@ -0,0 +1,127 @@
+/*
+ *  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 org.jmock.auto.Mock;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.apache.isis.applib.services.wrapper.WrapperFactory;
+import org.apache.isis.applib.services.wrapper.WrappingObject;
+import org.apache.isis.core.unittestsupport.jmocking.JUnitRuleMockery2;
+import org.apache.isis.core.wrapper.proxy.ProxyInstantiator;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class WrapperFactoryAbstractTest {
+
+    private static class DomainObject {
+    }
+
+    private static class WrappedDomainObject extends DomainObject implements WrappingObject {
+
+        private final DomainObject wrappedObject;
+        private final WrapperFactory.ExecutionMode mode;
+
+        WrappedDomainObject(final DomainObject wrappedObject, WrapperFactory.ExecutionMode mode) {
+            this.wrappedObject = wrappedObject;
+            this.mode = mode;
+        }
+
+        @Override
+        public void __isis_save() {
+        }
+
+        @Override
+        public Object __isis_wrapped() {
+            return wrappedObject;
+        }
+
+        @Override
+        public WrapperFactory.ExecutionMode __isis_executionMode() {
+            return mode;
+        }
+    }
+
+    @Rule
+    public JUnitRuleMockery2 context = JUnitRuleMockery2.createFor(JUnitRuleMockery2.Mode.INTERFACES_AND_CLASSES);
+
+    @Mock
+    private ProxyInstantiator mockProxyInstantiator;
+    private WrapperFactoryAbstract wrapperFactory;
+
+    private DomainObject createProxyCalledWithDomainObject;
+    private WrapperFactory.ExecutionMode createProxyCalledWithMode;
+
+    @Before
+    public void setUp() throws Exception {
+        wrapperFactory = new WrapperFactoryAbstract(mockProxyInstantiator) {
+
+            @Override
+            protected <T> T createProxy(T domainObject, ExecutionMode mode) {
+                WrapperFactoryAbstractTest.this.createProxyCalledWithMode = mode;
+                WrapperFactoryAbstractTest.this.createProxyCalledWithDomainObject = (DomainObject) domainObject;
+                return domainObject;
+            }
+        };
+    }
+
+    @Test
+    public void wrap_ofUnwrapped_delegates_to_createProxy() throws Exception {
+        final DomainObject domainObject = new DomainObject();
+        wrapperFactory.wrap(domainObject);
+
+        assertThat(createProxyCalledWithDomainObject, is(domainObject));
+        assertThat(createProxyCalledWithMode, is(WrapperFactory.ExecutionMode.EXECUTE));
+    }
+
+
+    @Test
+    public void wrap_ofWrapped_sameMode_returnsUnchanged() throws Exception {
+        // given
+        final DomainObject wrappedObject = new DomainObject();
+        final DomainObject domainObject = new WrappedDomainObject(wrappedObject, WrapperFactory.ExecutionMode.EXECUTE);
+
+        // when
+        final DomainObject wrappingObject = wrapperFactory.wrap(domainObject, WrapperFactory.ExecutionMode.EXECUTE);
+
+        // then
+        assertThat(wrappingObject, is(domainObject));
+        assertThat(createProxyCalledWithDomainObject, is(nullValue()));
+    }
+
+    @Test
+    public void wrap_ofWrapped_differentMode_delegates_to_createProxy() throws Exception {
+        // given
+        final DomainObject wrappedObject = new DomainObject();
+        final DomainObject domainObject = new WrappedDomainObject(wrappedObject, WrapperFactory.ExecutionMode.EXECUTE);
+
+        // when
+        final DomainObject wrappingObject = wrapperFactory.wrap(domainObject, WrapperFactory.ExecutionMode.SKIP_RULES);
+
+        // then
+        assertThat(wrappingObject, is(not(domainObject)));
+        assertThat(createProxyCalledWithDomainObject, is(wrappedObject));
+        assertThat(createProxyCalledWithMode, is(WrapperFactory.ExecutionMode.SKIP_RULES));
+    }
+
+}
\ No newline at end of file