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 2015/10/06 09:29:02 UTC

[3/5] isis git commit: ISIS-1205: support injection into Collection or List

ISIS-1205: support injection into Collection<T> or List<T>

The kitchensink app (isisaddons) has been updated to act as a regression test for this feature.


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

Branch: refs/heads/master
Commit: fba14c56fc25cef16ce9a618a0385e0a444aad76
Parents: fbb2fac
Author: Dan Haywood <da...@haywood-associates.co.uk>
Authored: Tue Oct 6 08:25:36 2015 +0100
Committer: Dan Haywood <da...@haywood-associates.co.uk>
Committed: Tue Oct 6 08:25:36 2015 +0100

----------------------------------------------------------------------
 .../guides/_rg_annotations_manpage-Inject.adoc  | 44 +++++++++++++
 .../services/ServicesInjectorDefault.java       | 65 ++++++++++++++------
 .../InjectorMethodEvaluatorDefault.java         |  8 ++-
 3 files changed, 97 insertions(+), 20 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/isis/blob/fba14c56/adocs/documentation/src/main/asciidoc/guides/_rg_annotations_manpage-Inject.adoc
----------------------------------------------------------------------
diff --git a/adocs/documentation/src/main/asciidoc/guides/_rg_annotations_manpage-Inject.adoc b/adocs/documentation/src/main/asciidoc/guides/_rg_annotations_manpage-Inject.adoc
index 326c5ba..00ddc71 100644
--- a/adocs/documentation/src/main/asciidoc/guides/_rg_annotations_manpage-Inject.adoc
+++ b/adocs/documentation/src/main/asciidoc/guides/_rg_annotations_manpage-Inject.adoc
@@ -58,6 +58,50 @@ Generally we recommend using `@javax.inject.Inject`; it involves less code, and
 
 
 
+== Injecting collection of services
+
+It can sometimes be useful to have declared multiple implementations of a particular domain service.  For example,
+you may have a module that defines an SPI service, where multiple other modules might provide implementations of that SPI
+ (akin to the chain of responsibility pattern).  To support these scenarios, it is possible to annotate a `List` or `Collection`.
+
+For example, suppose that we provide an SPI service to veto the placing of ``Order``s for certain ``Customer``s:
+
+[source,java]
+----
+public interface CustomerOrderAdvisorService {
+    @Programmatic
+    String vetoPlaceOrder(Customer c);
+----
+
+We could then inject a collection of these services:
+
+[source,java]
+----
+public class Customer {
+    public Order placeOrder(Product p, int quantity) { ... }
+    public String disablePlaceOrder(Product p, int quantity) {
+        for(CustomerOrderAdvisorService advisor: advisors) {
+            String reason = advisor.vetoPlaceOrder(this);
+            if(reason != null) { return reason; }
+        }
+        return null;
+    }
+    @Inject
+    Collection<CustomerOrderAdvisorService> advisors;       // <1>
+}
+----
+<1> inject a collection of the services.
+
+[NOTE]
+====
+An alternative and almost equivalent design would be to publish an event using the xref:rg.adoc#_rg_services-api_manpage-EventBusService[`EventBusService`] and implement the domain services as subscribers to
+the event.  This alternative design is used in the (non-ASF) http://github.com/isisaddons/isis-module-poly[Isis addons' poly] module, for example.
+====
+
+
+
+
+
 == Manually injecting services
 
 Isis performs dependency injection when domain entities are recreated.  It will also perform dependency injection if an object is created through the `DomainObjectContainer`.

http://git-wip-us.apache.org/repos/asf/isis/blob/fba14c56/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/ServicesInjectorDefault.java
----------------------------------------------------------------------
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/ServicesInjectorDefault.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/ServicesInjectorDefault.java
index 0813295..9eb9d36 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/ServicesInjectorDefault.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/ServicesInjectorDefault.java
@@ -22,6 +22,8 @@ package org.apache.isis.core.metamodel.services;
 import java.lang.reflect.Field;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
@@ -294,26 +296,58 @@ public class ServicesInjectorDefault implements ServicesInjectorSpi {
             autowire(object, field, services);
         }
         
-        // recurse up the hierarchy
+        // recurse up the object's class hierarchy
         final Class<?> superclass = cls.getSuperclass();
         if(superclass != null) {
             autowireViaFields(object, services, superclass);
         }
     }
 
