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/05/07 09:54:13 UTC

[3/4] ISIS-550: PostsCollectionAddToEvent first-cut impl

http://git-wip-us.apache.org/repos/asf/isis/blob/2637a055/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/ImperativeFacetUtilsTest.java
----------------------------------------------------------------------
diff --git a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/ImperativeFacetUtilsTest.java b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/ImperativeFacetUtilsTest.java
index 23238d9..5efadc3 100644
--- a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/ImperativeFacetUtilsTest.java
+++ b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/ImperativeFacetUtilsTest.java
@@ -19,8 +19,15 @@
 
 package org.apache.isis.core.metamodel.facets;
 
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.junit.Assert.assertThat;
+
 import java.lang.reflect.Method;
+
 import com.google.common.collect.Lists;
+
 import org.jmock.Expectations;
 import org.jmock.Mockery;
 import org.jmock.integration.junit4.JMock;
@@ -28,15 +35,12 @@ import org.jmock.integration.junit4.JUnit4Mockery;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+
 import org.apache.isis.applib.filter.Filter;
 import org.apache.isis.core.metamodel.facetapi.Facet;
-import org.apache.isis.core.metamodel.facets.ImperativeFacetUtils.ImperativeFacetFlags;
 import org.apache.isis.core.metamodel.spec.feature.ObjectMember;
 import org.apache.isis.core.unittestsupport.jmocking.JavassistImposteriser;
 
-import static org.hamcrest.CoreMatchers.*;
-import static org.junit.Assert.assertThat;
-
 @RunWith(JMock.class)
 public class ImperativeFacetUtilsTest {
 
@@ -78,7 +82,7 @@ public class ImperativeFacetUtilsTest {
                 will(returnValue(Lists.newArrayList()));
             }
         });
-        final ImperativeFacetFlags flags = ImperativeFacetUtils.getImperativeFacetFlags(mockObjectMember, method);
+        final ImperativeFacet.Flags flags = ImperativeFacetUtils.getImperativeFacetFlags(mockObjectMember, method);
         assertThat(flags, is(not(nullValue())));
         assertThat(flags.impliesResolve(), is(false));
         assertThat(flags.impliesObjectChanged(), is(false));
@@ -94,7 +98,7 @@ public class ImperativeFacetUtilsTest {
                 will(returnValue(Lists.newArrayList((Facet) imperativeFacet)));
             }
         });
-        final ImperativeFacetFlags flags = ImperativeFacetUtils.getImperativeFacetFlags(mockObjectMember, method);
+        final ImperativeFacet.Flags flags = ImperativeFacetUtils.getImperativeFacetFlags(mockObjectMember, method);
         assertThat(flags, is(not(nullValue())));
         // TODO: need more tests here, these don't go deep enough...
     }

http://git-wip-us.apache.org/repos/asf/isis/blob/2637a055/core/metamodel/src/test/java/org/apache/isis/core/progmodel/facets/collections/event/PostsCollectionAddedEventFacetAnnotationTest_newEvent.java
----------------------------------------------------------------------
diff --git a/core/metamodel/src/test/java/org/apache/isis/core/progmodel/facets/collections/event/PostsCollectionAddedEventFacetAnnotationTest_newEvent.java b/core/metamodel/src/test/java/org/apache/isis/core/progmodel/facets/collections/event/PostsCollectionAddedEventFacetAnnotationTest_newEvent.java
index 5f22cc4..c4f15c9 100644
--- a/core/metamodel/src/test/java/org/apache/isis/core/progmodel/facets/collections/event/PostsCollectionAddedEventFacetAnnotationTest_newEvent.java
+++ b/core/metamodel/src/test/java/org/apache/isis/core/progmodel/facets/collections/event/PostsCollectionAddedEventFacetAnnotationTest_newEvent.java
@@ -19,23 +19,33 @@ package org.apache.isis.core.progmodel.facets.collections.event;
 import static org.hamcrest.CoreMatchers.is;
 import static org.junit.Assert.assertThat;
 
-import org.joda.time.LocalDate;
+import java.util.Set;
+
 import org.junit.Test;
 
