You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@isis.apache.org by da...@apache.org on 2013/07/01 13:21:59 UTC

git commit: ISIS-452: @PostsPropertyChangedEvent annotation

Updated Branches:
  refs/heads/master e1fa54a20 -> bd0be0e07


ISIS-452: @PostsPropertyChangedEvent annotation


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

Branch: refs/heads/master
Commit: bd0be0e078afe8aa2f1cd9290ae2c30aa87df938
Parents: e1fa54a
Author: Dan Haywood <da...@apache.org>
Authored: Mon Jul 1 12:21:44 2013 +0100
Committer: Dan Haywood <da...@apache.org>
Committed: Mon Jul 1 12:21:44 2013 +0100

----------------------------------------------------------------------
 .../service/eventbus/EventBusServiceJdo.java    |  37 ++---
 .../annotation/PostsPropertyChangedEvent.java   |  59 +++++++
 .../applib/services/eventbus/ChangedEvent.java  |  49 ------
 .../services/eventbus/EventBusService.java      |  41 ++---
 .../services/eventbus/PropertyChangedEvent.java |  61 +++++++
 .../publish/PublishedActionFacetAbstract.java   |   4 +-
 .../event/PostsPropertyChangedEventFacet.java   |  35 ++++
 .../PostsPropertyChangedEventFacetAbstract.java |  37 +++++
 ...pertyChangedEventAnnotationFacetFactory.java | 117 +++++++++++++
 ...ostsPropertyChangedEventFacetAnnotation.java | 164 +++++++++++++++++++
 .../dflt/ProgrammingModelFacetsJava5.java       |   3 +
 ...hangedEventFacetAnnotationTest_newEvent.java |  42 +++++
 .../eventbus/EventBusServiceDefault.java        |  67 ++++++++
 .../system/persistence/PersistenceSession.java  |  16 ++
 14 files changed, 628 insertions(+), 104 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/isis/blob/bd0be0e0/component/objectstore/jdo/jdo-datanucleus/src/main/java/org/apache/isis/objectstore/jdo/datanucleus/service/eventbus/EventBusServiceJdo.java
----------------------------------------------------------------------
diff --git a/component/objectstore/jdo/jdo-datanucleus/src/main/java/org/apache/isis/objectstore/jdo/datanucleus/service/eventbus/EventBusServiceJdo.java b/component/objectstore/jdo/jdo-datanucleus/src/main/java/org/apache/isis/objectstore/jdo/datanucleus/service/eventbus/EventBusServiceJdo.java
index 510f7d2..d13ecb3 100644
--- a/component/objectstore/jdo/jdo-datanucleus/src/main/java/org/apache/isis/objectstore/jdo/datanucleus/service/eventbus/EventBusServiceJdo.java
+++ b/component/objectstore/jdo/jdo-datanucleus/src/main/java/org/apache/isis/objectstore/jdo/datanucleus/service/eventbus/EventBusServiceJdo.java
@@ -16,45 +16,30 @@
  */
 package org.apache.isis.objectstore.jdo.datanucleus.service.eventbus;
 
-import java.util.Collection;
-
-import com.google.common.eventbus.EventBus;
-
-import org.apache.isis.applib.services.eventbus.EventBusService;
-import org.apache.isis.core.runtime.system.context.IsisContext;
-import org.apache.isis.objectstore.jdo.applib.service.support.IsisJdoSupport;
+import org.apache.isis.core.runtime.services.eventbus.EventBusServiceDefault;
 import org.apache.isis.objectstore.jdo.datanucleus.JDOStateManagerForIsis;
 import org.apache.isis.objectstore.jdo.datanucleus.JDOStateManagerForIsis.Hint;
 
