Revision: 1073
Author: dhanji
Date: Fri Sep  4 02:08:15 2009
Log: Preliminary lifecycle and broadcasting extension experiment. Some  
servlet test cleanups bundled.
http://code.google.com/p/google-guice/source/detail?r=1073

Added:
  /trunk/lifecycle
  /trunk/lifecycle/build.properties
  /trunk/lifecycle/build.xml
  /trunk/lifecycle/lifecycle.iml
  /trunk/lifecycle/src
  /trunk/lifecycle/src/com
  /trunk/lifecycle/src/com/google
  /trunk/lifecycle/src/com/google/inject
  /trunk/lifecycle/src/com/google/inject/lifecycle
  /trunk/lifecycle/src/com/google/inject/lifecycle/BroadcastingLifecycle.java
  /trunk/lifecycle/src/com/google/inject/lifecycle/Lifecycle.java
  /trunk/lifecycle/src/com/google/inject/lifecycle/LifecycleModule.java
  /trunk/lifecycle/src/com/google/inject/lifecycle/ListOfMatchers.java
  /trunk/lifecycle/src/com/google/inject/lifecycle/Startable.java
  /trunk/lifecycle/src/com/google/inject/lifecycle/package-info.java
  /trunk/lifecycle/test
  /trunk/lifecycle/test/com
  /trunk/lifecycle/test/com/google
  /trunk/lifecycle/test/com/google/inject
  /trunk/lifecycle/test/com/google/inject/lifecycle
   
/trunk/lifecycle/test/com/google/inject/lifecycle/ArbitraryBroadcastTest.java
   
/trunk/lifecycle/test/com/google/inject/lifecycle/MultipleStartableTest.java
  /trunk/lifecycle/test/com/google/inject/lifecycle/StartableTest.java
Modified:
  /trunk/servlet/test/com/google/inject/servlet/DummyServlet.java
   
/trunk/servlet/test/com/google/inject/servlet/ServletDispatchIntegrationTest.java

=======================================
--- /dev/null
+++ /trunk/lifecycle/build.properties   Fri Sep  4 02:08:15 2009
@@ -0,0 +1,5 @@
+src.dir=src
+test.dir=test
+build.dir=build
+test.class=com.google.inject.lifecycle.LifecycleTest
+module=com.google.inject.lifecycle
=======================================
--- /dev/null
+++ /trunk/lifecycle/build.xml  Fri Sep  4 02:08:15 2009
@@ -0,0 +1,21 @@
+<?xml version="1.0"?>
+
+<project name="guice-lifecycle" basedir="." default="jar">
+
+  <import file="../common.xml"/>
+
+  <path id="compile.classpath">
+    <fileset dir="../lib" includes="*.jar"/>
+    <fileset dir="../lib/build" includes="*.jar"/>
+    <fileset dir="../build/dist" includes="*.jar"/>
+  </path>
+
+  <target name="jar" depends="jar.withdeps, manifest" description="Build  
jar.">
+    <jar destfile="${build.dir}/${ant.project.name}-${version}.jar"
+        manifest="${build.dir}/META-INF/MANIFEST.MF">
+      <zipfileset src="${build.dir}/${ant.project.name}-with-deps.jar"
+          excludes="com/google/inject/internal/**"/>
+    </jar>
+  </target>
+
+</project>
=======================================
--- /dev/null
+++ /trunk/lifecycle/lifecycle.iml      Fri Sep  4 02:08:15 2009
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module relativePaths="true" type="JAVA_MODULE" version="4">
+  <component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_5"  
inherit-compiler-output="true">
+    <exclude-output />
+    <content url="file://$MODULE_DIR$">
+      <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
+      <sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
+    </content>
+    <orderEntry type="inheritedJdk" />
+    <orderEntry type="sourceFolder" forTests="false" />
+    <orderEntry type="module" module-name="guice" />
+    <orderEntry type="module-library">
+      <library>
+        <CLASSES>
+          <root url="jar://$MODULE_DIR$/../lib/build/junit.jar!/" />
+        </CLASSES>
+        <JAVADOC />
+        <SOURCES />
+      </library>
+    </orderEntry>
+    <orderEntry type="module-library">
+      <library>
+        <CLASSES>
+          <root url="jar://$MODULE_DIR$/../lib/build/easymock.jar!/" />
+        </CLASSES>
+        <JAVADOC />
+        <SOURCES />
+      </library>
+    </orderEntry>
+  </component>
+</module>
+
=======================================
--- /dev/null
+++  
/trunk/lifecycle/src/com/google/inject/lifecycle/BroadcastingLifecycle.java     
 