-import org.apache.isis.applib.services.eventbus.PropertyChangedEvent;
+import org.apache.isis.applib.Identifier;
+import org.apache.isis.applib.services.eventbus.CollectionAddedToEvent;
 
 public class PostsCollectionAddedEventFacetAnnotationTest_newEvent {
 
-    public static class SomeDomainObject {}
+    public static class SomeDomainObject {
+        public Set<SomeReferencedObject> getReferences() { return null; }
+    }
+    public static class SomeReferencedObject {}
+    
+    public static class SomeDomainObjectCollectionAddedToEvent extends CollectionAddedToEvent<SomeDomainObject, SomeReferencedObject> {}
     
-//    public static class SomeDomainObjectCollectionAddedEvent extends AddToCollectionEvent<SomeDomainObject, SomeDomainObject> {}
-//    
-//    @Test
-//    public void test() throws Exception {
-//        SomeDomainObject sdo = new SomeDomainObject();
-//        final PropertyChangedEvent<SomeDomainObject, LocalDate> ev = PostsPropertyChangedEventFacetAnnotation.newEvent(SomeDatePropertyChangedEvent.class, new SomeDomainObject(), sdo);
-//        assertThat(ev.getSource(), is(sdo));
-//        assertThat(ev.getNewValue(), is(new LocalDate(2013,4,1)));
-//    }
+    @Test
+    public void test() throws Exception {
+        SomeDomainObject sdo = new SomeDomainObject();
+        SomeReferencedObject other = new SomeReferencedObject();
+        Identifier identifier = Identifier.propertyOrCollectionIdentifier(SomeDomainObject.class, "references");
+
+        final CollectionAddedToEvent<SomeDomainObject, SomeReferencedObject> ev = PostsCollectionAddedToEventFacetAnnotation.newEvent(
+                SomeDomainObjectCollectionAddedToEvent.class, sdo, identifier, other);
+        assertThat(ev.getSource(), is(sdo));
+        assertThat(ev.getIdentifier(), is(identifier));
+        assertThat(ev.getValue(), is(other));
+    }
 
 }

http://git-wip-us.apache.org/repos/asf/isis/blob/2637a055/core/metamodel/src/test/java/org/apache/isis/core/progmodel/facets/properties/event/PostsPropertyChangedEventFacetAnnotationTest_newEvent.java
----------------------------------------------------------------------
diff --git a/core/metamodel/src/test/java/org/apache/isis/core/progmodel/facets/properties/event/PostsPropertyChangedEventFacetAnnotationTest_newEvent.java b/core/metamodel/src/test/java/org/apache/isis/core/progmodel/facets/properties/event/PostsPropertyChangedEventFacetAnnotationTest_newEvent.java
index 972aea6..f997473 100644
--- a/core/metamodel/src/test/java/org/apache/isis/core/progmodel/facets/properties/event/PostsPropertyChangedEventFacetAnnotationTest_newEvent.java
+++ b/core/metamodel/src/test/java/org/apache/isis/core/progmodel/facets/properties/event/PostsPropertyChangedEventFacetAnnotationTest_newEvent.java
@@ -22,6 +22,7 @@ import static org.junit.Assert.assertThat;
 import org.joda.time.LocalDate;
 import org.junit.Test;
 
