diff --git a/core/src/main/java/org/jclouds/concurrent/config/ExecutorServiceModule.java b/core/src/main/java/org/jclouds/concurrent/config/ExecutorServiceModule.java index 99d8601a4c..e61c7fc824 100644 --- a/core/src/main/java/org/jclouds/concurrent/config/ExecutorServiceModule.java +++ b/core/src/main/java/org/jclouds/concurrent/config/ExecutorServiceModule.java @@ -45,8 +45,6 @@ import org.jclouds.lifecycle.Closer; import org.jclouds.logging.Logger; import com.google.common.annotations.VisibleForTesting; -import com.google.common.eventbus.AsyncEventBus; -import com.google.common.eventbus.EventBus; import com.google.common.util.concurrent.ThreadFactoryBuilder; import com.google.inject.AbstractModule; import com.google.inject.Provides; @@ -323,12 +321,6 @@ public class ExecutorServiceModule extends AbstractModule { } - @Provides - @Singleton - EventBus provideEventBus(@Named(Constants.PROPERTY_USER_THREADS) ExecutorService userThreads){ - return new AsyncEventBus(userThreads); - } - @Provides @Singleton @Named(Constants.PROPERTY_USER_THREADS) diff --git a/core/src/main/java/org/jclouds/events/config/ConfiguresEventBus.java b/core/src/main/java/org/jclouds/events/config/ConfiguresEventBus.java new file mode 100644 index 0000000000..13c9cd9481 --- /dev/null +++ b/core/src/main/java/org/jclouds/events/config/ConfiguresEventBus.java @@ -0,0 +1,38 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds licenses this file + * to you 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 org.jclouds.events.config; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import com.google.common.eventbus.EventBus; + +/** + * Designates the module configures an {@link EventBus}. + * + * @author Ignasi Barrera + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface ConfiguresEventBus { + +} diff --git a/core/src/main/java/org/jclouds/events/config/EventBusModule.java b/core/src/main/java/org/jclouds/events/config/EventBusModule.java new file mode 100644 index 0000000000..d7aa3fd864 --- /dev/null +++ b/core/src/main/java/org/jclouds/events/config/EventBusModule.java @@ -0,0 +1,85 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds licenses this file + * to you 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 org.jclouds.events.config; + +import java.util.concurrent.ExecutorService; + +import javax.inject.Named; +import javax.inject.Singleton; + +import org.jclouds.Constants; +import org.jclouds.concurrent.config.ExecutorServiceModule; +import org.jclouds.events.config.annotations.AsyncBus; +import org.jclouds.events.handlers.DeadEventLoggingHandler; + +import com.google.common.eventbus.AsyncEventBus; +import com.google.common.eventbus.EventBus; +import com.google.inject.AbstractModule; +import com.google.inject.Provides; + +/** + * Configures the {@link EventBus} to be used in the platform. + *

+ * This class will provide an {@link AsyncEventBus} to be used to provide a basic pub/sub system for + * asynchronous operations. + * + * @author Ignasi Barrera + * + * @see ExecutorServiceModule + * @see AsyncEventBus + * @see EventBus + * @see AsyncBus + */ +@ConfiguresEventBus +public class EventBusModule extends AbstractModule { + /** + * Provides an {@link AsyncEventBus} that will use the configured executor service to dispatch + * events to subscribers. + */ + @Provides + @Singleton + AsyncEventBus provideAsyncEventBus( + @Named(Constants.PROPERTY_USER_THREADS) final ExecutorService executor, + final DeadEventLoggingHandler deadEventsHandler) { + AsyncEventBus asyncBus = new AsyncEventBus("jclouds-async-event-bus", executor); + asyncBus.register(deadEventsHandler); + return asyncBus; + } + + /** + * Provides asynchronous {@link EventBus}. + */ + @Provides + @Singleton + EventBus provideSyncEventBus(final DeadEventLoggingHandler deadEventsHandler) { + EventBus syncBus = new EventBus("jclouds-sync-event-bus"); + syncBus.register(deadEventsHandler); + return syncBus; + } + + /** + * Configures the {@link EventBus} to be singleton and enables the {@link AsyncBus} annotation. + */ + @Override + protected void configure() { + bind(EventBus.class).annotatedWith(AsyncBus.class).to(AsyncEventBus.class); + } + +} diff --git a/core/src/main/java/org/jclouds/events/config/annotations/AsyncBus.java b/core/src/main/java/org/jclouds/events/config/annotations/AsyncBus.java new file mode 100644 index 0000000000..d92dc367e3 --- /dev/null +++ b/core/src/main/java/org/jclouds/events/config/annotations/AsyncBus.java @@ -0,0 +1,50 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds licenses this file + * to you 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 org.jclouds.events.config.annotations; + +import static java.lang.annotation.ElementType.ANNOTATION_TYPE; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import javax.inject.Qualifier; + +import org.jclouds.events.config.EventBusModule; + +import com.google.common.eventbus.AsyncEventBus; +import com.google.common.eventbus.EventBus; + +/** + * Used to configure {@link EventBus} injection, providing a flexible way to inject the + * {@link AsyncEventBus}. + * + * @author Ignasi Barrera + * + * @see EventBusModule + */ +@Target({ANNOTATION_TYPE, FIELD, PARAMETER}) +@Retention(RUNTIME) +@Qualifier +public @interface AsyncBus { + +} diff --git a/core/src/main/java/org/jclouds/events/handlers/DeadEventLoggingHandler.java b/core/src/main/java/org/jclouds/events/handlers/DeadEventLoggingHandler.java new file mode 100644 index 0000000000..20f709c1c4 --- /dev/null +++ b/core/src/main/java/org/jclouds/events/handlers/DeadEventLoggingHandler.java @@ -0,0 +1,52 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds licenses this file + * to you 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 org.jclouds.events.handlers; + +import javax.annotation.Resource; +import javax.inject.Singleton; + +import org.jclouds.logging.Logger; + +import com.google.common.eventbus.DeadEvent; +import com.google.common.eventbus.Subscribe; + +/** + * Default handler for dead events. + *

+ * It simply logs all dead events to allow debugging and troubleshooting. + * + * @author Ignasi Barrera + */ +@Singleton +public class DeadEventLoggingHandler +{ + @Resource + private Logger logger = Logger.NULL; + + /** + * Due to Guava Issue + * 786 {@link #handleDeadEvent(DeadEvent)} is marked finalto avoid having + * duplicate events. + */ + @Subscribe + public final void handleDeadEvent(DeadEvent deadEvent) { + logger.warn("detected dead event %s", deadEvent.getEvent()); + } +} diff --git a/core/src/main/java/org/jclouds/rest/RestContextBuilder.java b/core/src/main/java/org/jclouds/rest/RestContextBuilder.java index c77a721aa8..86f5c4f763 100644 --- a/core/src/main/java/org/jclouds/rest/RestContextBuilder.java +++ b/core/src/main/java/org/jclouds/rest/RestContextBuilder.java @@ -35,6 +35,8 @@ import org.jclouds.concurrent.MoreExecutors; import org.jclouds.concurrent.SingleThreaded; import org.jclouds.concurrent.config.ConfiguresExecutorService; import org.jclouds.concurrent.config.ExecutorServiceModule; +import org.jclouds.events.config.ConfiguresEventBus; +import org.jclouds.events.config.EventBusModule; import org.jclouds.http.RequiresHttp; import org.jclouds.http.config.ConfiguresHttpCommandExecutorService; import org.jclouds.http.config.JavaUrlHttpCommandExecutorServiceModule; @@ -109,6 +111,7 @@ public class RestContextBuilder { addHttpModuleIfNeededAndNotPresent(modules); ifHttpConfigureRestOtherwiseGuiceClientFactory(modules); addExecutorServiceIfNotPresent(modules); + addEventBusIfNotPresent(modules); addCredentialStoreIfNotPresent(modules); modules.add(new LifeCycleModule()); modules.add(new BindPropertiesToAnnotations()); @@ -211,6 +214,19 @@ public class RestContextBuilder { protected void addClientModule(List modules) { modules.add(new RestClientModule(syncClientType, asyncClientType)); } + + @VisibleForTesting + protected void addEventBusIfNotPresent(List modules) { + if (!any(modules, new Predicate() { + public boolean apply(Module input) { + return input.getClass().isAnnotationPresent(ConfiguresEventBus.class); + } + } + + )) { + modules.add(new EventBusModule()); + } + } @VisibleForTesting protected void addExecutorServiceIfNotPresent(List modules) { diff --git a/core/src/test/java/org/jclouds/events/config/EventBusModuleTest.java b/core/src/test/java/org/jclouds/events/config/EventBusModuleTest.java new file mode 100644 index 0000000000..5deafb7df6 --- /dev/null +++ b/core/src/test/java/org/jclouds/events/config/EventBusModuleTest.java @@ -0,0 +1,80 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds licenses this file + * to you 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 org.jclouds.events.config; + +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertTrue; + +import org.jclouds.Constants; +import org.jclouds.concurrent.config.ExecutorServiceModule; +import org.jclouds.events.config.annotations.AsyncBus; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import com.google.common.eventbus.AsyncEventBus; +import com.google.common.eventbus.EventBus; +import com.google.inject.Guice; +import com.google.inject.Injector; +import com.google.inject.Key; +import com.google.inject.name.Names; + +/** + * Unit tests for the {@link EventBusModule} class. + * + * @author Ignasi Barrera + */ +@Test(groups = "unit") +public class EventBusModuleTest +{ + private Injector injector; + + @BeforeMethod + public void setup() { + ExecutorServiceModule executorServiceModule = new ExecutorServiceModule() { + @Override + protected void configure() { + bindConstant().annotatedWith(Names.named(Constants.PROPERTY_IO_WORKER_THREADS)).to(1); + bindConstant().annotatedWith(Names.named(Constants.PROPERTY_USER_THREADS)).to(1); + super.configure(); + } + }; + EventBusModule eventBusModule = new EventBusModule(); + injector = Guice.createInjector(executorServiceModule, eventBusModule); + } + + public void testAsyncExecutorIsProvided() { + assertNotNull(injector.getInstance(AsyncEventBus.class)); + } + + public void testAsyncAnnotatedEventBusIsBound() { + Key eventBusKey = Key.get(EventBus.class, AsyncBus.class); + EventBus eventBus = injector.getInstance(eventBusKey); + + assertNotNull(eventBus); + assertTrue(eventBus instanceof AsyncEventBus); + } + + public void testEventBusIsSingleton() { + EventBus eventBus1 = injector.getInstance(EventBus.class); + EventBus eventBus2 = injector.getInstance(EventBus.class); + + assertTrue(eventBus1 == eventBus2); + } +} diff --git a/core/src/test/java/org/jclouds/rest/RestContextBuilderTest.java b/core/src/test/java/org/jclouds/rest/RestContextBuilderTest.java index a3a5b271ec..113be8c1cb 100644 --- a/core/src/test/java/org/jclouds/rest/RestContextBuilderTest.java +++ b/core/src/test/java/org/jclouds/rest/RestContextBuilderTest.java @@ -26,6 +26,7 @@ import java.util.List; import java.util.Properties; import org.jclouds.concurrent.config.ExecutorServiceModule; +import org.jclouds.events.config.EventBusModule; import org.jclouds.http.RequiresHttp; import org.jclouds.http.config.ConfiguresHttpCommandExecutorService; import org.jclouds.http.config.JavaUrlHttpCommandExecutorServiceModule; @@ -77,6 +78,17 @@ public class RestContextBuilderTest { assertEquals(modules.size(), 1); assertEquals(modules.remove(0), module); } + + @Test + public void testAddEventBusModuleIfNotPresent() { + List modules = new ArrayList(); + EventBusModule module = new EventBusModule(); + modules.add(module); + new RestContextBuilder(String.class, String.class, new Properties()) + .addEventBusIfNotPresent(modules); + assertEquals(modules.size(), 1); + assertEquals(modules.remove(0), module); + } @Test public void testAddExecutorServiceModuleIfNotPresent() {