Fri Sep  4 02:08:15 2009
@@ -0,0 +1,133 @@
+package com.google.inject.lifecycle;
+
+import com.google.inject.Binding;
+import com.google.inject.Inject;
+import com.google.inject.Injector;
+import com.google.inject.Key;
+import com.google.inject.Scopes;
+import com.google.inject.Singleton;
+import com.google.inject.internal.Lists;
+import com.google.inject.internal.Maps;
+import com.google.inject.internal.Preconditions;
+import com.google.inject.matcher.Matcher;
+import static com.google.inject.matcher.Matchers.any;
+import java.lang.reflect.Method;
+import java.util.List;
+import java.util.Map;
+import net.sf.cglib.proxy.InvocationHandler;
+import net.sf.cglib.proxy.Proxy;
+
+/** @author [email protected] (Dhanji R. Prasanna) */
+...@singleton class BroadcastingLifecycle implements Lifecycle {
+  private final Injector injector;
+  private final List<Class<?>> callableClasses;
+
+  // @GuardedBy(this)
+  private Map<Class<?>, List<Key<?>>> callableKeys;
+
+  private volatile boolean started = false;
+
+  @Inject
+  public BroadcastingLifecycle(Injector injector, @ListOfMatchers  
List<Class<?>> callableClasses) {
+    this.injector = injector;
+    this.callableClasses = callableClasses;
+
+    // Self start. Eventually we may want to move this to a hook-in from  
Guice-core.
+    start();
+  }
+
+  public void start() {
+    if (started) {
+      // throw? log warning?
+      return;
+    }
+
+    // OK to start the startables now.
+    // Guaranteed to return in order of module binding..
+    Map<Key<?>, Binding<?>> allBindings = injector.getBindings();
+
+    List<Binding<Startable>> startables = Lists.newArrayList();
+    Map<Class<?>, List<Key<?>>> callableKeys = Maps.newLinkedHashMap();
+
+    // Do not collapse into loop below (in synchronized block). Time  
complexity is still linear.
+    for (Binding<?> binding : allBindings.values()) {
+
+      Class<?> bindingType =  
binding.getKey().getTypeLiteral().getRawType();
+
+      // inner loop N*M complexity
+      for (Class<?> callable : callableClasses) {
+        if (callable.isAssignableFrom(bindingType)) {
+
+          // we don't want to instantiate these right now...
+          List<Key<?>> list = callableKeys.get(callable);
+
+          // Multimap put.
+          if (null == list) {
+            list = Lists.newArrayList();
+            callableKeys.put(callable, list);
+          }
+
+          list.add(binding.getKey());
+        }
+      }
+
+      // check startables now.
+      if (Startable.class.isAssignableFrom(bindingType)) {
+
+        // First make sure this is a singleton.
+        Preconditions.checkState(Scopes.isSingleton(binding),
+            "Egregious error, all Startables must be scopes as  
singletons!");
+
+        //noinspection unchecked
+        startables.add((Binding<Startable>) binding);
+      }
+    }
+
+    synchronized (this) {
+      for (Binding<Startable> binding : startables) {
+
+        // Go go zilla go! (sequential startup)
+        injector.getInstance(binding.getKey()).start();
+      }
+
+      // Safely publish keymap.
+      this.callableKeys = callableKeys;
+
+      // success!
+      started = true;
+    }
+  }
+
+  public <T> T broadcast(Class<T> clazz) {
+    return broadcast(clazz, any());
+  }
+
+  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);
+      }
+    }
+
+    @SuppressWarnings("unchecked") T caster = (T) Proxy
+        .newProxyInstance(clazz.getClassLoader(), new Class[] { clazz },  
new InvocationHandler() {
+          public Object invoke(Object o, Method method, Object[] objects)  
throws Throwable {
+
+            // propagate the method call with the same arg list to all  
instances.
+            for (T t : ts) {
+              method.invoke(t, objects);
+            }
+
+            // We can't return from multiple instances, so just return  
null.
+            return null;
+          }
+        });
+
+    return caster;
+  }
+}
=======================================
--- /dev/null
+++ /trunk/lifecycle/src/com/google/inject/lifecycle/Lifecycle.java     Fri  
Sep  4 02:08:15 2009
@@ -0,0 +1,17 @@
+package com.google.inject.lifecycle;
+
+import com.google.inject.ImplementedBy;
+import com.google.inject.matcher.Matcher;
+
+/**
+ *  @author [email protected] (Dhanji R. Prasanna)
+ */
+...@implementedby(BroadcastingLifecycle.class)
+public interface Lifecycle {
+
+  void start();
+
+  <T> T broadcast(Class<T> clazz);
+
+  <T> T broadcast(Class<T> clazz, Matcher<? super T> matcher);
+}
=======================================
--- /dev/null
+++ /trunk/lifecycle/src/com/google/inject/lifecycle/LifecycleModule.java       
 