+import org.apache.isis.applib.Identifier;
 import org.apache.isis.applib.services.eventbus.PropertyChangedEvent;
 
 public class PostsPropertyChangedEventFacetAnnotationTest_newEvent {
@@ -32,11 +33,18 @@ public class PostsPropertyChangedEventFacetAnnotationTest_newEvent {
     
     @Test
     public void test() throws Exception {
+
         SomeDomainObject sdo = new SomeDomainObject();
-        final PropertyChangedEvent<SomeDomainObject, LocalDate> ev = PostsPropertyChangedEventFacetAnnotation.newEvent(SomeDatePropertyChangedEvent.class, new LocalDate(2013,4,1), new LocalDate(2013,5,2), sdo);
+        Identifier identifier = Identifier.propertyOrCollectionIdentifier(SomeDomainObject.class, "someDateProperty");
+        LocalDate oldValue = new LocalDate(2013,4,1);
+        LocalDate newValue = new LocalDate(2013,5,2);
+        
+        final PropertyChangedEvent<SomeDomainObject, LocalDate> ev = 
+                PostsPropertyChangedEventFacetAnnotation.newEvent(SomeDatePropertyChangedEvent.class, sdo, identifier, oldValue, newValue);
         assertThat(ev.getSource(), is(sdo));
-        assertThat(ev.getOldValue(), is(new LocalDate(2013,4,1)));
-        assertThat(ev.getNewValue(), is(new LocalDate(2013,5,2)));
+        assertThat(ev.getIdentifier(), is(identifier));
+        assertThat(ev.getOldValue(), is(oldValue));
+        assertThat(ev.getNewValue(), is(newValue));
     }
 
 }

http://git-wip-us.apache.org/repos/asf/isis/blob/2637a055/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
index ab1e808..b8a4464 100644
--- 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
@@ -23,14 +23,17 @@ 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 = new ArrayList<Method>();
-    private final List<Method> vetoedMethods = new ArrayList<Method>();
+    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;

http://git-wip-us.apache.org/repos/asf/isis/blob/2637a055/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
index 93b6d6b..1dc2293 100644
--- 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
@@ -27,6 +27,7 @@ import java.util.List;
 import java.util.Map;
 
 import org.apache.isis.applib.annotation.Where;
+import org.apache.isis.applib.annotation.WrapperPolicy;
 import org.apache.isis.applib.events.CollectionAccessEvent;
 import org.apache.isis.applib.events.InteractionEvent;
 import org.apache.isis.applib.events.ObjectTitleEvent;
@@ -40,8 +41,8 @@ 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.applib.services.wrapper.WrapperObject;
 import org.apache.isis.core.commons.authentication.AuthenticationSession;
 import org.apache.isis.core.commons.authentication.AuthenticationSessionProvider;
 import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
@@ -54,6 +55,7 @@ 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.PostsEventWithWrapperPolicy;
 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;
@@ -62,6 +64,7 @@ import org.apache.isis.core.metamodel.facets.collections.modify.CollectionRemove
 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.PropertyClearFacet;
 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;
@@ -176,9 +179,6 @@ public class DomainObjectInvocationHandler<T> extends DelegatingInvocationHandle
             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)) {
@@ -186,11 +186,12 @@ public class DomainObjectInvocationHandler<T> extends DelegatingInvocationHandle
             }
 
             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);
             }
         }
@@ -200,16 +201,15 @@ public class DomainObjectInvocationHandler<T> extends DelegatingInvocationHandle
                 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);
             }
         }
@@ -230,6 +230,10 @@ public class DomainObjectInvocationHandler<T> extends DelegatingInvocationHandle
 
         if (objectMember instanceof ObjectAction) {
 
+            // for all members, check visibility and usability
+            checkVisibility(getAuthenticationSession(), targetAdapter, objectMember);
+
+
             if (instanceOf(imperativeFacets, ActionValidationFacetViaMethod.class)) {
                 throw new UnsupportedOperationException(String.format("Cannot invoke supporting method '%s'; use only the 'invoke' method", memberName));
             }
@@ -338,10 +342,13 @@ public class DomainObjectInvocationHandler<T> extends DelegatingInvocationHandle
     // /////////////////////////////////////////////////////////////////
 
     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");
         }
 
+        checkVisibility(getAuthenticationSession(), targetAdapter, otoa);
+
         resolveIfRequired(targetAdapter);
 
         final ObjectAdapter currentReferencedAdapter = otoa.get(targetAdapter);
@@ -361,13 +368,23 @@ public class DomainObjectInvocationHandler<T> extends DelegatingInvocationHandle
             throw new IllegalArgumentException("Invoking a setter should only have a single argument");
         }
 
-        resolveIfRequired(targetAdapter);
-
         final Object argumentObj = underlying(args[0]);