-public class EventBusServiceJdo extends EventBusService {
-
-    
-    @Override
-    protected EventBus getEventBus() {
-        return IsisContext.getSession().getEventBus();
-    }
-
-    // //////////////////////////////////////
+/**
+ * An implementation that allows events to be {@link #post(Object) posted} from the
+ * setters of entities, automatically ignoring any calls to those setters that occur
+ * as a side-effect of the JDO load/detach lifecycle. 
+ */
+public class EventBusServiceJdo extends EventBusServiceDefault {
 
-    @Override
-    protected void ensureLoaded(final Collection<?> collection) {
-        isisJdoSupport.ensureLoaded(collection);
-    }
 
     /**
      * skip if called in any way by way of the {@link JDOStateManagerForIsis}.
+     * 
+     * <p>
+     * The {@link JDOStateManagerForIsis} sets a {@link JDOStateManagerForIsis#hint threadlocal}
+     * if it has been called.
      */
     @Override
     protected boolean skip(Object event) {
         return JDOStateManagerForIsis.hint.get() != Hint.NONE;
     }
     
-    // //////////////////////////////////////
-
-    private IsisJdoSupport isisJdoSupport;
-    public void setIsisJdoSupport(IsisJdoSupport isisJdoSupport) {
-        this.isisJdoSupport = isisJdoSupport;
-    }
 
 }
 

http://git-wip-us.apache.org/repos/asf/isis/blob/bd0be0e0/core/applib/src/main/java/org/apache/isis/applib/annotation/PostsPropertyChangedEvent.java
----------------------------------------------------------------------
diff --git a/core/applib/src/main/java/org/apache/isis/applib/annotation/PostsPropertyChangedEvent.java b/core/applib/src/main/java/org/apache/isis/applib/annotation/PostsPropertyChangedEvent.java
new file mode 100644
index 0000000..f1d9491
--- /dev/null
+++ b/core/applib/src/main/java/org/apache/isis/applib/annotation/PostsPropertyChangedEvent.java
@@ -0,0 +1,59 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+package org.apache.isis.applib.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.apache.isis.applib.services.eventbus.PropertyChangedEvent;
+
+/**
+ * Applies only to properties; any changes should be propagated as events to subscribers.  
+ * Only posted after a successful validation.
+ * 
+ * <p>For example:
+ * <pre>
+ * public static class StartDateChangedEvent extends PropertyChangedEvent {}
+ * 
+ * &#64;PostsPropertyChangedEvent(StartDateChangedEvent.class)
+ * public LocalDate getStartDate() { ...}
+ * </pre>
+ * 
+ * <p>
+ * It is highly advisable that only domain services - not domain entities - are registered as subscribers.  
+ * Domain services are guaranteed to be instantiated and resident in memory, whereas the same is not true
+ * of domain entities.  The typical implementation of a domain service subscriber is to identify the impacted entities,
+ * load them using a repository, and then to delegate to the event to them.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE, ElementType.METHOD})
+public @interface PostsPropertyChangedEvent {
+
+    /**
+     * The subclass of {@link PropertyChangedEvent event} to be instantiated and posted.
+     * 
+     * <p>
+     * This subclass must provide a no-arg constructor; the fields are set reflectively.
+     */
+    Class<? extends PropertyChangedEvent<?,?>> value();
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/bd0be0e0/core/applib/src/main/java/org/apache/isis/applib/services/eventbus/ChangedEvent.java
----------------------------------------------------------------------
diff --git a/core/applib/src/main/java/org/apache/isis/applib/services/eventbus/ChangedEvent.java b/core/applib/src/main/java/org/apache/isis/applib/services/eventbus/ChangedEvent.java
deleted file mode 100644
index d023d15..0000000
--- a/core/applib/src/main/java/org/apache/isis/applib/services/eventbus/ChangedEvent.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you under the Apache License, Version 2.0 (the
- *  "License"); you may not use this file except in compliance
- *  with the License.  You may obtain a copy of the License at
- *
- *        http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing,
- *  software distributed under the License is distributed on an
- *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *  KIND, either express or implied.  See the License for the
- *  specific language governing permissions and limitations
- *  under the License.
- */
-package org.apache.isis.applib.services.eventbus;
-
-import org.apache.isis.applib.util.ObjectContracts;
-
-public abstract class ChangedEvent<S,T> {
-    private final S source;
-    private final T oldValue;
-    private final T newValue;
-    
-    public ChangedEvent(S source, T oldValue, T newValue) {
-        this.source = source;
-        this.oldValue = oldValue;
-        this.newValue = newValue;
-    }
-
-    public S getSource() {
-        return source;
-    }
-    
-    public T getOldValue() {
-        return oldValue;
-    }
-    public T getNewValue() {
-        return newValue;
-    }
-    
-    @Override
-    public String toString() {
-        return ObjectContracts.toString(this, "source,oldValue,newValue");
-    }
-}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/isis/blob/bd0be0e0/core/applib/src/main/java/org/apache/isis/applib/services/eventbus/EventBusService.java
----------------------------------------------------------------------
diff --git a/core/applib/src/main/java/org/apache/isis/applib/services/eventbus/EventBusService.java b/core/applib/src/main/java/org/apache/isis/applib/services/eventbus/EventBusService.java
index 3414a22..53add32 100644
--- a/core/applib/src/main/java/org/apache/isis/applib/services/eventbus/EventBusService.java
+++ b/core/applib/src/main/java/org/apache/isis/applib/services/eventbus/EventBusService.java
@@ -16,17 +16,20 @@
  */
 package org.apache.isis.applib.services.eventbus;
 
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-
-import javax.annotation.PostConstruct;
-
-import com.google.common.collect.Lists;
 import com.google.common.eventbus.EventBus;
 
 import org.apache.isis.applib.annotation.Programmatic;
 
+/**
+ * A wrapper for a Guava {@link EventBus}, allowing arbitrary events to be posted and
+ * subscribed to.
+ *  
+ * <p>
+ * It is highly advisable that only domain services - not domain entities - are registered as subscribers.  
+ * Domain services are guaranteed to be instantiated and resident in memory, whereas the same is not true
+ * of domain entities.  The typical implementation of a domain service subscriber is to identify the impacted entities,
+ * load them using a repository, and then to delegate to the event to them.
+ */
 public abstract class EventBusService {
 
     /**
@@ -39,7 +42,7 @@ public abstract class EventBusService {
         @Override
         public void unregister(Object domainObject) {};
         @Override
-        public void post(Object event, java.util.Collection<?>... collections) {}
+        public void post(Object event) {}
         @Override
         protected EventBus getEventBus() {
             return null;
@@ -79,22 +82,17 @@ public abstract class EventBusService {
     }
     
     /**
-     * Post an event, but ensuring that any possible subscribers 
-     * to that event have been brought into memory.
+     * Post an event.
      */
     @Programmatic
-    public void post(Object event, Collection<?>... collections ) {
+    public void post(Object event) {
         if(skip(event)) {
             return;
         }
-        final List<Object> list = Lists.newArrayList();
-        for (Collection<?> collection : collections) {
-            list.addAll(collection);
-        }
-        ensureLoaded(list);
         getEventBus().post(event);
     }
 
+    
     /**
      * A hook to allow subclass implementations to skip the publication of certain events.
      * 
@@ -105,16 +103,5 @@ public abstract class EventBusService {
     protected boolean skip(Object event) {
         return false;
     }
-
-    /**
-     * Overrideable hook method.
-     * 
-     * <p>
-     * If using JDO objectstore, then use the <tt>EventBusServiceJdo</tt> implementation, 
-     * which overrides this method to load objects from the database.
-     */
-    protected void ensureLoaded(final Collection<?> collection) {
-    }
-    
 }
 

http://git-wip-us.apache.org/repos/asf/isis/blob/bd0be0e0/core/applib/src/main/java/org/apache/isis/applib/services/eventbus/PropertyChangedEvent.java
----------------------------------------------------------------------
diff --git a/core/applib/src/main/java/org/apache/isis/applib/services/eventbus/PropertyChangedEvent.java b/core/applib/src/main/java/org/apache/isis/applib/services/eventbus/PropertyChangedEvent.java
new file mode 100644
index 0000000..e528867
--- /dev/null
+++ b/core/applib/src/main/java/org/apache/isis/applib/services/eventbus/PropertyChangedEvent.java
@@ -0,0 +1,61 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.apache.isis.applib.services.eventbus;
+
+import org.apache.isis.applib.annotation.PostsPropertyChangedEvent;
+import org.apache.isis.applib.util.ObjectContracts;
+
+public abstract class PropertyChangedEvent<S,T> {
+    private final S source;
+    private final T oldValue;
+    private final T newValue;
+    
+    /**
+     * To instantiate reflectively when the {@link PostsPropertyChangedEvent} annotation
+     * is used.
+     * 
+     * <p>
+     * The fields ({@link #source}, {@link #oldValue} and {@link #newValue}) are
+     * then set reflectively.
+     */
+    public PropertyChangedEvent() {
+        this(null, null, null);
+    }
+    public PropertyChangedEvent(S source, T oldValue, T newValue) {
+        this.source = source;
+        this.oldValue = oldValue;
+        this.newValue = newValue;
+    }
+
+    public S getSource() {
+        return source;
+    }
+    
+    public T getOldValue() {
+        return oldValue;
+    }
+    public T getNewValue() {
+        return newValue;
+    }
+    
+    @Override
+    public String toString() {
+        return ObjectContracts.toString(this, "source,oldValue,newValue");
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/isis/blob/bd0be0e0/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/publish/PublishedActionFacetAbstract.java
----------------------------------------------------------------------
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/publish/PublishedActionFacetAbstract.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/publish/PublishedActionFacetAbstract.java
index 3923bcf..e964111 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/publish/PublishedActionFacetAbstract.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/publish/PublishedActionFacetAbstract.java
@@ -30,8 +30,8 @@ public abstract class PublishedActionFacetAbstract extends SingleValueFacetAbstr
         return PublishedActionFacet.class;
     }
 
-    public PublishedActionFacetAbstract(final PublishedAction.PayloadFactory eventCanonicalizer, final FacetHolder holder) {
-        super(type(), eventCanonicalizer, holder);
+    public PublishedActionFacetAbstract(final PublishedAction.PayloadFactory payloadFactory, final FacetHolder holder) {
+        super(type(), payloadFactory, holder);
     }
 
 }

http://git-wip-us.apache.org/repos/asf/isis/blob/bd0be0e0/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/properties/event/PostsPropertyChangedEventFacet.java
----------------------------------------------------------------------
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/properties/event/PostsPropertyChangedEventFacet.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/properties/event/PostsPropertyChangedEventFacet.java
new file mode 100644
index 0000000..a01e7f4
--- /dev/null
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/properties/event/PostsPropertyChangedEventFacet.java
@@ -0,0 +1,35 @@
+/*
+ *  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.metamodel.facets.properties.event;
+
+import org.apache.isis.applib.services.eventbus.EventBusService;
+import org.apache.isis.applib.services.eventbus.PropertyChangedEvent;
+import org.apache.isis.core.metamodel.facetapi.MultiTypedFacet;
+import org.apache.isis.core.metamodel.facets.SingleValueFacet;
+import org.apache.isis.core.metamodel.facets.properties.modify.PropertyClearFacet;
+import org.apache.isis.core.metamodel.facets.properties.modify.PropertySetterFacet;
+
+/**
+ * Indicates that (the specified subclass of) {@link PropertyChangedEvent} should be posted to the
+ * {@link EventBusService}.
+ */
+public interface PostsPropertyChangedEventFacet extends SingleValueFacet<Class<? extends PropertyChangedEvent<?,?>>>, PropertyClearFacet, PropertySetterFacet, MultiTypedFacet {
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/bd0be0e0/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/properties/event/PostsPropertyChangedEventFacetAbstract.java
----------------------------------------------------------------------
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/properties/event/PostsPropertyChangedEventFacetAbstract.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/properties/event/PostsPropertyChangedEventFacetAbstract.java
new file mode 100644
index 0000000..281438a
--- /dev/null
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/properties/event/PostsPropertyChangedEventFacetAbstract.java
@@ -0,0 +1,37 @@
+/*
+ *  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.metamodel.facets.properties.event;
+
+import org.apache.isis.applib.services.eventbus.PropertyChangedEvent;
+import org.apache.isis.core.metamodel.facetapi.Facet;
+import org.apache.isis.core.metamodel.facetapi.FacetHolder;
+import org.apache.isis.core.metamodel.facets.SingleValueFacetAbstract;
+
+public abstract class PostsPropertyChangedEventFacetAbstract extends SingleValueFacetAbstract<Class<? extends PropertyChangedEvent<?,?>>> implements PostsPropertyChangedEventFacet {
+
+    public static Class<? extends Facet> type() {
+        return PostsPropertyChangedEventFacet.class;
+    }
+
+    public PostsPropertyChangedEventFacetAbstract(final Class<? extends PropertyChangedEvent<?,?>> changedEventType, final FacetHolder holder) {
+        super(type(), changedEventType, holder);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/bd0be0e0/core/metamodel/src/main/java/org/apache/isis/core/progmodel/facets/properties/event/PostsPropertyChangedEventAnnotationFacetFactory.java
----------------------------------------------------------------------
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/progmodel/facets/properties/event/PostsPropertyChangedEventAnnotationFacetFactory.java b/core/metamodel/src/main/java/org/apache/isis/core/progmodel/facets/properties/event/PostsPropertyChangedEventAnnotationFacetFactory.java
new file mode 100644
index 0000000..48d12d0
--- /dev/null
+++ b/core/metamodel/src/main/java/org/apache/isis/core/progmodel/facets/properties/event/PostsPropertyChangedEventAnnotationFacetFactory.java
@@ -0,0 +1,117 @@
+/*
+ *  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.progmodel.facets.properties.event;
+
+import java.lang.reflect.Method;
+import java.util.Comparator;
+import java.util.List;
+
+import org.apache.isis.applib.annotation.PostsPropertyChangedEvent;
+import org.apache.isis.applib.services.eventbus.PropertyChangedEvent;
+import org.apache.isis.core.commons.config.IsisConfiguration;
+import org.apache.isis.core.metamodel.adapter.ServicesProvider;
+import org.apache.isis.core.metamodel.adapter.ServicesProviderAware;
+import org.apache.isis.core.metamodel.facetapi.FacetHolder;
+import org.apache.isis.core.metamodel.facetapi.FacetUtil;
+import org.apache.isis.core.metamodel.facetapi.FeatureType;
+import org.apache.isis.core.metamodel.facetapi.MetaModelValidatorRefiner;
+import org.apache.isis.core.metamodel.facets.Annotations;
+import org.apache.isis.core.metamodel.facets.FacetFactoryAbstract;
+import org.apache.isis.core.metamodel.facets.accessor.PropertyOrCollectionAccessorFacet;
+import org.apache.isis.core.metamodel.facets.collections.sortedby.SortedByFacet;
+import org.apache.isis.core.metamodel.facets.properties.event.PostsPropertyChangedEventFacet;
+import org.apache.isis.core.metamodel.facets.properties.modify.PropertyClearFacet;
+import org.apache.isis.core.metamodel.facets.properties.modify.PropertySetterFacet;
+import org.apache.isis.core.metamodel.spec.ObjectSpecification;
+import org.apache.isis.core.metamodel.spec.feature.OneToManyAssociation;
+import org.apache.isis.core.metamodel.specloader.validator.MetaModelValidatorComposite;
+import org.apache.isis.core.metamodel.specloader.validator.MetaModelValidatorVisiting;
+import org.apache.isis.core.metamodel.specloader.validator.MetaModelValidatorVisiting.Visitor;
+import org.apache.isis.core.metamodel.specloader.validator.ValidationFailures;
+
+public class PostsPropertyChangedEventAnnotationFacetFactory extends FacetFactoryAbstract implements ServicesProviderAware, MetaModelValidatorRefiner {
+
+    private ServicesProvider servicesProvider;
+
+    public PostsPropertyChangedEventAnnotationFacetFactory() {
+        super(FeatureType.PROPERTIES_ONLY);
+    }
+
+    @Override
+    public void process(final ProcessMethodContext processMethodContext) {
+        final Method method = processMethodContext.getMethod();
+        FacetUtil.addFacet(create(method, processMethodContext.getFacetHolder()));
+    }
+
+    private PostsPropertyChangedEventFacet create(Method method, final FacetHolder holder) {
+        final PostsPropertyChangedEvent annotation = Annotations.getAnnotation(method, PostsPropertyChangedEvent.class);
+        if(annotation == null) {
+            return null;
+        }
+        final PropertyOrCollectionAccessorFacet getterFacet = holder.getFacet(PropertyOrCollectionAccessorFacet.class);
+        if(getterFacet == null) {
+            return null;
+        } 
+        final PropertyClearFacet clearFacet = holder.getFacet(PropertyClearFacet.class);
+        final PropertySetterFacet setterFacet = holder.getFacet(PropertySetterFacet.class);
+        if (clearFacet == null && setterFacet == null) {
+            return null;
+        }
+        if(setterFacet != null) {
+            holder.removeFacet(setterFacet);
+        }
+        if(clearFacet != null) {
+            holder.removeFacet(clearFacet);
+        }
+        final Class<? extends PropertyChangedEvent<?, ?>> changedEventType = annotation.value();
+        return new PostsPropertyChangedEventFacetAnnotation(changedEventType, getterFacet, setterFacet, clearFacet, servicesProvider, holder);
+    }
+
+    @Override
+    public void setServicesProvider(ServicesProvider servicesProvider) {
+        this.servicesProvider = servicesProvider;
+    }
+
+    @Override
+    public void refineMetaModelValidator(MetaModelValidatorComposite metaModelValidator, IsisConfiguration configuration) {
+        metaModelValidator.add(new MetaModelValidatorVisiting(newValidatorVisitor()));
+    }
+
+    protected Visitor newValidatorVisitor() {
+        return new MetaModelValidatorVisiting.Visitor() {
+
+            @Override
+            public boolean visit(ObjectSpecification objectSpec, ValidationFailures validationFailures) {
+                List<OneToManyAssociation> objectCollections = objectSpec.getCollections();
+                for (OneToManyAssociation objectCollection : objectCollections) {
+                    final SortedByFacet facet = objectCollection.getFacet(SortedByFacet.class);
+                    if(facet != null) {
+                        final Class<? extends Comparator<?>> cls = facet.value();
+                        if(!Comparator.class.isAssignableFrom(cls)) {
+                            validationFailures.add("%s#%s is annotated with @SortedBy, but the class specified '%s' is not a Comparator", objectSpec.getIdentifier().getClassName(), objectCollection.getId(), facet.value().getName());
+                        }
+                    }
+                }
+                return true;
+            }
+        };
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/bd0be0e0/core/metamodel/src/main/java/org/apache/isis/core/progmodel/facets/properties/event/PostsPropertyChangedEventFacetAnnotation.java
----------------------------------------------------------------------
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/progmodel/facets/properties/event/PostsPropertyChangedEventFacetAnnotation.java b/core/metamodel/src/main/java/org/apache/isis/core/progmodel/facets/properties/event/PostsPropertyChangedEventFacetAnnotation.java
new file mode 100644
index 0000000..57d5ff3
--- /dev/null
+++ b/core/metamodel/src/main/java/org/apache/isis/core/progmodel/facets/properties/event/PostsPropertyChangedEventFacetAnnotation.java
@@ -0,0 +1,164 @@
+/*
+ *  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.progmodel.facets.properties.event;
+
+import java.lang.reflect.Field;
+import java.util.List;
+
+import com.google.common.base.Objects;
+import com.google.common.collect.Lists;
+
+import org.apache.isis.applib.ApplicationException;
+import org.apache.isis.applib.services.eventbus.EventBusService;
+import org.apache.isis.applib.services.eventbus.PropertyChangedEvent;
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+import org.apache.isis.core.metamodel.adapter.ServicesProvider;
+import org.apache.isis.core.metamodel.facetapi.Facet;
+import org.apache.isis.core.metamodel.facetapi.FacetHolder;
+import org.apache.isis.core.metamodel.facets.accessor.PropertyOrCollectionAccessorFacet;
+import org.apache.isis.core.metamodel.facets.properties.event.PostsPropertyChangedEventFacet;
+import org.apache.isis.core.metamodel.facets.properties.event.PostsPropertyChangedEventFacetAbstract;
+import org.apache.isis.core.metamodel.facets.properties.modify.PropertyClearFacet;
+import org.apache.isis.core.metamodel.facets.properties.modify.PropertySetterFacet;
+
+public class PostsPropertyChangedEventFacetAnnotation extends PostsPropertyChangedEventFacetAbstract {
+
+    private final PropertyOrCollectionAccessorFacet getterFacet;
+    private final PropertySetterFacet setterFacet;
+    private final PropertyClearFacet clearFacet;
+    private final ServicesProvider servicesProvider;
+    
+    private EventBusService eventBusService;
+    private boolean searchedForEventBusService = false;
+
+    public PostsPropertyChangedEventFacetAnnotation(
+            final Class<? extends PropertyChangedEvent<?, ?>> changedEventType, 
+            final PropertyOrCollectionAccessorFacet getterFacet, 
+            final PropertySetterFacet setterFacet, 
+            final PropertyClearFacet clearFacet, 
+            final ServicesProvider servicesProvider, 
+            final FacetHolder holder) {
+        super(changedEventType, holder);
+        this.getterFacet = getterFacet;
+        this.setterFacet = setterFacet;
+        this.clearFacet = clearFacet;
+        this.servicesProvider = servicesProvider;
+    }
+
+    @Override
+    public void setProperty(ObjectAdapter inObject, ObjectAdapter value) {
+        if(this.setterFacet == null) {
+            return;
+        }
+        eventBusService = getEventBusService();
+        if(eventBusService == null) {
+            setterFacet.setProperty(inObject, value);
+            return;
+        }
+        
+        final Object oldValue = this.getterFacet.getProperty(inObject);
+        this.setterFacet.setProperty(inObject, value);
+        final Object newValue = this.getterFacet.getProperty(inObject);
+        postEventIfChanged(inObject, oldValue, newValue);
+    }
+
+    @Override
+    public void clearProperty(ObjectAdapter inObject) {
+        if(this.clearFacet == null) {
+            return;
+        }
+        eventBusService = getEventBusService();
+        if(eventBusService == null) {
+            clearFacet.clearProperty(inObject);
+            return;
+        }
+
+        final Object oldValue = this.getterFacet.getProperty(inObject);
+        this.clearFacet.clearProperty(inObject);
+        final Object newValue = this.getterFacet.getProperty(inObject);
+        postEventIfChanged(inObject, oldValue, newValue);
+    }
+
+
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    private void postEventIfChanged(ObjectAdapter inObject, final Object oldValue, final Object newValue) {
+        if(Objects.equal(oldValue, newValue)) {
+            // do nothing.
+            return;
+        }
+        final Object source = inObject.getObject();
+        try {
+            final Class type = value();
+            final PropertyChangedEvent<?, ?> event = newEvent(type, oldValue, newValue, source);
+            
+            eventBusService.post(event);
+        } catch (Exception e) {
+            throw new ApplicationException(e);
+        }
+    }
+
+    static <S,T> PropertyChangedEvent<S,T> newEvent(final Class<? extends PropertyChangedEvent<S, T>> type, final T oldValue, final T newValue, final S source) throws InstantiationException, IllegalAccessException, NoSuchFieldException {
+        final PropertyChangedEvent<S, T> event = type.newInstance();
+        
+        setField("source", event, source);
+        setField("oldValue", event, oldValue);
+        setField("newValue", event, newValue);
+        return event;
+    }
+
+    private static void setField(final String name, final PropertyChangedEvent<?, ?> event, final Object sourceValue) throws NoSuchFieldException, IllegalAccessException {
+        final Field sourceField = PropertyChangedEvent.class.getDeclaredField(name);
+        sourceField.setAccessible(true);
+        sourceField.set(event, sourceValue);
+    }
+    
+    private EventBusService getEventBusService() {
+        if(!searchedForEventBusService) {
+            final List<ObjectAdapter> serviceAdapters = servicesProvider.getServices();
+            for (ObjectAdapter serviceAdapter : serviceAdapters) {
+                final Object service = serviceAdapter.getObject();
+                if(service instanceof EventBusService) {
+                    eventBusService = (EventBusService) service;
+                    break;
+                }
+            }
+        } 
+        searchedForEventBusService = true;
+        return eventBusService;
+    }
+    
+    // //////////////////////////////////////
+    // MultiTypedFacet
+
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public Class<? extends Facet>[] facetTypes() {
+        return Lists.newArrayList(PostsPropertyChangedEventFacet.class, PropertySetterFacet.class, PropertyClearFacet.class).toArray(new Class[]{});
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public <T extends Facet> T getFacet(Class<T> facet) {
+        return (T) this;
+    }
+
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/bd0be0e0/core/metamodel/src/main/java/org/apache/isis/progmodels/dflt/ProgrammingModelFacetsJava5.java
----------------------------------------------------------------------
diff --git a/core/metamodel/src/main/java/org/apache/isis/progmodels/dflt/ProgrammingModelFacetsJava5.java b/core/metamodel/src/main/java/org/apache/isis/progmodels/dflt/ProgrammingModelFacetsJava5.java
index 29bb10b..9150f59 100644
--- a/core/metamodel/src/main/java/org/apache/isis/progmodels/dflt/ProgrammingModelFacetsJava5.java
+++ b/core/metamodel/src/main/java/org/apache/isis/progmodels/dflt/ProgrammingModelFacetsJava5.java
@@ -151,6 +151,7 @@ import org.apache.isis.core.progmodel.facets.properties.choices.method.PropertyC
 import org.apache.isis.core.progmodel.facets.properties.defaults.fromtype.PropertyDefaultDerivedFromTypeFacetFactory;
 import org.apache.isis.core.progmodel.facets.properties.defaults.method.PropertyDefaultFacetFactory;
 import org.apache.isis.core.progmodel.facets.properties.disabled.fromimmutable.DisabledFacetForPropertyDerivedFromImmutableTypeFacetFactory;
+import org.apache.isis.core.progmodel.facets.properties.event.PostsPropertyChangedEventAnnotationFacetFactory;
 import org.apache.isis.core.progmodel.facets.properties.mandatory.annotation.OptionalAnnotationForPropertyFacetFactory;
 import org.apache.isis.core.progmodel.facets.properties.mandatory.dflt.MandatoryDefaultForPropertiesFacetFactory;
 import org.apache.isis.core.progmodel.facets.properties.mandatory.staticmethod.PropertyOptionalFacetFactory;
@@ -345,6 +346,8 @@ public final class ProgrammingModelFacetsJava5 extends ProgrammingModelAbstract
         addFactory(DisabledFacetForPropertyDerivedFromImmutableTypeFacetFactory.class);
         addFactory(DisabledFacetForCollectionDerivedFromImmutableTypeFacetFactory.class);
 
+        addFactory(PostsPropertyChangedEventAnnotationFacetFactory.class);
+
         addFactory(ImmutableMarkerInterfaceFacetFactory.class);
 
         addFactory(ViewModelAnnotationFacetFactory.class);

http://git-wip-us.apache.org/repos/asf/isis/blob/bd0be0e0/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
new file mode 100644
index 0000000..eeddb35
--- /dev/null
+++ b/core/metamodel/src/test/java/org/apache/isis/core/progmodel/facets/properties/event/PostsPropertyChangedEventFacetAnnotationTest_newEvent.java
@@ -0,0 +1,42 @@
+/**
+ *  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.progmodel.facets.properties.event;
+
+import static org.hamcrest.CoreMatchers.*;
+import static org.junit.Assert.*;
+
+import org.joda.time.LocalDate;
+import org.junit.Test;
+
+import org.apache.isis.applib.services.eventbus.PropertyChangedEvent;
+
+public class PostsPropertyChangedEventFacetAnnotationTest_newEvent {
+
+    public static class SomeDomainObject {}
+    
+    public static class SomeDatePropertyChangedEvent extends PropertyChangedEvent<SomeDomainObject, LocalDate> {}
+    
+    @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);
+        assertThat(ev.getSource(), is(sdo));
+        assertThat(ev.getOldValue(), is(new LocalDate(2013,4,1)));
+        assertThat(ev.getNewValue(), is(new LocalDate(2013,5,2)));
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/bd0be0e0/core/runtime/src/main/java/org/apache/isis/core/runtime/services/eventbus/EventBusServiceDefault.java
----------------------------------------------------------------------
diff --git a/core/runtime/src/main/java/org/apache/isis/core/runtime/services/eventbus/EventBusServiceDefault.java b/core/runtime/src/main/java/org/apache/isis/core/runtime/services/eventbus/EventBusServiceDefault.java
new file mode 100644
index 0000000..f689a55
--- /dev/null
+++ b/core/runtime/src/main/java/org/apache/isis/core/runtime/services/eventbus/EventBusServiceDefault.java
@@ -0,0 +1,67 @@
+/**
+ *  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.runtime.services.eventbus;
+
+import java.util.Set;
+
+import com.google.common.collect.Sets;
+import com.google.common.eventbus.EventBus;
+
+import org.apache.isis.applib.services.eventbus.EventBusService;
+import org.apache.isis.core.runtime.system.context.IsisContext;
+
+public class EventBusServiceDefault extends EventBusService {
+
+    
+    private final Set<Object> objectsToRegister = Sets.newHashSet();
+    
+    @Override
+    protected EventBus getEventBus() {
+        return IsisContext.getSession().getEventBus();
+    }
+    
+    
+    @Override
+    public void register(Object domainObject) {
+        // lazily registered
+        // (a) there may be no session initially
+        // (b) so can be unregistered at when closed
+        objectsToRegister.add(domainObject);
+    }
+    
+    @Override
+    public void unregister(Object domainObject) {
+        if(IsisContext.inSession()) {
+            getEventBus().unregister(domainObject);
+        }
+        objectsToRegister.remove(domainObject);
+    }
+
+    public void open() {
+        for (final Object object : objectsToRegister) {
+            getEventBus().register(object);
+        }
+    }
+
+    public void close() {
+        for (final Object object : objectsToRegister) {
+            getEventBus().unregister(object);
+        }
+    }
+
+}
+

http://git-wip-us.apache.org/repos/asf/isis/blob/bd0be0e0/core/runtime/src/main/java/org/apache/isis/core/runtime/system/persistence/PersistenceSession.java
----------------------------------------------------------------------
diff --git a/core/runtime/src/main/java/org/apache/isis/core/runtime/system/persistence/PersistenceSession.java b/core/runtime/src/main/java/org/apache/isis/core/runtime/system/persistence/PersistenceSession.java
index 9b39e3c..48f980d 100644
--- a/core/runtime/src/main/java/org/apache/isis/core/runtime/system/persistence/PersistenceSession.java
+++ b/core/runtime/src/main/java/org/apache/isis/core/runtime/system/persistence/PersistenceSession.java
@@ -83,6 +83,7 @@ import org.apache.isis.core.runtime.persistence.query.PersistenceQueryFindByPatt
 import org.apache.isis.core.runtime.persistence.query.PersistenceQueryFindByTitle;
 import org.apache.isis.core.runtime.persistence.query.PersistenceQueryFindUsingApplibQueryDefault;
 import org.apache.isis.core.runtime.persistence.query.PersistenceQueryFindUsingApplibQuerySerializable;
+import org.apache.isis.core.runtime.services.eventbus.EventBusServiceDefault;
 import org.apache.isis.core.runtime.system.context.IsisContext;
 import org.apache.isis.core.runtime.system.transaction.EnlistedObjectDirtying;
 import org.apache.isis.core.runtime.system.transaction.IsisTransactionManager;
@@ -238,6 +239,11 @@ public class PersistenceSession implements Persistor, EnlistedObjectDirtying, To
         if (LOG.isDebugEnabled()) {
             LOG.debug("closing " + this);
         }
+        
+        // a bit of a hack
+        if(eventBusService != null) {
+            eventBusService.close();
+        }
 
         try {
             objectStore.close();
@@ -286,6 +292,14 @@ public class PersistenceSession implements Persistor, EnlistedObjectDirtying, To
                 final RootOid persistentOid = (RootOid) serviceAdapter.getOid();
                 registerService(persistentOid);
             }
+            
+            // a bit of a hack
+            final Object object = serviceAdapter.getObject();
+            if(object instanceof EventBusServiceDefault) {
+                eventBusService = (EventBusServiceDefault) object;
+                EventBusServiceDefault ebs = eventBusService;
+                ebs.open();
+            }
 
         }
         getTransactionManager().endTransaction();
@@ -945,6 +959,8 @@ public class PersistenceSession implements Persistor, EnlistedObjectDirtying, To
 
     private Map<Oid, Oid> persistentByTransient = Maps.newHashMap();
 
+    private EventBusServiceDefault eventBusService;
+
     /**
      * Callback from the {@link PersistAlgorithm} (or equivalent; some object
      * stores such as Hibernate will use listeners instead) to indicate that the