Fri Sep  4 02:08:15 2009
@@ -0,0 +1,45 @@
+package com.google.inject.lifecycle;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.TypeLiteral;
+import com.google.inject.internal.ImmutableList;
+import com.google.inject.internal.Lists;
+import java.util.List;
+
+/**
+ * Use this module to configure lifecycle and multicasting support
+ * for your Guice applications.
+ *
+ * @author [email protected] (Dhanji R. Prasanna)
+// */
+public abstract class LifecycleModule extends AbstractModule {
+
+  private final List<Class<?>> callables = Lists.newArrayList();
+  private boolean autostart = false;
+
+  @Override
+  protected final void configure() {
+
+    // Call down into module.
+    configureLifecycle();
+
+    // The only real purpose of this is to do some error checking.
+    bind(new TypeLiteral<List<Class<?>>>() { })
+        .annotatedWith(ListOfMatchers.class)
+        .toInstance(ImmutableList.copyOf(callables));
+  }
+
+  protected abstract void configureLifecycle();
+
+  protected void callable(Class<?> type) {
+    callables.add(type);
+  }
+
+  protected void autostart() {
+    this.autostart = true;
+
+    // This is a cool method that will execute after injector creating
+    // completes, and thus much better than the eager singleton hack.
+    throw new UnsupportedOperationException("Asplode!");
+  }
+}
=======================================
--- /dev/null
+++ /trunk/lifecycle/src/com/google/inject/lifecycle/ListOfMatchers.java        
 
