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/11 19:41:53 UTC

[08/16] git commit: ISIS-550: Oscar's work to date on ActionInvokedEvent

ISIS-550: Oscar's work to date on ActionInvokedEvent

- Refactored ActionInvocationFacetViaMethod (distinguish meaning of null, introduced internalInvoke)
- New PostsActionInvokedEvent annotation class
- New facet interface, abstract class, implementation (latter subclassing ActionInvocationFacetViaMethod).


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

Branch: refs/heads/master
Commit: cfcb201838aeda52396563539cee2418d5e20f3c
Parents: fe05886
Author: Dan Haywood <da...@haywood-associates.co.uk>
Authored: Sat May 10 07:05:33 2014 +0100
Committer: Dan Haywood <da...@haywood-associates.co.uk>
Committed: Sat May 10 07:05:33 2014 +0100

----------------------------------------------------------------------
 .../annotation/PostsActionInvokedEvent.java     |  67 +++++++
 .../services/eventbus/ActionInvokedEvent.java   |  63 ++++++
 .../event/PostsActionInvokedEventFacet.java     |  32 +++
 .../PostsActionInvokedEventFacetAbstract.java   |  42 ++++
 .../invoke/ActionInvocationFacetViaMethod.java  |  45 ++++-
 .../PostsActionInvokedEventFacetFactory.java    | 196 +++++++++++++++++++
 .../PostsActionInvokedEventFacetViaMethod.java  | 129 ++++++++++++
 .../dflt/ProgrammingModelFacetsJava5.java       |   2 +
 8 files changed, 570 insertions(+), 6 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/isis/blob/cfcb2018/core/applib/src/main/java/org/apache/isis/applib/annotation/PostsActionInvokedEvent.java