-    private void autowire(final Object object, final Field field, final List<Object> services) {
+    private void autowire(
+            final Object object,
+            final Field field,
+            final List<Object> services) {
+
+        final Class<?> type = field.getType();
+        // don't think that type can ever be null,
+        // but Javadoc for java.lang.reflect.Field doesn't say
+        if(type == null) {
+            return;
+        }
+
+        // inject into Collection<T> or List<T>
+        if(Collection.class.isAssignableFrom(type) || List.class.isAssignableFrom(type)) {
+            final Type genericType = field.getGenericType();
+            if(genericType instanceof ParameterizedType) {
+                final ParameterizedType listParameterizedType = (ParameterizedType) genericType;
+                final Class<?> listType = (Class<?>) listParameterizedType.getActualTypeArguments()[0];
+                final List<Object> listOfServices =
+                        Collections.unmodifiableList(
+                            Lists.newArrayList(
+                                Iterables.filter(services, new Predicate<Object>() {
+                                    @Override
+                                    public boolean apply(final Object input) {
+                                        return input != null && listType.isAssignableFrom(input.getClass());
+                                    }
+                                })));
+                invokeInjectorField(field, object, listOfServices);
+            }
+        }
+
         for (final Object service : services) {
             final Class<?> serviceClass = service.getClass();
-            final boolean canInject = isInjectorFieldFor(field, serviceClass);
-            if(canInject) {
-                field.setAccessible(true);
+            if(type.isAssignableFrom(serviceClass)) {
                 invokeInjectorField(field, object, service);
                 return;
             }
         }
     }
 
-    private void autowireViaPrefixedMethods(final Object object, final List<Object> services, final Class<?> cls, final String prefix) {
+    private void autowireViaPrefixedMethods(
+            final Object object,
+            final List<Object> services,
+            final Class<?> cls,
+            final String prefix) {
         final List<Method> methods = Arrays.asList(cls.getMethods());
         final Iterable<Method> prefixedMethods = Iterables.filter(methods, new Predicate<Method>(){
             public boolean apply(final Method method) {
@@ -327,7 +361,10 @@ public class ServicesInjectorDefault implements ServicesInjectorSpi {
         }
     }
 
-    private void autowire(final Object object, final Method prefixedMethod, final List<Object> services) {
+    private void autowire(
+            final Object object,
+            final Method prefixedMethod,
+            final List<Object> services) {
         for (final Object service : services) {
             final Class<?> serviceClass = service.getClass();
             final boolean isInjectorMethod = injectorMethodEvaluator.isInjectorMethodFor(prefixedMethod, serviceClass);
@@ -339,22 +376,13 @@ public class ServicesInjectorDefault implements ServicesInjectorSpi {
         }
     }
 
-    private boolean isInjectorFieldFor(final Field field, final Class<?> serviceClass) {
-        final Class<?> type = field.getType();
-        // don't think that type can ever be null, but Javadoc for java.lang.reflect.Field doesn't say
-        return type != null && type.isAssignableFrom(serviceClass);
-    }
-
-
     private static void invokeMethod(final Method method, final Object target, final Object[] parameters) {
         try {
             method.invoke(target, parameters);
-        } catch (final SecurityException e) {
+        } catch (final SecurityException | IllegalAccessException e) {
             throw new MetaModelException(String.format("Cannot access the %s method in %s", method.getName(), target.getClass().getName()));
         } catch (final IllegalArgumentException e1) {
             throw new MetaModelException(e1);
-        } catch (final IllegalAccessException e1) {
-            throw new MetaModelException(String.format("Cannot access the %s method in %s", method.getName(), target.getClass().getName()));
         } catch (final InvocationTargetException e) {
             final Throwable targetException = e.getTargetException();
             if (targetException instanceof RuntimeException) {
@@ -367,6 +395,7 @@ public class ServicesInjectorDefault implements ServicesInjectorSpi {
 
     private static void invokeInjectorField(final Field field, final Object target, final Object parameter) {
         try {
+            field.setAccessible(true);
             field.set(target, parameter);
         } catch (final IllegalArgumentException e) {
             throw new MetaModelException(e);
@@ -405,7 +434,7 @@ public class ServicesInjectorDefault implements ServicesInjectorSpi {
     @Override
     public <T> List<T> lookupServices(final Class<T> serviceClass) {
         locateAndCache(serviceClass);
-        return (List<T>) servicesAssignableToType.get(serviceClass);
+        return Collections.unmodifiableList((List<T>) servicesAssignableToType.get(serviceClass));
     };
 
     private void locateAndCache(final Class<?> serviceClass) {

http://git-wip-us.apache.org/repos/asf/isis/blob/fba14c56/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/InjectorMethodEvaluatorDefault.java
----------------------------------------------------------------------
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/InjectorMethodEvaluatorDefault.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/InjectorMethodEvaluatorDefault.java
index 03a1c2d..85e00f8 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/InjectorMethodEvaluatorDefault.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/InjectorMethodEvaluatorDefault.java
@@ -32,7 +32,9 @@ public final class InjectorMethodEvaluatorDefault implements InjectorMethodEvalu
 
     private final Map<Method, Map<Class<?>, Boolean>> isInjectorMethod = Maps.newConcurrentMap();
 
-    public boolean isInjectorMethodFor(Method method, final Class<?> serviceClass) {
+    public boolean isInjectorMethodFor(
+            final Method method,
+            final Class<?> serviceClass) {
         Map<Class<?>, Boolean> classBooleanMap = isInjectorMethod.get(method);
         if(classBooleanMap == null) {
             synchronized (isInjectorMethod) {
@@ -48,7 +50,9 @@ public final class InjectorMethodEvaluatorDefault implements InjectorMethodEvalu
         return result;
     }
 
-    private static boolean determineIsInjectorMethodFor(Method method, Class<?> serviceClass) {
+    private static boolean determineIsInjectorMethodFor(
+            final Method method,
+            final Class<?> serviceClass) {
         final String methodName = method.getName();
         if (methodName.startsWith("set") || methodName.startsWith("inject")) {
             final Class<?>[] parameterTypes = method.getParameterTypes();