+
+        final WrapperPolicy wrapperPolicy = determineWrapperPolicy(otoa, argumentObj);
+        if(wrapperPolicy == WrapperPolicy.ENFORCE_RULES) {
+            checkVisibility(getAuthenticationSession(), targetAdapter, otoa);
+            checkUsability(getAuthenticationSession(), targetAdapter, otoa);
+        }
+
         final ObjectAdapter argumentAdapter = argumentObj != null ? getAdapterManager().adapterFor(argumentObj) : null;
 
-        final InteractionResult interactionResult = otoa.isAssociationValid(targetAdapter, argumentAdapter).getInteractionResult();
-        notifyListenersAndVetoIfRequired(interactionResult);
+        resolveIfRequired(targetAdapter);
+
+
+        if(wrapperPolicy == WrapperPolicy.ENFORCE_RULES) {
+            final InteractionResult interactionResult = otoa.isAssociationValid(targetAdapter, argumentAdapter).getInteractionResult();
+            notifyListenersAndVetoIfRequired(interactionResult);
+        }
 
         if (getExecutionMode() == ExecutionMode.EXECUTE) {
             otoa.set(targetAdapter, argumentAdapter);
@@ -378,15 +395,33 @@ public class DomainObjectInvocationHandler<T> extends DelegatingInvocationHandle
         return null;
     }
 
+    private static WrapperPolicy determineWrapperPolicy(final OneToOneAssociation otoa, final Object argumentObj) {
+        final PostsEventWithWrapperPolicy wrapperPolicyFacet;
+        if(argumentObj != null) {
+            final PropertySetterFacet setterFacet = otoa.getFacet(PropertySetterFacet.class);
+            wrapperPolicyFacet = PostsEventWithWrapperPolicy.Util.getWrapperPolicyFacet(setterFacet);
+        } else {
+            final PropertyClearFacet clearFacet = otoa.getFacet(PropertyClearFacet.class);
+            wrapperPolicyFacet = PostsEventWithWrapperPolicy.Util.getWrapperPolicyFacet(clearFacet);
+        }
+        return wrapperPolicyFacet != null ? wrapperPolicyFacet.getWrapperPolicy() : WrapperPolicy.ENFORCE_RULES;
+    }
+
+    
+
     // /////////////////////////////////////////////////////////////////
     // 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");
         }
 
+        checkVisibility(getAuthenticationSession(), targetAdapter, otma);
+        
         resolveIfRequired(targetAdapter);
 
         final ObjectAdapter currentReferencedAdapter = otma.get(targetAdapter);
@@ -445,6 +480,12 @@ public class DomainObjectInvocationHandler<T> extends DelegatingInvocationHandle
             throw new IllegalArgumentException("Invoking a addTo should only have a single argument");
         }
 
+        final WrapperPolicy wrapperPolicy = determineAddToWrapperPolicy(otma);
+        if(wrapperPolicy == WrapperPolicy.ENFORCE_RULES) {
+            checkVisibility(getAuthenticationSession(), targetAdapter, otma);
+            checkUsability(getAuthenticationSession(), targetAdapter, otma);
+        }
+
         resolveIfRequired(targetAdapter);
 
         final Object argumentObj = underlying(args[0]);
@@ -453,8 +494,10 @@ public class DomainObjectInvocationHandler<T> extends DelegatingInvocationHandle
         }
         final ObjectAdapter argumentNO = getAdapterManager().adapterFor(argumentObj);
 
-        final InteractionResult interactionResult = otma.isValidToAdd(targetAdapter, argumentNO).getInteractionResult();
-        notifyListenersAndVetoIfRequired(interactionResult);
+        if(wrapperPolicy == WrapperPolicy.ENFORCE_RULES) {
+            final InteractionResult interactionResult = otma.isValidToAdd(targetAdapter, argumentNO).getInteractionResult();
+            notifyListenersAndVetoIfRequired(interactionResult);
+        }
 
         if (getExecutionMode() == ExecutionMode.EXECUTE) {
             otma.addElement(targetAdapter, argumentNO);
@@ -465,6 +508,13 @@ public class DomainObjectInvocationHandler<T> extends DelegatingInvocationHandle
         return null;
     }
 