Fri Sep  4 02:08:15 2009
@@ -0,0 +1,11 @@
+package com.google.inject.lifecycle;
+
+import com.google.inject.BindingAnnotation;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/** @author [email protected] (Dhanji R. Prasanna) */
+...@retention(RetentionPolicy.RUNTIME)
+...@bindingannotation
+...@interface ListOfMatchers {
+}
=======================================
--- /dev/null
+++ /trunk/lifecycle/src/com/google/inject/lifecycle/Startable.java     Fri  
Sep  4 02:08:15 2009
@@ -0,0 +1,22 @@
+package com.google.inject.lifecycle;
+
+/**
+ * A convenience lifecycle interface. Any class the exposes
+ * this interface will be started if the lifecycle module is
+ * installed. The lifecycle module guarantees that the order
+ * in which Startables are called will match the order in which
+ * modules are installed.
+ *
+ * All instances that wish to use startable *must* be bound as
+ * singletons.
+ *
+ * @author [email protected] (Dhanji R. Prasanna)
+ */
+public interface Startable {
+  /**
+   * Called once the injector has been created completely.
+   * In PRODUCTION mode, this means when all singletons
+   * have been instantiated.
+   */
+  void start();
+}
=======================================
--- /dev/null
+++ /trunk/lifecycle/src/com/google/inject/lifecycle/package-info.java  Fri  
Sep  4 02:08:15 2009
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2009 Google Inc.
+ *
+ * Licensed 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.
+ */
+
+/**
+ * Lifecycle extension; this extension requires {...@code  
guice-lifecycle-2.0.jar}.
+ */
+package com.google.inject.lifecycle;
=======================================
--- /dev/null
+++  
/trunk/lifecycle/test/com/google/inject/lifecycle/ArbitraryBroadcastTest.java   
 
Fri Sep  4 02:08:15 2009
@@ -0,0 +1,37 @@
+package com.google.inject.lifecycle;
+
+import com.google.inject.Guice;
+import com.google.inject.Singleton;
+import junit.framework.TestCase;
+
+/** @author [email protected] (Dhanji R. Prasanna) */
+public class ArbitraryBroadcastTest extends TestCase {
+  private static int called;
+
+  public final void testCallable() {
+    called = 0;
+    Lifecycle lifecycle = Guice.createInjector(new LifecycleModule() {
+
+      @Override
+      protected void configureLifecycle() {
+        bind(Runnable.class).to(AClass.class).in(Singleton.class);
+        bind(AClass.class).in(Singleton.class);
+
+        callable(Runnable.class);
+      }
+
+    }).getInstance(Lifecycle.class);
+
+    lifecycle
+      .broadcast(Runnable.class)
+      .run();
+
+    assertEquals(2, called);
+  }
+
+  public static class AClass implements Runnable {
+    public void run() {
+      called++;
+    }
+  }
+}
=======================================
--- /dev/null
+++  
/trunk/lifecycle/test/com/google/inject/lifecycle/MultipleStartableTest.java    
 
Fri Sep  4 02:08:15 2009
@@ -0,0 +1,51 @@
+package com.google.inject.lifecycle;
+
+import com.google.inject.Guice;
+import com.google.inject.Singleton;
+import junit.framework.TestCase;
+
+/** @author [email protected] (Dhanji R. Prasanna) */
+public class MultipleStartableTest extends TestCase {
+  private static int started;
+
+  public final void testMultiStartable() {
+    started = 0;
+    Guice.createInjector(new LifecycleModule() {
+
+      @Override
+      protected void configureLifecycle() {
+        bind(AClass.class).in(Singleton.class);
+        bind(Startable.class)
+            .annotatedWith(ListOfMatchers.class)
+            .to(BClass.class)
+            .in(Singleton.class);
+        bind(Startable.class).to(CClass.class).in(Singleton.class);
+      }
+
+    }).getInstance(Lifecycle.class)
+      .start();
+
+    assertEquals(3, started);
+  }
+
+  public static class AClass implements Startable {
+
+    public void start() {
+      started++;
+    }
+  }
+
+  public static class BClass implements Startable {
+
+    public void start() {
+      started++;
+    }
+  }
+
+  public static class CClass implements Startable {
+
+    public void start() {
+      started++;
+    }
+  }
+}
=======================================
--- /dev/null
+++ /trunk/lifecycle/test/com/google/inject/lifecycle/StartableTest.java        
 
