Revision: 1236
Author: dhanji
Date: Wed Sep 15 22:32:53 2010
Log: Service API extension initial checkin. Support for basic start/stop lifecycle and parallelizing service startup. Needs more integration tests.
http://code.google.com/p/google-guice/source/detail?r=1236

Added:
 /trunk/extensions/service
 /trunk/extensions/service/service.iml
 /trunk/extensions/service/src
 /trunk/extensions/service/src/com
 /trunk/extensions/service/src/com/google
 /trunk/extensions/service/src/com/google/inject
 /trunk/extensions/service/src/com/google/inject/service
 /trunk/extensions/service/src/com/google/inject/service/AsyncService.java
/trunk/extensions/service/src/com/google/inject/service/CompositeService.java
 /trunk/extensions/service/src/com/google/inject/service/Service.java
 /trunk/extensions/service/test
 /trunk/extensions/service/test/com
 /trunk/extensions/service/test/com/google
 /trunk/extensions/service/test/com/google/inject
 /trunk/extensions/service/test/com/google/inject/service
/trunk/extensions/service/test/com/google/inject/service/SingleServiceIntegrationTest.java

=======================================
--- /dev/null
+++ /trunk/extensions/service/service.iml       Wed Sep 15 22:32:53 2010
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="JAVA_MODULE" version="4">
+  <component name="NewModuleRootManager" 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" />
+  </component>
+</module>
+
=======================================
--- /dev/null
+++ /trunk/extensions/service/src/com/google/inject/service/AsyncService.java Wed Sep 15 22:32:53 2010
@@ -0,0 +1,131 @@
+/**
+ * Copyright (C) 2010 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.
+ */
+
+package com.google.inject.service;
+
+import com.google.inject.internal.util.Preconditions;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
+import java.util.concurrent.FutureTask;
+
+/**
+ * An asynchronous implementation of {...@link com.google.inject.service.Service}
+ * that provides convenience callbacks to create your own services.
+ *
+ * @author [email protected] (Dhanji R. Prasanna)
+ */
+public abstract class AsyncService implements Service {
+  private final ExecutorService executor;
+
+  /**
+   * A runnable that does nothing.
+   */
+  private static final Runnable NOOP = new Runnable() {
+    public void run() { }
+  };
+
+  private volatile State state;
+
+  public AsyncService(ExecutorService executor) {
+    this.executor = executor;
+  }
+
+  public synchronized final Future<State> start() {
+    Preconditions.checkState(state != State.STOPPED,
+        "Cannot restart a service that has been stopped");
+
+    // Starts are idempotent.
+    if (state == State.STARTED) {
+      return new FutureTask<State>(NOOP, State.STARTED);
+    }
+
+    final Future<State> task = executor.submit(new Callable<State>() {
+      public State call() {
+        onStart();
+        return state = State.STARTED;
+      }
+    });
+
+    return task;
+    // Wrap it in another future to catch failures.
+//    return new FutureTask<State>(futureGet(task));
+  }
+
+  /**
+   * Called back when this service must do its start work. Typically occurs
+   * in a background thread. The result of this method is returned to the
+   * original caller of {...@link Service#start()} and can thus be used to
+   * return a status message after start completes (or fails as the case
+   * may be).
+   */
+  protected abstract void onStart();
+
+  public synchronized final Future<State> stop() {
+ Preconditions.checkState(state != null, "Must start this service before you stop it!");
+
+    // Likewise, stops are idempotent.
+    if (state == State.STOPPED) {
+      return new FutureTask<State>(NOOP, State.STOPPED);
+    }
+
+    // TODO Should we bother doing the wrap, or is it enough to return
+    // this future as is?
+    final Future<State> task = executor.submit(new Callable<State>() {
+      public State call() {
+        onStop();
+        return state = State.STOPPED;
+      }
+    });
+
+    // Wrap it in another future to catch failures.
+    return task;
+//    return new FutureTask<State>(futureGet(task));
+  }
+
+  /**
+   * Called back when this service must shutdown. Typically occurs
+   * in a background thread. The result of this method is returned to the
+   * original caller of {...@link Service#stop()} and can thus be used to
+   * return a status message after stop completes (or fails as the case
+   * may be).
+   */
+  protected abstract void onStop();
+
+  public final State state() {
+    return state;
+  }
+
+  /**
+   * Returns a runnable that when run will get() the given future and
+   * update {...@link #state} to FAILED if there was an exception thrown.
+   */
+  private Callable<State> futureGet(final Future<State> task) {
+    return new Callable<State>() {
+      public State call() {
+        try {
+          System.out.println("FutureGEtting");
+          return task.get();
+        } catch (InterruptedException e) {
+          return state = State.FAILED;
+        } catch (ExecutionException e) {
+          return state = State.FAILED;
+        }
+      }
+    };
+  }
+}
=======================================
--- /dev/null
+++ /trunk/extensions/service/src/com/google/inject/service/CompositeService.java Wed Sep 15 22:32:53 2010
@@ -0,0 +1,125 @@
+/**
+ * Copyright (C) 2010 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.
+ */
+package com.google.inject.service;
+
+import com.google.inject.Inject;
+import com.google.inject.Injector;
+import com.google.inject.Key;
+import com.google.inject.internal.util.ImmutableList;
+import com.google.inject.internal.util.Lists;
+import com.google.inject.internal.util.Preconditions;
+import com.google.inject.internal.util.Sets;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.FutureTask;
+
+/**
+ * A service that composes other services together in a fixed order.
+ *
+ * @author [email protected] (Dhanji R. Prasanna)
+ */
+public class CompositeService {
+  private final Injector injector;
+
+ private final Set<Key<? extends Service>> services = Sets.newLinkedHashSet();
+
+  /**
+   * Represents the state of this composite service. Will equal FAILED
+   * even if only one component service fails to start or stop. In other
+   * words, all component services must start successfully for this
+   * service to be considered started and similarly for stopped.
+   */
+  private volatile Service.State compositeState;
+  private boolean composed;
+
+  @Inject
+  CompositeService(Injector injector) {
+    this.injector = injector;
+  }
+
+  public CompositeService add(Class<? extends Service> service) {
+    return add(Key.get(service));
+  }
+
+  public CompositeService add(Key<? extends Service> service) {
+    Preconditions.checkState(!composed,
+ "Cannot reuse a CompositeService after it has been compose()d. Please create a new one.");
+    // Verify that the binding exists. Throws an exception if not.
+    injector.getBinding(service);
+
+    services.add(service);
+    return this;
+  }
+
+  public Service compose() {
+    Preconditions.checkState(!composed,
+ "Cannot reuse a CompositeService after it has been compose()d. Please create a new one.");
+    composed = true;
+
+    // Defensive copy.
+ final List<Key<? extends Service>> services = ImmutableList.copyOf(this.services);
+
+    return new Service() {
+      public Future<State> start() {
+        final List<Future<State>> tasks = Lists.newArrayList();
+        for (Key<? extends Service> service : services) {
+          tasks.add(injector.getInstance(service).start());
+        }
+
+        return futureGet(tasks, State.STARTED);
+      }
+
+      public Future<State> stop() {
+        final List<Future<State>> tasks = Lists.newArrayList();
+        for (Key<? extends Service> service : services) {
+          tasks.add(injector.getInstance(service).stop());
+        }
+
+        return futureGet(tasks, State.STOPPED);
+      }
+
+      public State state() {
+        return compositeState;
+      }
+    };
+  }
+
+ private FutureTask<Service.State> futureGet(final List<Future<Service.State>> tasks,
+      final Service.State state) {
+    return new FutureTask<Service.State>(new Callable<Service.State>() {
+      public Service.State call() {
+        System.out.println("GeT :--- ");
+        boolean ok = true;
+        for (Future<Service.State> task : tasks) {
+          try {
+            System.out.println("GeT : " + task);
+            ok = state == task.get();
+            System.out.println("OK : " + task);
+          } catch (InterruptedException e) {
+            return compositeState = Service.State.FAILED;
+          } catch (ExecutionException e) {
+            return compositeState = Service.State.FAILED;
+          }
+        }
+
+        return compositeState = ok ? state : Service.State.FAILED;
+      }
+    });
+  }
+}
=======================================
--- /dev/null
+++ /trunk/extensions/service/src/com/google/inject/service/Service.java Wed Sep 15 22:32:53 2010
@@ -0,0 +1,67 @@
+/**
+ * Copyright (C) 2010 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.
+ */
+
+package com.google.inject.service;
+
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+
+/**
+ * An object with an operational state, asynchronous {...@link #start()} and
+ * {...@link #stop()} lifecycle methods to transition in and out of this state.
+ * Example services include http servers, RPC systems and timer tasks.
+ *
+ * @author [email protected] (Dhanji R. Prasanna)
+ */
+public interface Service {
+  /**
+   * If the service has already been started, this method returns
+ * immediately without taking action. A stopped service may not be restarted.
+   *
+ * @return a future for the startup result, regardless of whether this call
+   *     initiated startup. Calling {...@link Future#get} will block until the
+   *     service has finished starting, and returns the resultant state. If
+ * the service fails to start, {...@link Future#get} will throw an {...@link
+   *     ExecutionException}. If it has already finished starting,
+   *     {...@link Future#get} returns immediately.
+   */
+  Future<State> start();
+
+  /**
+   * If the service is {...@link State#STARTED} initiates service shutdown and
+   * returns immediately. If the service has already been stopped, this
+   * method returns immediately without taking action.
+   *
+ * @return a future for the shutdown result, regardless of whether this call + * initiated shutdown. Calling {...@link Future#get} will block until the
+   *     service has finished shutting down, and either returns {...@link
+   *     State#STOPPED} or throws an {...@link ExecutionException}. If it has
+   *     already finished stopping, {...@link Future#get} returns immediately.
+   */
+  Future<State> stop();
+
+  /**
+ * Returns the current state of this service. One of {...@link State} possible + * values, or null if this is a brand new object, i.e., has not been put into
+   * any state yet.
+   */
+  State state();
+
+  /**
+   * The lifecycle states of a service.
+   */
+  enum State { STARTED, STOPPED, FAILED }
+}
=======================================
--- /dev/null
+++ /trunk/extensions/service/test/com/google/inject/service/SingleServiceIntegrationTest.java Wed Sep 15 22:32:53 2010
@@ -0,0 +1,66 @@
+package com.google.inject.service;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicInteger;
+import junit.framework.TestCase;
+
+/**
+ * Tests using Async Service.
+ */
+public class SingleServiceIntegrationTest extends TestCase {
+
+ public final void testAsyncServiceLifecycle() throws InterruptedException {
+    ExecutorService executor = Executors.newSingleThreadExecutor();
+
+    final CountDownLatch latch = new CountDownLatch(2);
+    AsyncService service = new AsyncService(executor) {
+      @Override protected void onStart() {
+        assertEquals(2, latch.getCount());
+
+        latch.countDown();
+      }
+
+      @Override protected void onStop() {
+        assertEquals(1, latch.getCount());
+
+        latch.countDown();
+      }
+    };
+
+    service.start();
+    latch.await(2, TimeUnit.SECONDS);
+
+    service.stop();
+    latch.await(2, TimeUnit.SECONDS);
+
+    executor.shutdown();
+    assertEquals(0, latch.getCount());
+  }
+
+  public final void testAsyncServiceBlockingLifecycle()
+      throws InterruptedException, ExecutionException, TimeoutException {
+    ExecutorService executor = Executors.newSingleThreadExecutor();
+
+    final AtomicInteger integer = new AtomicInteger(2);
+    AsyncService service = new AsyncService(executor) {
+      @Override protected void onStart() {
+        assertEquals(2, integer.getAndDecrement());
+      }
+
+      @Override protected void onStop() {
+        assertEquals(1, integer.getAndDecrement());
+      }
+    };
+
+    service.start().get(2, TimeUnit.SECONDS);
+    service.stop().get(2, TimeUnit.SECONDS);
+
+    executor.shutdown();
+    assertEquals(0, integer.get());
+  }
+}

--
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