+    private static WrapperPolicy determineAddToWrapperPolicy(final OneToManyAssociation otma) {
+        final CollectionAddToFacet facet = otma.getFacet(CollectionAddToFacet.class);
+        final PostsEventWithWrapperPolicy wrapperPolicyFacet = PostsEventWithWrapperPolicy.Util.getWrapperPolicyFacet(facet);
+        return wrapperPolicyFacet != null ? wrapperPolicyFacet.getWrapperPolicy() : WrapperPolicy.ENFORCE_RULES;
+    }
+
+
     // /////////////////////////////////////////////////////////////////
     // collection - remove from
     // /////////////////////////////////////////////////////////////////
@@ -474,6 +524,13 @@ public class DomainObjectInvocationHandler<T> extends DelegatingInvocationHandle
             throw new IllegalArgumentException("Invoking a removeFrom should only have a single argument");
         }
 
+        final WrapperPolicy wrapperPolicy = determineRemoveFromWrapperPolicy(otma);
+        if(wrapperPolicy == WrapperPolicy.ENFORCE_RULES) {
+            checkVisibility(getAuthenticationSession(), targetAdapter, otma);
+            checkUsability(getAuthenticationSession(), targetAdapter, otma);
+        }
+
+
         resolveIfRequired(targetAdapter);
 
         final Object argumentObj = underlying(args[0]);
@@ -482,8 +539,10 @@ public class DomainObjectInvocationHandler<T> extends DelegatingInvocationHandle
         }
         final ObjectAdapter argumentAdapter = getAdapterManager().adapterFor(argumentObj);
 
-        final InteractionResult interactionResult = otma.isValidToRemove(targetAdapter, argumentAdapter).getInteractionResult();
-        notifyListenersAndVetoIfRequired(interactionResult);
+        if(wrapperPolicy == WrapperPolicy.ENFORCE_RULES) {
+            final InteractionResult interactionResult = otma.isValidToRemove(targetAdapter, argumentAdapter).getInteractionResult();
+            notifyListenersAndVetoIfRequired(interactionResult);
+        }
 
         if (getExecutionMode() == ExecutionMode.EXECUTE) {
             otma.removeElement(targetAdapter, argumentAdapter);
@@ -494,6 +553,13 @@ public class DomainObjectInvocationHandler<T> extends DelegatingInvocationHandle
         return null;
     }
 
+    private static WrapperPolicy determineRemoveFromWrapperPolicy(final OneToManyAssociation otma) {
+        final CollectionRemoveFromFacet removeFromFacet = otma.getFacet(CollectionRemoveFromFacet.class);
+
+        final PostsEventWithWrapperPolicy wrapperPolicyFacet = PostsEventWithWrapperPolicy.Util.getWrapperPolicyFacet(removeFromFacet);
+        return wrapperPolicyFacet != null ? wrapperPolicyFacet.getWrapperPolicy() : WrapperPolicy.ENFORCE_RULES;
+    }
+
     // /////////////////////////////////////////////////////////////////
     // action
     // /////////////////////////////////////////////////////////////////

http://git-wip-us.apache.org/repos/asf/isis/blob/2637a055/example/application/quickstart_wicket_restful_jdo/dom/src/main/java/dom/todo/ToDoItem.java
----------------------------------------------------------------------
diff --git a/example/application/quickstart_wicket_restful_jdo/dom/src/main/java/dom/todo/ToDoItem.java b/example/application/quickstart_wicket_restful_jdo/dom/src/main/java/dom/todo/ToDoItem.java
index c384a9c..5f9fc1d 100644
--- a/example/application/quickstart_wicket_restful_jdo/dom/src/main/java/dom/todo/ToDoItem.java
+++ b/example/application/quickstart_wicket_restful_jdo/dom/src/main/java/dom/todo/ToDoItem.java
@@ -22,13 +22,17 @@ import java.math.BigDecimal;
 import java.net.MalformedURLException;
 import java.net.URL;
 import java.util.*;
