aboutsummaryrefslogtreecommitdiff
path: root/extensions/service
diff options
context:
space:
mode:
authordhanji <dhanji@d779f126-a31b-0410-b53b-1d3aecad763e>2010-09-16 05:32:53 +0000
committerdhanji <dhanji@d779f126-a31b-0410-b53b-1d3aecad763e>2010-09-16 05:32:53 +0000
commit65888c01862049c0f7744cf4dfac371ce780fb24 (patch)
tree3c55edc5aec038758f5a1b37f1d7679e558a312a /extensions/service
parent190e95c007d434c2c2ceae18fa3efc7d4887c0d2 (diff)
downloadguice-65888c01862049c0f7744cf4dfac371ce780fb24.tar.gz
Service API extension initial checkin. Support for basic start/stop lifecycle and parallelizing service startup. Needs more integration tests.
git-svn-id: https://google-guice.googlecode.com/svn/trunk@1236 d779f126-a31b-0410-b53b-1d3aecad763e
Diffstat (limited to 'extensions/service')
-rw-r--r--extensions/service/service.iml14
-rw-r--r--extensions/service/src/com/google/inject/service/AsyncService.java131
-rw-r--r--extensions/service/src/com/google/inject/service/CompositeService.java125
-rw-r--r--extensions/service/src/com/google/inject/service/Service.java67
-rw-r--r--extensions/service/test/com/google/inject/service/SingleServiceIntegrationTest.java66
5 files changed, 403 insertions, 0 deletions
diff --git a/extensions/service/service.iml b/extensions/service/service.iml
new file mode 100644
index 00000000..ee3fb50c
--- /dev/null
+++ b/extensions/service/service.iml
@@ -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>
+
diff --git a/extensions/service/src/com/google/inject/service/AsyncService.java b/extensions/service/src/com/google/inject/service/AsyncService.java
new file mode 100644
index 00000000..8672c0be
--- /dev/null
+++ b/extensions/service/src/com/google/inject/service/AsyncService.java
@@ -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 dhanji@gmail.com (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;
+ }
+ }
+ };
+ }
+}
diff --git a/extensions/service/src/com/google/inject/service/CompositeService.java b/extensions/service/src/com/google/inject/service/CompositeService.java
new file mode 100644
index 00000000..8e578c6a
--- /dev/null
+++ b/extensions/service/src/com/google/inject/service/CompositeService.java
@@ -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 dhanji@gmail.com (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;
+ }
+ });
+ }
+}
diff --git a/extensions/service/src/com/google/inject/service/Service.java b/extensions/service/src/com/google/inject/service/Service.java
new file mode 100644
index 00000000..fb7dc0d9
--- /dev/null
+++ b/extensions/service/src/com/google/inject/service/Service.java
@@ -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 dhanji@gmail.com (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 }
+}
diff --git a/extensions/service/test/com/google/inject/service/SingleServiceIntegrationTest.java b/extensions/service/test/com/google/inject/service/SingleServiceIntegrationTest.java
new file mode 100644
index 00000000..27a9baf6
--- /dev/null
+++ b/extensions/service/test/com/google/inject/service/SingleServiceIntegrationTest.java
@@ -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());
+ }
+}