----------------------------------------------------------------------
diff --git a/core/applib/src/main/java/org/apache/isis/applib/annotation/PostsActionInvokedEvent.java b/core/applib/src/main/java/org/apache/isis/applib/annotation/PostsActionInvokedEvent.java
new file mode 100644
index 0000000..6b772e7
--- /dev/null
+++ b/core/applib/src/main/java/org/apache/isis/applib/annotation/PostsActionInvokedEvent.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.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.ActionInvokedEvent;
+import org.apache.isis.applib.services.eventbus.PropertyChangedEvent;
+import org.apache.isis.applib.services.wrapper.WrapperFactory;
+
+/**
+ * Applies only to actions; any changes should be propagated as events to subscribers.  
+ * Only posted if no exception is thrown.
+ * 
+ * <p>For example:
+ * <pre>
+ * public static class ChangeStartDateEvent extends ActionInvokedEvent {}
+ * 
+ * &#64;PostsActionInvokedEvent(StartDateChangedEvent.class)
+ * public void changeStartDate(final Date startDate) { ...}
+ * </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 PostsActionInvokedEvent {
+
+    /**
+     * 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 ActionInvokedEvent<?>> value() default ActionInvokedEvent.Default.class;
+
+    /**
+     * If invoked through the {@link WrapperFactory}, whether business rules (&quot;see it, use it, do it&quot;)
+     * should be enforced or not.
+     */
+    WrapperPolicy wrapperPolicy() default WrapperPolicy.ENFORCE_RULES;
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/cfcb2018/core/applib/src/main/java/org/apache/isis/applib/services/eventbus/ActionInvokedEvent.java
----------------------------------------------------------------------
diff --git a/core/applib/src/main/java/org/apache/isis/applib/services/eventbus/ActionInvokedEvent.java b/core/applib/src/main/java/org/apache/isis/applib/services/eventbus/ActionInvokedEvent.java
new file mode 100644
index 0000000..2140525
--- /dev/null
+++ b/core/applib/src/main/java/org/apache/isis/applib/services/eventbus/ActionInvokedEvent.java
@@ -0,0 +1,63 @@
+/*
+ *  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 ActionInvokedEvent<S> {
+    
+    public static class Default extends ActionInvokedEvent<Object> {}
+
+    private final S source;
+    private final String action;
+    private final String parameters;
+    
+    /**
+     * To instantiate reflectively when the {@link PostsActionInvokedEvent} annotation
+     * is used.
+     * 
+     * <p>
+     * The fields ({@link #source} and {@link #value} are then set reflectively.
+     */
+    public ActionInvokedEvent() {
+        this(null, null, null);
+    }
+    
+    public ActionInvokedEvent(S source, String action, String parameters) {
+        this.source = source;
+        this.action = action;
+        this.parameters = parameters;
+    }
+    
+    public S getSource() {
+        return source;
+    }
+    public String getAction() {
+        return action;
+    }
+    public String getParameters() {
+        return parameters;
+    }
+    
+    @Override
+    public String toString() {
+        return ObjectContracts.toString(this, "source,action,parameters");
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/isis/blob/cfcb2018/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/invoke/event/PostsActionInvokedEventFacet.java
----------------------------------------------------------------------
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/invoke/event/PostsActionInvokedEventFacet.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/invoke/event/PostsActionInvokedEventFacet.java
new file mode 100644
index 0000000..8f9d070
--- /dev/null
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/invoke/event/PostsActionInvokedEventFacet.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.metamodel.facets.actions.invoke.event;
+
+import org.apache.isis.core.metamodel.facets.actions.invoke.ActionInvocationFacet;
+
+/**
+ * Extends the mechanism by which the action should be invoked by sending an
+ * Event to the internal Event Bus after being invoked without throwing 
+ * an Exception.
+ * 
+ */
+public interface PostsActionInvokedEventFacet extends ActionInvocationFacet {
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/cfcb2018/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/invoke/event/PostsActionInvokedEventFacetAbstract.java
----------------------------------------------------------------------
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/invoke/event/PostsActionInvokedEventFacetAbstract.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/invoke/event/PostsActionInvokedEventFacetAbstract.java
new file mode 100644
index 0000000..8a0115f
--- /dev/null
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/invoke/event/PostsActionInvokedEventFacetAbstract.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.metamodel.facets.actions.invoke.event;
+
+import org.apache.isis.core.metamodel.facetapi.Facet;
+import org.apache.isis.core.metamodel.facetapi.FacetAbstract;
+import org.apache.isis.core.metamodel.facetapi.FacetHolder;
+import org.apache.isis.core.metamodel.facets.actions.invoke.ActionInvocationFacet;
+
+public abstract class PostsActionInvokedEventFacetAbstract extends FacetAbstract implements ActionInvocationFacet {
+
+    public static Class<? extends Facet> type() {
+    	
+	    // the "primary" type is ActionInvocationFacet rather than PostsActionInvokedEventFacet
+	    // so that this facet can wrap an existing (via setUnderlying).
+	    
+        //return PostsActionInvokedEventFacetAbstract.class;
+        return ActionInvocationFacet.class;
+    }
+
+    public PostsActionInvokedEventFacetAbstract(final FacetHolder holder) {
+        super(type(), holder, Derivation.NOT_DERIVED);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/cfcb2018/core/metamodel/src/main/java/org/apache/isis/core/progmodel/facets/actions/invoke/ActionInvocationFacetViaMethod.java
----------------------------------------------------------------------
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/progmodel/facets/actions/invoke/ActionInvocationFacetViaMethod.java b/core/metamodel/src/main/java/org/apache/isis/core/progmodel/facets/actions/invoke/ActionInvocationFacetViaMethod.java
index 4063b49..c8f81d3 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/progmodel/facets/actions/invoke/ActionInvocationFacetViaMethod.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/progmodel/facets/actions/invoke/ActionInvocationFacetViaMethod.java
@@ -113,13 +113,44 @@ public class ActionInvocationFacetViaMethod extends ActionInvocationFacetAbstrac
     public ObjectAdapter invoke(final ObjectAdapter target, final ObjectAdapter[] parameters) {
         return invoke(null, target, parameters);
     }
-
+    
     @Override
     public ObjectAdapter invoke(
             final ObjectAction owningAction, 
             final ObjectAdapter targetAdapter, 
             final ObjectAdapter[] arguments) {
-
+    
+    	// Can return null both because the action finally was not invoked 
+    	// or because it returned null.
+    	return internalInvoke(owningAction, targetAdapter, arguments).getResult();
+    	
+    }
+    
+    public class InvocationResult {
+    	
+    	private final Boolean wasInvoked;
+    	private final ObjectAdapter result;
+    	
+    	public InvocationResult(Boolean invoked, ObjectAdapter result) {
+    		this.wasInvoked = invoked;
+    		this.result = result;
+    	}
+
+		public Boolean getWasInvoked() {
+			return wasInvoked;
+		}
+
+		public ObjectAdapter getResult() {
+			return result;
+		}
+    	
+    }
+    
+    protected InvocationResult internalInvoke(
+            final ObjectAction owningAction, 
+            final ObjectAdapter targetAdapter, 
+            final ObjectAdapter[] arguments) {
+    	
         final Bulk.InteractionContext bulkInteractionContext = getServicesInjector().lookupService(Bulk.InteractionContext.class);
         final CommandContext commandContext = getServicesInjector().lookupService(CommandContext.class);
         final Command command = commandContext != null ? commandContext.getCommand() : null;
@@ -195,7 +226,7 @@ public class ActionInvocationFacetViaMethod extends ActionInvocationFacetAbstrac
                 if(commandService.persistIfPossible(command)) {
                     // force persistence, then return the command itself.
                     final ObjectAdapter resultAdapter = getAdapterManager().adapterFor(command);
-                    return resultAdapter;
+                    return new InvocationResult(true, resultAdapter);
                 } else {
                     throw new IsisException(
                             "Unable to schedule action '"
@@ -216,7 +247,7 @@ public class ActionInvocationFacetViaMethod extends ActionInvocationFacetAbstrac
                     LOG.debug(" action result " + result);
                 }
                 if (result == null) {
-                    return null;
+                	return new InvocationResult(true, null);
                 }
 
                 final ObjectAdapter resultAdapter = getAdapterManager().adapterFor(result);
@@ -238,7 +269,7 @@ public class ActionInvocationFacetViaMethod extends ActionInvocationFacetAbstrac
                             ? new CurrentInvocation(targetAdapter, getIdentified(), arguments, resultAdapter, command)
                             :null);
                 
-                return resultAdapter;
+                return new InvocationResult(true, resultAdapter);
             }
 
         } catch (final IllegalArgumentException e) {
@@ -259,7 +290,9 @@ public class ActionInvocationFacetViaMethod extends ActionInvocationFacetAbstrac
             }
 
             ThrowableExtensions.throwWithinIsisException(e, "Exception executing " + method);
-            return null;
+            
+            // Action was not invoked (an Exception was thrown)
+            return new InvocationResult(false, null);
         } catch (final IllegalAccessException e) {
             throw new ReflectiveActionException("Illegal access of " + method, e);
         }

http://git-wip-us.apache.org/repos/asf/isis/blob/cfcb2018/core/metamodel/src/main/java/org/apache/isis/core/progmodel/facets/actions/invoke/event/PostsActionInvokedEventFacetFactory.java
----------------------------------------------------------------------
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/progmodel/facets/actions/invoke/event/PostsActionInvokedEventFacetFactory.java b/core/metamodel/src/main/java/org/apache/isis/core/progmodel/facets/actions/invoke/event/PostsActionInvokedEventFacetFactory.java
new file mode 100644
index 0000000..e46e7cc
--- /dev/null
+++ b/core/metamodel/src/main/java/org/apache/isis/core/progmodel/facets/actions/invoke/event/PostsActionInvokedEventFacetFactory.java
@@ -0,0 +1,196 @@
+/*
+ *  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.actions.invoke.event;
+
+import java.lang.reflect.Method;
+
+import org.apache.isis.applib.annotation.PostsCollectionAddedToEvent;
+import org.apache.isis.applib.services.eventbus.CollectionAddedToEvent;
+import org.apache.isis.core.commons.lang.StringExtensions;
+import org.apache.isis.core.metamodel.adapter.mgr.AdapterManager;
+import org.apache.isis.core.metamodel.adapter.mgr.AdapterManagerAware;
+import org.apache.isis.core.metamodel.facetapi.Facet;
+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.facets.Annotations;
+import org.apache.isis.core.metamodel.facets.actions.debug.DebugFacet;
+import org.apache.isis.core.metamodel.facets.actions.exploration.ExplorationFacet;
+import org.apache.isis.core.metamodel.facets.actions.invoke.ActionInvocationFacet;
+import org.apache.isis.core.metamodel.facets.named.NamedFacet;
+import org.apache.isis.core.metamodel.facets.named.NamedFacetInferred;
+import org.apache.isis.core.metamodel.runtimecontext.RuntimeContext;
+import org.apache.isis.core.metamodel.runtimecontext.RuntimeContextAware;
+import org.apache.isis.core.metamodel.runtimecontext.ServicesInjector;
+import org.apache.isis.core.metamodel.runtimecontext.ServicesInjectorAware;
+import org.apache.isis.core.metamodel.spec.ObjectSpecification;
+import org.apache.isis.core.progmodel.facets.MethodPrefixBasedFacetFactoryAbstract;
+import org.apache.isis.core.progmodel.facets.actions.invoke.DebugFacetViaNamingConvention;
+import org.apache.isis.core.progmodel.facets.actions.invoke.ExplorationFacetViaNamingConvention;
+
+/**
+ * Sets up {@link ActionInvocationFacet}, along with a number of supporting
+ * facets that are based on the action's name.
+ * 
+ * <p>
+ * The supporting methods are: {@link ExecutedFacet}, {@link ExplorationFacet}
+ * and {@link DebugFacet}. In addition a {@link NamedFacet} is inferred from the
+ * name (taking into account the above well-known prefixes).
+ */
+public class PostsActionInvokedEventFacetFactory extends MethodPrefixBasedFacetFactoryAbstract implements AdapterManagerAware, ServicesInjectorAware, RuntimeContextAware {
+
+    private static final String EXPLORATION_PREFIX = "Exploration";
+    private static final String DEBUG_PREFIX = "Debug";
+
+    private static final String[] PREFIXES = { EXPLORATION_PREFIX, DEBUG_PREFIX };
+
+    private AdapterManager adapterManager;
+    private ServicesInjector servicesInjector;
+    private RuntimeContext runtimeContext;
+
+    /**
+     * Note that the {@link Facet}s registered are the generic ones from
+     * noa-architecture (where they exist)
+     */
+    public PostsActionInvokedEventFacetFactory() {
+        super(FeatureType.ACTIONS_ONLY, OrphanValidation.VALIDATE, PREFIXES);
+    }
+
+    // ///////////////////////////////////////////////////////
+    // Actions
+    // ///////////////////////////////////////////////////////
+
+    @Override
+    public void process(final ProcessMethodContext processMethodContext) {
+
+        // InvocationFacet
+        attachInvocationFacet(processMethodContext);
+
+        // DebugFacet, ExplorationFacet
+        attachDebugFacetIfActionMethodNamePrefixed(processMethodContext);
+        attachExplorationFacetIfActionMethodNamePrefixed(processMethodContext);
+
+        // inferred name
+        // (must be called after the attachinvocationFacet methods)
+        attachNamedFacetInferredFromMethodName(processMethodContext); 
+    }
+
+    private void attachInvocationFacet(final ProcessMethodContext processMethodContext) {
+
+        final Method actionMethod = processMethodContext.getMethod();
+
+        try {
+            final PostsCollectionAddedToEvent annotation = Annotations.getAnnotation(actionMethod, PostsCollectionAddedToEvent.class);
+            if(annotation == null) {
+                return;
+            }
+        	
+            final Class<?> returnType = actionMethod.getReturnType();
+            final ObjectSpecification returnSpec = getSpecificationLoader().loadSpecification(returnType);
+            if (returnSpec == null) {
+                return;
+            }
+
+            final Class<?> cls = processMethodContext.getCls();
+            final ObjectSpecification typeSpec = getSpecificationLoader().loadSpecification(cls);
+            final FacetHolder holder = processMethodContext.getFacetHolder();
+            
+            final Class<? extends CollectionAddedToEvent<?,?>> changedEventType = annotation.value();
+
+            FacetUtil.addFacet(new PostsActionInvokedEventFacetViaMethod(actionMethod, typeSpec, returnSpec, holder, getRuntimeContext(), getAdapterManager(), getServicesInjector(), changedEventType));
+        } finally {
+            processMethodContext.removeMethod(actionMethod);
+        }
+    }
+
+    private void attachDebugFacetIfActionMethodNamePrefixed(final ProcessMethodContext processMethodContext) {
+
+        final Method actionMethod = processMethodContext.getMethod();
+        final String capitalizedName = StringExtensions.asCapitalizedName(actionMethod.getName());
+        if (!capitalizedName.startsWith(DEBUG_PREFIX)) {
+            return;
+        }
+        final FacetHolder facetedMethod = processMethodContext.getFacetHolder();
+        FacetUtil.addFacet(new DebugFacetViaNamingConvention(facetedMethod));
+    }
+
+    private void attachExplorationFacetIfActionMethodNamePrefixed(final ProcessMethodContext processMethodContext) {
+
+        final Method actionMethod = processMethodContext.getMethod();
+        final String capitalizedName = StringExtensions.asCapitalizedName(actionMethod.getName());
+        if (!capitalizedName.startsWith(EXPLORATION_PREFIX)) {
+            return;
+        }
+        final FacetHolder facetedMethod = processMethodContext.getFacetHolder();
+        FacetUtil.addFacet(new ExplorationFacetViaNamingConvention(facetedMethod));
+    }
+
+    /**
+     * Must be called after added the debug, exploration etc facets.
+     * 
+     * <p>
+     * TODO: remove this hack
+     */
+    private void attachNamedFacetInferredFromMethodName(final ProcessMethodContext processMethodContext) {
+
+        final Method method = processMethodContext.getMethod();
+        final String capitalizedName = StringExtensions.asCapitalizedName(method.getName());
+
+        // this is nasty...
+        String name = capitalizedName;
+        name = StringExtensions.removePrefix(name, DEBUG_PREFIX);
+        name = StringExtensions.removePrefix(name, EXPLORATION_PREFIX);
+        name = StringExtensions.asNaturalName2(name);
+
+        final FacetHolder facetedMethod = processMethodContext.getFacetHolder();
+        FacetUtil.addFacet(new NamedFacetInferred(name, facetedMethod));
+    }
+
+    // ///////////////////////////////////////////////////////////////
+    // Dependencies
+    // ///////////////////////////////////////////////////////////////
+
+    @Override
+    public void setAdapterManager(final AdapterManager adapterManager) {
+        this.adapterManager = adapterManager;
+    }
+
+    private AdapterManager getAdapterManager() {
+        return adapterManager;
+    }
+
+    @Override
+    public void setServicesInjector(final ServicesInjector servicesInjector) {
+        this.servicesInjector = servicesInjector;
+    }
+
+    private ServicesInjector getServicesInjector() {
+        return servicesInjector;
+    }
+
+    private RuntimeContext getRuntimeContext() {
+        return runtimeContext;
+    }
+    
+    @Override
+    public void setRuntimeContext(RuntimeContext runtimeContext) {
+        this.runtimeContext = runtimeContext;
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/cfcb2018/core/metamodel/src/main/java/org/apache/isis/core/progmodel/facets/actions/invoke/event/PostsActionInvokedEventFacetViaMethod.java
----------------------------------------------------------------------
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/progmodel/facets/actions/invoke/event/PostsActionInvokedEventFacetViaMethod.java b/core/metamodel/src/main/java/org/apache/isis/core/progmodel/facets/actions/invoke/event/PostsActionInvokedEventFacetViaMethod.java
new file mode 100644
index 0000000..7e22b78
--- /dev/null
+++ b/core/metamodel/src/main/java/org/apache/isis/core/progmodel/facets/actions/invoke/event/PostsActionInvokedEventFacetViaMethod.java
@@ -0,0 +1,129 @@
+/*
+ *  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.actions.invoke.event;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+
+import org.apache.isis.applib.FatalException;
+import org.apache.isis.applib.services.eventbus.ActionInvokedEvent;
+import org.apache.isis.applib.services.eventbus.CollectionAddedToEvent;
+import org.apache.isis.applib.services.eventbus.EventBusService;
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+import org.apache.isis.core.metamodel.adapter.mgr.AdapterManager;
+import org.apache.isis.core.metamodel.facetapi.FacetHolder;
+import org.apache.isis.core.metamodel.facets.ImperativeFacet;
+import org.apache.isis.core.metamodel.runtimecontext.RuntimeContext;
+import org.apache.isis.core.metamodel.runtimecontext.ServicesInjector;
+import org.apache.isis.core.metamodel.spec.ObjectSpecification;
+import org.apache.isis.core.metamodel.spec.feature.ObjectAction;
+import org.apache.isis.core.progmodel.facets.actions.invoke.ActionInvocationFacetViaMethod;
+
+public class PostsActionInvokedEventFacetViaMethod extends ActionInvocationFacetViaMethod implements ImperativeFacet {
+	
+	private EventBusService eventBusService;
+	private boolean searchedForEventBusService = false;
+	private ServicesInjector servicesInjector;
+	private Class<? extends CollectionAddedToEvent<?, ?>> eventType;
+
+    public PostsActionInvokedEventFacetViaMethod(
+            final Method method, 
+            final ObjectSpecification onType, 
+            final ObjectSpecification returnType, 
+            final FacetHolder holder, 
+            final RuntimeContext runtimeContext, 
+            final AdapterManager adapterManager, 
+            final ServicesInjector servicesInjector,
+			final Class<? extends CollectionAddedToEvent<?, ?>> eventType) {
+        super(method, onType, returnType, holder, runtimeContext, adapterManager, servicesInjector);
+        
+        // Also needed here.
+        this.servicesInjector = servicesInjector;
+        
+        this.eventType = eventType;
+    }
+
+    @Override
+    public ObjectAdapter invoke(
+            final ObjectAction owningAction, 
+            final ObjectAdapter targetAdapter, 
+            final ObjectAdapter[] arguments) {
+
+    	final InvocationResult invocationResult = this.internalInvoke(owningAction, targetAdapter, arguments);
+    	
+    	// Perhaps the Action was not properly invoked (i.e. an exception was raised).
+    	if (invocationResult.getWasInvoked()) {
+    		// If invoked, then send the ActionInvokedEvent to the EventBus.
+    		postEvent(owningAction, targetAdapter, arguments);
+    	}
+    	
+    	return invocationResult.getResult();
+    	
+    }
+    
+	@SuppressWarnings({ "rawtypes", "unchecked" })
+	private void postEvent(
+            final ObjectAction owningAction, 
+            final ObjectAdapter targetAdapter, 
+            final ObjectAdapter[] arguments) {
+	    
+		final Object source = targetAdapter.getObject();
+		try {
+			final Class type = eventType;
+			final ActionInvokedEvent event = newEvent(type, owningAction, source, arguments);
+			getEventBusService().post(event);
+		} catch (Exception e) {
+			throw new FatalException(e);
+		}
+	}
+
+	static <S> ActionInvokedEvent<S> newEvent(
+			final Class<? extends ActionInvokedEvent<S>> type,
+		            final ObjectAction owningAction, 
+		            final S source, 
+		            final ObjectAdapter[] arguments)
+			throws InstantiationException, IllegalAccessException,
+			NoSuchFieldException {
+		final ActionInvokedEvent<S> event = type.newInstance();
+
+		setField("owningAction", event, owningAction);
+		setField("targetAdapter", event, source);
+		setField("arguments", event, arguments.toString());
+		return event;
+	}
+
+	private static void setField(final String name,
+			final ActionInvokedEvent<?> event, final Object sourceValue)
+			throws NoSuchFieldException, IllegalAccessException {
+		final Field sourceField = CollectionAddedToEvent.class
+				.getDeclaredField(name);
+		sourceField.setAccessible(true);
+		sourceField.set(event, sourceValue);
+	}
+
+	private EventBusService getEventBusService() {
+		if (!searchedForEventBusService) {
+			eventBusService = this.servicesInjector.lookupService(EventBusService.class);
+		}
+		searchedForEventBusService = true;
+		return eventBusService;
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/cfcb2018/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 ca75555..74933a9 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
@@ -31,6 +31,7 @@ import org.apache.isis.core.progmodel.facets.actions.defaults.method.ActionDefau
 import org.apache.isis.core.progmodel.facets.actions.exploration.annotation.ExplorationAnnotationFacetFactory;
 import org.apache.isis.core.progmodel.facets.actions.homepage.HomePageAnnotationFacetFactory;
 import org.apache.isis.core.progmodel.facets.actions.invoke.ActionInvocationFacetFactory;
+import org.apache.isis.core.progmodel.facets.actions.invoke.event.PostsActionInvokedEventFacetFactory;
 import org.apache.isis.core.progmodel.facets.actions.notcontributed.annotation.NotContributedAnnotationFacetFactory;
 import org.apache.isis.core.progmodel.facets.actions.notinservicemenu.annotation.NotInServiceMenuAnnotationFacetFactory;
 import org.apache.isis.core.progmodel.facets.actions.notinservicemenu.method.NotInServiceMenuMethodFacetFactory;
@@ -368,6 +369,7 @@ public final class ProgrammingModelFacetsJava5 extends ProgrammingModelAbstract
         addFactory(PostsPropertyChangedEventAnnotationFacetFactory.class);
         addFactory(PostsCollectionAddedToEventAnnotationFacetFactory.class);
         addFactory(PostsCollectionRemovedFromEventAnnotationFacetFactory.class);
+        addFactory(PostsActionInvokedEventFacetFactory.class);
 
         
         addFactory(ImmutableMarkerInterfaceFacetFactory.class);