Fri Sep  4 02:08:15 2009
@@ -0,0 +1,32 @@
+package com.google.inject.lifecycle;
+
+import com.google.inject.Guice;
+import com.google.inject.Singleton;
+import junit.framework.TestCase;
+
+/** @author [email protected] (Dhanji R. Prasanna) */
+public class StartableTest extends TestCase {
+  private static boolean started;
+
+  public final void testStartable() {
+    started = false;
+    Guice.createInjector(new LifecycleModule() {
+
+      @Override
+      protected void configureLifecycle() {
+        bind(AClass.class).in(Singleton.class);
+      }
+
+    }).getInstance(Lifecycle.class)
+      .start();
+
+    assertTrue(started);
+  }
+
+  public static class AClass implements Startable {
+
+    public void start() {
+      started = true;
+    }
+  }
+}
=======================================
--- /trunk/servlet/test/com/google/inject/servlet/DummyServlet.java     Mon Dec 
 
15 01:42:36 2008
+++ /trunk/servlet/test/com/google/inject/servlet/DummyServlet.java     Fri  
Sep  4 02:08:15 2009
@@ -15,6 +15,7 @@
   */
  package com.google.inject.servlet;

+import com.google.inject.Singleton;
  import javax.servlet.http.HttpServlet;

  /**
@@ -22,6 +23,7 @@
   *
   * @author Dhanji R. Prasanna (dha...@gmail com)
   */
+...@singleton
  public class DummyServlet extends HttpServlet {

  }
=======================================
---  
/trunk/servlet/test/com/google/inject/servlet/ServletDispatchIntegrationTest.java
        
Sat Feb 14 02:28:08 2009
+++  
/trunk/servlet/test/com/google/inject/servlet/ServletDispatchIntegrationTest.java
        
Fri Sep  4 02:08:15 2009
@@ -30,6 +30,7 @@
  import javax.servlet.ServletResponse;
  import javax.servlet.http.HttpServlet;
  import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
  import junit.framework.TestCase;
  import static org.easymock.EasyMock.createMock;
  import static org.easymock.EasyMock.expect;
@@ -188,4 +189,50 @@
        destroys++;
      }
    }
-}
+
+
+  @Singleton
+  public static class ForwardingServlet extends HttpServlet {
+    public void service(ServletRequest servletRequest, ServletResponse  
servletResponse)
+        throws IOException, ServletException {
+      final HttpServletRequest request = (HttpServletRequest)  
servletRequest;
+
+      request.getRequestDispatcher("/blah.jsp")
+          .forward(servletRequest, servletResponse);
+    }
+  }
+
+  @Singleton
+  public static class ForwardedServlet extends HttpServlet {
+    public void service(ServletRequest servletRequest, ServletResponse  
servletResponse)
+        throws IOException, ServletException {
+      final HttpServletRequest request = (HttpServletRequest)  
servletRequest;
+
+      System.out.println(request.getRequestURI());
+ }
+  }
+
+  public void testForwardUsingRequestDispatcher() throws IOException,  
ServletException {
+    Guice.createInjector(new ServletModule() {
+      @Override
+      protected void configureServlets() {
+        serve("/*").with(ForwardingServlet.class);
+        serve("/blah.jsp").with(ForwardedServlet.class);
+      }
+    });
+
+    final HttpServletRequest requestMock =  
createMock(HttpServletRequest.class);
+    HttpServletResponse responseMock =  
createMock(HttpServletResponse.class);
+    expect(requestMock.getServletPath())
+        .andReturn("/")
+        .anyTimes();
+
+    expect(responseMock.isCommitted()).andReturn(false);
+
+    replay(requestMock, responseMock);
+
+    new GuiceFilter()
+        .doFilter(requestMock, responseMock,
+            createMock(FilterChain.class));
+  }
+}

--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups 
"google-guice-dev" group.
To post to this group, send email to [email protected]
To unsubscribe from this group, send email to 
[email protected]
For more options, visit this group at 
http://groups.google.com/group/google-guice-dev?hl=en
-~----------~----~----~----~------~----~------~--~---

Reply via email to