From 4ced43566b7b0be3cc6eae7174386d312fd83e0d Mon Sep 17 00:00:00 2001 From: adriancole Date: Mon, 8 Apr 2013 13:51:14 -0700 Subject: [PATCH] issue #1501: allow Closeable, top-level apis to be used as opposed to RestContext --- .../main/java/org/jclouds/ContextBuilder.java | 15 ++++ .../DelegatesToInvocationFunction.java | 32 +++++++- .../annotationparsing/ClosableApiTest.java | 73 +++++++++++++++++++ 3 files changed, 116 insertions(+), 4 deletions(-) create mode 100644 core/src/test/java/org/jclouds/rest/annotationparsing/ClosableApiTest.java diff --git a/core/src/main/java/org/jclouds/ContextBuilder.java b/core/src/main/java/org/jclouds/ContextBuilder.java index 2d28474026..910afbe3e7 100644 --- a/core/src/main/java/org/jclouds/ContextBuilder.java +++ b/core/src/main/java/org/jclouds/ContextBuilder.java @@ -45,6 +45,7 @@ import static org.jclouds.Constants.PROPERTY_PROVIDER; import static org.jclouds.reflect.Reflection2.typeToken; import static org.jclouds.util.Throwables2.propagateAuthorizationOrOriginalException; +import java.io.Closeable; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; @@ -611,6 +612,20 @@ public class ContextBuilder { return (C) buildInjector().getInstance(Key.get(TypeLiteral.get(returnType.getType()))); } + /** + * This will return the top-level interface for the api or provider. + * + * Ex. + *
+    * api = ContextBuilder.newBuilder("openstack-nova")
+    *                     ... 
+    *                     .buildApi(NovaApi.class);
+    *
+ */ + public A buildApi(Class api) { + return buildInjector().getInstance(api); + } + public ApiMetadata getApiMetadata() { return apiMetadata; } diff --git a/core/src/main/java/org/jclouds/rest/internal/DelegatesToInvocationFunction.java b/core/src/main/java/org/jclouds/rest/internal/DelegatesToInvocationFunction.java index d1ff456cbf..014b9116f5 100644 --- a/core/src/main/java/org/jclouds/rest/internal/DelegatesToInvocationFunction.java +++ b/core/src/main/java/org/jclouds/rest/internal/DelegatesToInvocationFunction.java @@ -30,6 +30,7 @@ import static org.jclouds.util.Optionals2.unwrapIfOptional; import static org.jclouds.util.Throwables2.getFirstThrowableOfType; import static org.jclouds.util.Throwables2.propagateIfPossible; +import java.io.Closeable; import java.lang.annotation.Annotation; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; @@ -44,6 +45,7 @@ import javax.inject.Inject; import javax.inject.Qualifier; import org.jclouds.javax.annotation.Nullable; +import org.jclouds.lifecycle.Closer; import org.jclouds.reflect.FunctionalReflection; import org.jclouds.reflect.Invocation; import org.jclouds.reflect.InvocationSuccess; @@ -129,12 +131,34 @@ public final class DelegatesToInvocationFunction CLOSE; + + static { + try { + CLOSE = Invokable.from(Closeable.class.getMethod("close")); + } catch (SecurityException e) { + throw propagate(e); + } catch (NoSuchMethodException e) { + throw propagate(e); + } + } + protected Object handle(Invocation invocation) { - if (invocation.getInvokable().isAnnotationPresent(Provides.class)) - return lookupValueFromGuice(invocation.getInvokable()); - else if (invocation.getInvokable().isAnnotationPresent(Delegate.class)) + Invokable invokable = invocation.getInvokable(); + if (CLOSE.equals(invokable)) { + try { + injector.getInstance(Closer.class).close(); + return null; + } catch (Throwable e) { + throw propagate(e); + } + } else if (invokable.isAnnotationPresent(Provides.class)) { + return lookupValueFromGuice(invokable); + } else if (invokable.isAnnotationPresent(Delegate.class)) { return propagateContextToDelegate(invocation); - return methodInvoker.apply(invocation); + } else { + return methodInvoker.apply(invocation); + } } private final Injector injector; diff --git a/core/src/test/java/org/jclouds/rest/annotationparsing/ClosableApiTest.java b/core/src/test/java/org/jclouds/rest/annotationparsing/ClosableApiTest.java new file mode 100644 index 0000000000..febe4eaa89 --- /dev/null +++ b/core/src/test/java/org/jclouds/rest/annotationparsing/ClosableApiTest.java @@ -0,0 +1,73 @@ +/** + * 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.rest.annotationparsing; + +import static org.easymock.EasyMock.createMock; +import static org.easymock.EasyMock.expect; +import static org.easymock.EasyMock.replay; +import static org.easymock.EasyMock.verify; +import static org.jclouds.providers.AnonymousProviderMetadata.forClientMappedToAsyncClientOnEndpoint; + +import java.io.Closeable; +import java.io.IOException; + +import org.jclouds.ContextBuilder; +import org.jclouds.concurrent.config.ExecutorServiceModule; +import org.jclouds.providers.ProviderMetadata; +import org.testng.annotations.Test; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.common.util.concurrent.ListeningExecutorService; +import com.google.inject.Module; + +/** + * ensures that jclouds can be operated w/o reference to a context as the Api + * itself is closeable. + * + * @author Adrian Cole + */ +@Test(groups = "unit", testName = "ClosableApiTest") +public class ClosableApiTest { + + static interface DelegatingApi extends Closeable { + } + + static interface DelegatingAsyncApi extends Closeable { + } + + ProviderMetadata provider = forClientMappedToAsyncClientOnEndpoint(DelegatingApi.class, DelegatingAsyncApi.class, + "http://mock"); + + public void testApiClosesExecutorServiceOnClose() throws IOException { + ListeningExecutorService executor = createMock(ListeningExecutorService.class); + + expect(executor.shutdownNow()).andReturn(ImmutableList. of()).atLeastOnce(); + + replay(executor); + + DelegatingApi api = ContextBuilder.newBuilder(provider) + .modules(ImmutableSet. builder() + .add(new ExecutorServiceModule(executor, executor)) + .build()) + .buildApi(DelegatingApi.class); + api.close(); + verify(executor); + } +}