+
 import javax.jdo.JDOHelper;
 import javax.jdo.annotations.IdentityType;
 import javax.jdo.annotations.VersionStrategy;
+
 import com.google.common.base.Objects;
 import com.google.common.base.Predicate;
 import com.google.common.collect.Ordering;
+
 import org.joda.time.LocalDate;
+
 import org.apache.isis.applib.DomainObjectContainer;
 import org.apache.isis.applib.NonRecoverableException;
 import org.apache.isis.applib.RecoverableException;
@@ -41,8 +45,11 @@ import org.apache.isis.applib.clock.Clock;
 import org.apache.isis.applib.services.background.BackgroundService;
 import org.apache.isis.applib.services.clock.ClockService;
 import org.apache.isis.applib.services.command.CommandContext;
+import org.apache.isis.applib.services.eventbus.CollectionAddedToEvent;
 import org.apache.isis.applib.services.eventbus.EventBusService;
+import org.apache.isis.applib.services.eventbus.PropertyChangedEvent;
 import org.apache.isis.applib.services.scratchpad.Scratchpad;
+import org.apache.isis.applib.services.wrapper.WrapperFactory;
 import org.apache.isis.applib.util.ObjectContracts;
 import org.apache.isis.applib.util.TitleBuffer;
 import org.apache.isis.applib.value.Blob;
