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();