Lifecycle now supports parallel execution. Need to add support for parallel startup and autostart next.
TODO added to check servlet spec compliance in GS2 request dispatcher.
git-svn-id: https://google-guice.googlecode.com/svn/trunk@1074 d779f126-a31b-0410-b53b-1d3aecad763e
diff --git a/lifecycle/src/com/google/inject/lifecycle/BroadcastingLifecycle.java b/lifecycle/src/com/google/inject/lifecycle/BroadcastingLifecycle.java
index 74cb6af..0368185 100644
--- a/lifecycle/src/com/google/inject/lifecycle/BroadcastingLifecycle.java
+++ b/lifecycle/src/com/google/inject/lifecycle/BroadcastingLifecycle.java
@@ -14,11 +14,16 @@
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
import net.sf.cglib.proxy.InvocationHandler;
import net.sf.cglib.proxy.Proxy;
-/** @author [email protected] (Dhanji R. Prasanna) */
-@Singleton class BroadcastingLifecycle implements Lifecycle {
+/**
+ * @author [email protected] (Dhanji R. Prasanna)
+ */
+@Singleton
+class BroadcastingLifecycle implements Lifecycle {
private final Injector injector;
private final List<Class<?>> callableClasses;
@@ -103,16 +108,7 @@
}
public <T> T broadcast(Class<T> clazz, Matcher<? super T> matcher) {
- final List<T> ts = Lists.newArrayList();
- for (Key<?> key : callableKeys.get(clazz)) {
- // Should this get instancing happen during method call?
- @SuppressWarnings("unchecked") // Guarded by getInstance
- T t = (T) injector.getInstance(key);
-
- if (matcher.matches(t)) {
- ts.add(t);
- }
- }
+ final List<T> ts = instantiateForBroadcast(clazz, matcher);
@SuppressWarnings("unchecked") T caster = (T) Proxy
.newProxyInstance(clazz.getClassLoader(), new Class[] { clazz }, new InvocationHandler() {
@@ -130,4 +126,50 @@
return caster;
}
-}
\ No newline at end of file
+
+ private <T> List<T> instantiateForBroadcast(Class<T> clazz, Matcher<? super T> matcher) {
+ final List<T> ts = Lists.newArrayList();
+ for (Key<?> key : callableKeys.get(clazz)) {
+ // Should this get instancing happen during method call?
+ @SuppressWarnings("unchecked") // Guarded by getInstance
+ T t = (T) injector.getInstance(key);
+
+ if (matcher.matches(t)) {
+ ts.add(t);
+ }
+ }
+ return ts;
+ }
+
+ public <T> T broadcast(Class<T> clazz, final ExecutorService executorService) {
+ return broadcast(clazz, executorService, any());
+ }
+
+ public <T> T broadcast(Class<T> clazz, final ExecutorService executorService,
+ Matcher<? super T> matcher) {
+ final List<T> ts = instantiateForBroadcast(clazz, matcher);
+
+ @SuppressWarnings("unchecked") T caster = (T) Proxy
+ .newProxyInstance(clazz.getClassLoader(), new Class[] { clazz }, new InvocationHandler() {
+ public Object invoke(Object o, final Method method, final Object[] objects)
+ throws Throwable {
+
+ // propagate the method call with the same arg list to all instances.
+ for (final T t : ts) {
+ // Submit via executor service. TODO See if this can be parallelized by
+ // yet another dimension, i.e. inParallel(N)
+ executorService.submit(new Callable() {
+ public Object call() throws Exception {
+ return method.invoke(t, objects);
+ }
+ });
+ }
+
+ // We can't return from multiple instances, so just return null.
+ return null;
+ }
+ });
+
+ return caster;
+ }
+}
diff --git a/lifecycle/src/com/google/inject/lifecycle/Lifecycle.java b/lifecycle/src/com/google/inject/lifecycle/Lifecycle.java
index 0ec38b4..7204521 100644
--- a/lifecycle/src/com/google/inject/lifecycle/Lifecycle.java
+++ b/lifecycle/src/com/google/inject/lifecycle/Lifecycle.java
@@ -2,9 +2,10 @@
import com.google.inject.ImplementedBy;
import com.google.inject.matcher.Matcher;
+import java.util.concurrent.ExecutorService;
/**
- * @author [email protected] (Dhanji R. Prasanna)
+ * @author [email protected] (Dhanji R. Prasanna)
*/
@ImplementedBy(BroadcastingLifecycle.class)
public interface Lifecycle {
@@ -14,4 +15,8 @@
<T> T broadcast(Class<T> clazz);
<T> T broadcast(Class<T> clazz, Matcher<? super T> matcher);
+
+ <T> T broadcast(Class<T> clazz, ExecutorService executorService);
+
+ <T> T broadcast(Class<T> clazz, ExecutorService executorService, Matcher<? super T> matcher);
}
diff --git a/lifecycle/src/com/google/inject/lifecycle/ListOfMatchers.java b/lifecycle/src/com/google/inject/lifecycle/ListOfMatchers.java
index 847b1e1..32aedad 100644
--- a/lifecycle/src/com/google/inject/lifecycle/ListOfMatchers.java
+++ b/lifecycle/src/com/google/inject/lifecycle/ListOfMatchers.java
@@ -4,7 +4,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-/** @author [email protected] (Dhanji R. Prasanna) */
+/** @author [email protected] (Dhanji R. Prasanna) */
@Retention(RetentionPolicy.RUNTIME)
@BindingAnnotation
@interface ListOfMatchers {
diff --git a/lifecycle/src/com/google/inject/lifecycle/Startable.java b/lifecycle/src/com/google/inject/lifecycle/Startable.java
index beae593..6ea9ea8 100644
--- a/lifecycle/src/com/google/inject/lifecycle/Startable.java
+++ b/lifecycle/src/com/google/inject/lifecycle/Startable.java
@@ -10,7 +10,7 @@
* All instances that wish to use startable *must* be bound as
* singletons.
*
- * @author [email protected] (Dhanji R. Prasanna)
+ * @author [email protected] (Dhanji R. Prasanna)
*/
public interface Startable {
/**
diff --git a/lifecycle/test/com/google/inject/lifecycle/ArbitraryBroadcastTest.java b/lifecycle/test/com/google/inject/lifecycle/ArbitraryBroadcastTest.java
index 0751807..f2e930c 100644
--- a/lifecycle/test/com/google/inject/lifecycle/ArbitraryBroadcastTest.java
+++ b/lifecycle/test/com/google/inject/lifecycle/ArbitraryBroadcastTest.java
@@ -4,7 +4,7 @@
import com.google.inject.Singleton;
import junit.framework.TestCase;
-/** @author [email protected] (Dhanji R. Prasanna) */
+/** @author [email protected] (Dhanji R. Prasanna) */
public class ArbitraryBroadcastTest extends TestCase {
private static int called;
diff --git a/lifecycle/test/com/google/inject/lifecycle/MultipleStartableTest.java b/lifecycle/test/com/google/inject/lifecycle/MultipleStartableTest.java
index 500bced..6e67ff9 100644
--- a/lifecycle/test/com/google/inject/lifecycle/MultipleStartableTest.java
+++ b/lifecycle/test/com/google/inject/lifecycle/MultipleStartableTest.java
@@ -4,7 +4,7 @@
import com.google.inject.Singleton;
import junit.framework.TestCase;
-/** @author [email protected] (Dhanji R. Prasanna) */
+/** @author [email protected] (Dhanji R. Prasanna) */
public class MultipleStartableTest extends TestCase {
private static int started;
diff --git a/lifecycle/test/com/google/inject/lifecycle/ParallelizedBroadcastTest.java b/lifecycle/test/com/google/inject/lifecycle/ParallelizedBroadcastTest.java
new file mode 100644
index 0000000..27e8775
--- /dev/null
+++ b/lifecycle/test/com/google/inject/lifecycle/ParallelizedBroadcastTest.java
@@ -0,0 +1,55 @@
+package com.google.inject.lifecycle;
+
+import com.google.inject.Guice;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import junit.framework.TestCase;
+
+/** @author [email protected] (Dhanji R. Prasanna) */
+public class ParallelizedBroadcastTest extends TestCase {
+ private static final AtomicInteger called = new AtomicInteger();
+
+ public final void testCallable() throws InterruptedException {
+ called.set(0);
+ Lifecycle lifecycle = Guice.createInjector(new LifecycleModule() {
+
+ @Override
+ protected void configureLifecycle() {
+ bind(Runnable.class).to(AClass.class);
+
+ // Does not get called because the key does not expose the callable.
+ bind(Object.class).to(AClass.class);
+
+ bind(BaseClass.class).to(AClass.class);
+
+ callable(Runnable.class);
+ }
+
+ }).getInstance(Lifecycle.class);
+
+ final ExecutorService executorService = Executors.newFixedThreadPool(3);
+ lifecycle
+ .broadcast(Runnable.class, executorService)
+ .run();
+
+ executorService.shutdown();
+ executorService.awaitTermination(5L, TimeUnit.SECONDS);
+
+ assertEquals(2, called.get());
+ }
+
+ public static class BaseClass implements Runnable {
+
+ public void run() {
+ called.incrementAndGet();
+ }
+ }
+
+ public static class AClass extends BaseClass implements Runnable {
+ public void run() {
+ super.run();
+ }
+ }
+}
diff --git a/lifecycle/test/com/google/inject/lifecycle/StartableTest.java b/lifecycle/test/com/google/inject/lifecycle/StartableTest.java
index 7f197b8..0ec2e37 100644
--- a/lifecycle/test/com/google/inject/lifecycle/StartableTest.java
+++ b/lifecycle/test/com/google/inject/lifecycle/StartableTest.java
@@ -4,7 +4,7 @@
import com.google.inject.Singleton;
import junit.framework.TestCase;
-/** @author [email protected] (Dhanji R. Prasanna) */
+/** @author [email protected] (Dhanji R. Prasanna) */
public class StartableTest extends TestCase {
private static boolean started;
diff --git a/servlet/src/com/google/inject/servlet/ManagedServletPipeline.java b/servlet/src/com/google/inject/servlet/ManagedServletPipeline.java
index 320229e..f5392fb 100755
--- a/servlet/src/com/google/inject/servlet/ManagedServletPipeline.java
+++ b/servlet/src/com/google/inject/servlet/ManagedServletPipeline.java
@@ -115,6 +115,10 @@
*/
RequestDispatcher getRequestDispatcher(String path) {
final String newRequestUri = path;
+
+ // TODO(dhanji): check servlet spec to see if the following is legal or not.
+ // Need to strip query string if requested...
+
for (final ServletDefinition servletDefinition : servletDefinitions) {
if (servletDefinition.shouldServe(path)) {
return new RequestDispatcher() {