@@ -410,6 +417,7 @@ public class ToDoItem implements Comparable<ToDoItem> {
     private String notes;
 
     @javax.jdo.annotations.Column(allowsNull="true", length=400)
+    @PostsPropertyChangedEvent()
     public String getNotes() {
         return notes;
     }
@@ -479,6 +487,7 @@ public class ToDoItem implements Comparable<ToDoItem> {
     @javax.jdo.annotations.Element(column="dependentId")
     private SortedSet<ToDoItem> dependencies = new TreeSet<ToDoItem>();
 
+    @PostsCollectionAddedToEvent(wrapperPolicy=WrapperPolicy.SKIP_RULES)
     @SortedBy(DependenciesComparator.class)
     public SortedSet<ToDoItem> getDependencies() {
         return dependencies;
@@ -487,13 +496,19 @@ public class ToDoItem implements Comparable<ToDoItem> {
     public void setDependencies(final SortedSet<ToDoItem> dependencies) {
         this.dependencies = dependencies;
     }
-
     
+    public void addToDependencies(final ToDoItem toDoItem) {
+        getDependencies().add(toDoItem);
+    }
+    public void removeFromDependencies(final ToDoItem toDoItem) {
+        getDependencies().remove(toDoItem);
+    }
+
     @PublishedAction
     public ToDoItem add(
             @TypicalLength(20)
             final ToDoItem toDoItem) {
-        getDependencies().add(toDoItem);
+        wrapperFactory.wrap(this).addToDependencies(toDoItem);
         return this;
     }
     public List<ToDoItem> autoComplete0Add(final @MinLength(2) String search) {
@@ -523,7 +538,7 @@ public class ToDoItem implements Comparable<ToDoItem> {
     public ToDoItem remove(
             @TypicalLength(20)
             final ToDoItem toDoItem) {
-        getDependencies().remove(toDoItem);
+        this.removeFromDependencies(toDoItem);
         return this;
     }
     // disable action dependent on state of object
@@ -733,6 +748,7 @@ public class ToDoItem implements Comparable<ToDoItem> {
     // Events
     // //////////////////////////////////////
 
+
     public static abstract class AbstractEvent {
         private final String eventDescription;
         private final ToDoItem toDoItem;
@@ -899,6 +915,9 @@ public class ToDoItem implements Comparable<ToDoItem> {
         this.eventBusService = eventBusService;
     }
 
+    @javax.inject.Inject
+    private WrapperFactory wrapperFactory;
+    
     //endregion
 
 }

http://git-wip-us.apache.org/repos/asf/isis/blob/2637a055/example/application/quickstart_wicket_restful_jdo/dom/src/main/java/dom/todo/ToDoItemSubscriptions.java
----------------------------------------------------------------------
diff --git a/example/application/quickstart_wicket_restful_jdo/dom/src/main/java/dom/todo/ToDoItemSubscriptions.java b/example/application/quickstart_wicket_restful_jdo/dom/src/main/java/dom/todo/ToDoItemSubscriptions.java
index c748e18..0a4b02f 100644
--- a/example/application/quickstart_wicket_restful_jdo/dom/src/main/java/dom/todo/ToDoItemSubscriptions.java
+++ b/example/application/quickstart_wicket_restful_jdo/dom/src/main/java/dom/todo/ToDoItemSubscriptions.java
@@ -22,7 +22,9 @@ import com.google.common.eventbus.Subscribe;
 
 import org.apache.isis.applib.DomainObjectContainer;
 import org.apache.isis.applib.annotation.Programmatic;
+import org.apache.isis.applib.services.eventbus.CollectionAddedToEvent;
 import org.apache.isis.applib.services.eventbus.EventBusService;
+import org.apache.isis.applib.services.eventbus.PropertyChangedEvent;
 
 public class ToDoItemSubscriptions {
 
@@ -36,6 +38,18 @@ public class ToDoItemSubscriptions {
         LOG.info(ev.getEventDescription() + ": " + container.titleOf(ev.getToDoItem()));
     }
 
+    @Programmatic
+    @Subscribe
+    public void on(PropertyChangedEvent<?,?> ev) {
+        LOG.info(container.titleOf(ev.getSource()) + ", changed " + ev.getIdentifier().getMemberName() + " : " + ev.getOldValue() + " -> " + ev.getNewValue());
+    }
+    
+    @Programmatic
+    @Subscribe
+    public void on(CollectionAddedToEvent<?,?> ev) {
+        LOG.info(container.titleOf(ev.getSource()) + ", added to " + ev.getIdentifier().getMemberName() + " : " + ev.getValue());
+    }
+    
     //region > injected services
     // //////////////////////////////////////
     

http://git-wip-us.apache.org/repos/asf/isis/blob/2637a055/example/application/quickstart_wicket_restful_jdo/webapp/pom.xml
----------------------------------------------------------------------
diff --git a/example/application/quickstart_wicket_restful_jdo/webapp/pom.xml b/example/application/quickstart_wicket_restful_jdo/webapp/pom.xml
index e415798..1381579 100644
--- a/example/application/quickstart_wicket_restful_jdo/webapp/pom.xml
+++ b/example/application/quickstart_wicket_restful_jdo/webapp/pom.xml
@@ -185,6 +185,10 @@
         </dependency>
         <dependency>
             <groupId>org.apache.isis.core</groupId>
+            <artifactId>isis-core-wrapper</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.isis.core</groupId>
             <artifactId>isis-core-profilestore</artifactId>
         </dependency>
         <dependency>

http://git-wip-us.apache.org/repos/asf/isis/blob/2637a055/example/application/quickstart_wicket_restful_jdo/webapp/src/main/webapp/WEB-INF/isis.properties
----------------------------------------------------------------------
diff --git a/example/application/quickstart_wicket_restful_jdo/webapp/src/main/webapp/WEB-INF/isis.properties b/example/application/quickstart_wicket_restful_jdo/webapp/src/main/webapp/WEB-INF/isis.properties
index 87f1857..153f3f9 100644
--- a/example/application/quickstart_wicket_restful_jdo/webapp/src/main/webapp/WEB-INF/isis.properties
+++ b/example/application/quickstart_wicket_restful_jdo/webapp/src/main/webapp/WEB-INF/isis.properties
@@ -189,6 +189,7 @@ isis.services = \
                 org.apache.isis.applib.annotation.Bulk$InteractionContext,\
                 org.apache.isis.applib.services.scratchpad.Scratchpad,\
                 org.apache.isis.applib.services.queryresultscache.QueryResultsCache,\
+                org.apache.isis.core.wrapper.WrapperFactoryDefault,\
                 \
                 # JDO implementation of the EventBusService, \
                 org.apache.isis.objectstore.jdo.datanucleus.service.eventbus.EventBusServiceJdo,\