getSupportedVersions();
}
diff --git a/apis/vcloud/src/main/java/org/jclouds/vcloud/internal/VCloudLoginAsyncClient.java b/apis/vcloud/src/main/java/org/jclouds/vcloud/internal/VCloudLoginAsyncClient.java
index 0081e0e732..e24f241a6d 100644
--- a/apis/vcloud/src/main/java/org/jclouds/vcloud/internal/VCloudLoginAsyncClient.java
+++ b/apis/vcloud/src/main/java/org/jclouds/vcloud/internal/VCloudLoginAsyncClient.java
@@ -18,6 +18,8 @@
*/
package org.jclouds.vcloud.internal;
+import java.io.Closeable;
+
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
@@ -40,7 +42,7 @@ import com.google.common.util.concurrent.ListenableFuture;
*/
@Endpoint(org.jclouds.vcloud.endpoints.VCloudLogin.class)
@RequestFilters(BasicAuthentication.class)
-public interface VCloudLoginAsyncClient {
+public interface VCloudLoginAsyncClient extends Closeable {
/**
* This request returns a token to use in subsequent requests. After 30 minutes of inactivity,
diff --git a/apis/vcloud/src/main/java/org/jclouds/vcloud/internal/VCloudLoginClient.java b/apis/vcloud/src/main/java/org/jclouds/vcloud/internal/VCloudLoginClient.java
index 7c383cc2f2..1bafb9b86e 100644
--- a/apis/vcloud/src/main/java/org/jclouds/vcloud/internal/VCloudLoginClient.java
+++ b/apis/vcloud/src/main/java/org/jclouds/vcloud/internal/VCloudLoginClient.java
@@ -18,9 +18,11 @@
*/
package org.jclouds.vcloud.internal;
+import java.io.Closeable;
+
import org.jclouds.vcloud.domain.VCloudSession;
-public interface VCloudLoginClient {
+public interface VCloudLoginClient extends Closeable {
VCloudSession login();
}
diff --git a/common/trmk/src/main/java/org/jclouds/trmk/vcloud_0_8/TerremarkVCloudAsyncClient.java b/common/trmk/src/main/java/org/jclouds/trmk/vcloud_0_8/TerremarkVCloudAsyncClient.java
index b9ecf9b0cc..4323124637 100644
--- a/common/trmk/src/main/java/org/jclouds/trmk/vcloud_0_8/TerremarkVCloudAsyncClient.java
+++ b/common/trmk/src/main/java/org/jclouds/trmk/vcloud_0_8/TerremarkVCloudAsyncClient.java
@@ -34,6 +34,7 @@ import static org.jclouds.trmk.vcloud_0_8.TerremarkVCloudMediaType.VAPPTEMPLATE_
import static org.jclouds.trmk.vcloud_0_8.TerremarkVCloudMediaType.VAPP_XML;
import static org.jclouds.trmk.vcloud_0_8.TerremarkVCloudMediaType.VDC_XML;
+import java.io.Closeable;
import java.net.URI;
import java.util.Map;
import java.util.Set;
@@ -124,7 +125,7 @@ import com.google.inject.Provides;
* @author Adrian Cole
*/
@RequestFilters(SetVCloudTokenCookie.class)
-public interface TerremarkVCloudAsyncClient {
+public interface TerremarkVCloudAsyncClient extends Closeable {
/**
* @see TerremarkVCloudClient#getCatalogItemInOrg
diff --git a/common/trmk/src/main/java/org/jclouds/trmk/vcloud_0_8/TerremarkVCloudClient.java b/common/trmk/src/main/java/org/jclouds/trmk/vcloud_0_8/TerremarkVCloudClient.java
index 004fe55efb..64cc02700e 100644
--- a/common/trmk/src/main/java/org/jclouds/trmk/vcloud_0_8/TerremarkVCloudClient.java
+++ b/common/trmk/src/main/java/org/jclouds/trmk/vcloud_0_8/TerremarkVCloudClient.java
@@ -18,6 +18,7 @@
*/
package org.jclouds.trmk.vcloud_0_8;
+import java.io.Closeable;
import java.net.URI;
import java.util.Map;
import java.util.Set;
@@ -55,7 +56,7 @@ import com.google.inject.Provides;
* />
* @author Adrian Cole
*/
-public interface TerremarkVCloudClient {
+public interface TerremarkVCloudClient extends Closeable {
Catalog getCatalog(URI catalogId);
/**
diff --git a/common/trmk/src/main/java/org/jclouds/trmk/vcloud_0_8/internal/TerremarkVCloudLoginAsyncClient.java b/common/trmk/src/main/java/org/jclouds/trmk/vcloud_0_8/internal/TerremarkVCloudLoginAsyncClient.java
index e63b922380..e1f5a3bae6 100644
--- a/common/trmk/src/main/java/org/jclouds/trmk/vcloud_0_8/internal/TerremarkVCloudLoginAsyncClient.java
+++ b/common/trmk/src/main/java/org/jclouds/trmk/vcloud_0_8/internal/TerremarkVCloudLoginAsyncClient.java
@@ -18,6 +18,8 @@
*/
package org.jclouds.trmk.vcloud_0_8.internal;
+import java.io.Closeable;
+
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
@@ -40,7 +42,7 @@ import com.google.common.util.concurrent.ListenableFuture;
*/
@Endpoint(org.jclouds.trmk.vcloud_0_8.endpoints.VCloudLogin.class)
@RequestFilters(BasicAuthentication.class)
-public interface TerremarkVCloudLoginAsyncClient {
+public interface TerremarkVCloudLoginAsyncClient extends Closeable {
/**
* This request returns a token to use in subsequent requests. After ten minutes of inactivity,
diff --git a/common/trmk/src/main/java/org/jclouds/trmk/vcloud_0_8/internal/TerremarkVCloudLoginClient.java b/common/trmk/src/main/java/org/jclouds/trmk/vcloud_0_8/internal/TerremarkVCloudLoginClient.java
index 95328361f9..deee35c515 100644
--- a/common/trmk/src/main/java/org/jclouds/trmk/vcloud_0_8/internal/TerremarkVCloudLoginClient.java
+++ b/common/trmk/src/main/java/org/jclouds/trmk/vcloud_0_8/internal/TerremarkVCloudLoginClient.java
@@ -18,9 +18,11 @@
*/
package org.jclouds.trmk.vcloud_0_8.internal;
+import java.io.Closeable;
+
import org.jclouds.trmk.vcloud_0_8.domain.VCloudSession;
-public interface TerremarkVCloudLoginClient {
+public interface TerremarkVCloudLoginClient extends Closeable {
VCloudSession login();
}
diff --git a/common/trmk/src/main/java/org/jclouds/trmk/vcloud_0_8/internal/TerremarkVCloudVersionsAsyncClient.java b/common/trmk/src/main/java/org/jclouds/trmk/vcloud_0_8/internal/TerremarkVCloudVersionsAsyncClient.java
index a4ecb1b37a..9e861cb51c 100644
--- a/common/trmk/src/main/java/org/jclouds/trmk/vcloud_0_8/internal/TerremarkVCloudVersionsAsyncClient.java
+++ b/common/trmk/src/main/java/org/jclouds/trmk/vcloud_0_8/internal/TerremarkVCloudVersionsAsyncClient.java
@@ -18,6 +18,7 @@
*/
package org.jclouds.trmk.vcloud_0_8.internal;
+import java.io.Closeable;
import java.net.URI;
import java.util.SortedMap;
@@ -36,7 +37,7 @@ import com.google.common.util.concurrent.ListenableFuture;
* @see
* @author Adrian Cole
*/
-public interface TerremarkVCloudVersionsAsyncClient {
+public interface TerremarkVCloudVersionsAsyncClient extends Closeable {
/**
* Retrieve information for supported versions
diff --git a/common/trmk/src/main/java/org/jclouds/trmk/vcloud_0_8/internal/TerremarkVCloudVersionsClient.java b/common/trmk/src/main/java/org/jclouds/trmk/vcloud_0_8/internal/TerremarkVCloudVersionsClient.java
index 5e49e158a0..d9badbdb17 100644
--- a/common/trmk/src/main/java/org/jclouds/trmk/vcloud_0_8/internal/TerremarkVCloudVersionsClient.java
+++ b/common/trmk/src/main/java/org/jclouds/trmk/vcloud_0_8/internal/TerremarkVCloudVersionsClient.java
@@ -18,9 +18,10 @@
*/
package org.jclouds.trmk.vcloud_0_8.internal;
+import java.io.Closeable;
import java.net.URI;
import java.util.SortedMap;
-public interface TerremarkVCloudVersionsClient {
+public interface TerremarkVCloudVersionsClient extends Closeable {
SortedMap getSupportedVersions();
}
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/config/RestModule.java b/core/src/main/java/org/jclouds/rest/config/RestModule.java
index e4819c8b44..db7f67b07a 100644
--- a/core/src/main/java/org/jclouds/rest/config/RestModule.java
+++ b/core/src/main/java/org/jclouds/rest/config/RestModule.java
@@ -27,6 +27,7 @@ import static org.jclouds.reflect.Reflection2.method;
import static org.jclouds.reflect.Reflection2.methods;
import static org.jclouds.rest.config.BinderUtils.bindHttpApi;
+import java.io.Closeable;
import java.net.Proxy;
import java.net.URI;
import java.util.Map;
@@ -120,12 +121,27 @@ public class RestModule extends AbstractModule {
public static void putInvokables(Class> sync, Class> async, Cache, Invokable, ?>> cache) {
for (Invokable, ?> invoked : methods(sync)) {
Invokable, ?> delegatedMethod = method(async, invoked.getName(), getParameterTypes(invoked));
- checkArgument(delegatedMethod.getExceptionTypes().equals(invoked.getExceptionTypes()),
+ checkArgument(
+ delegatedMethod.getExceptionTypes().equals(invoked.getExceptionTypes()) || isCloseable(delegatedMethod),
"invoked %s has different typed exceptions than target %s", invoked, delegatedMethod);
cache.put(invoked, delegatedMethod);
}
}
+ /**
+ * In JDK7 Closeable.close is declared in AutoCloseable, which throws
+ * Exception vs IOException, so we have to be more lenient about exception
+ * type declarations.
+ *
+ * note
+ *
+ * This will be refactored out when we delete Async code in jclouds 1.7.
+ */
+ private static boolean isCloseable(Invokable, ?> delegatedMethod) {
+ return "close".equals(delegatedMethod.getName())
+ && Closeable.class.isAssignableFrom(delegatedMethod.getDeclaringClass());
+ }
+
/**
* for portability with {@link Class#getMethod(String, Class...)}
*/
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/http/IntegrationTestAsyncClient.java b/core/src/test/java/org/jclouds/http/IntegrationTestAsyncClient.java
index 17b9d2462f..07dbfcd3f7 100644
--- a/core/src/test/java/org/jclouds/http/IntegrationTestAsyncClient.java
+++ b/core/src/test/java/org/jclouds/http/IntegrationTestAsyncClient.java
@@ -20,6 +20,7 @@ package org.jclouds.http;
import static com.google.common.util.concurrent.Futures.immediateFuture;
+import java.io.Closeable;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -61,7 +62,7 @@ import com.google.inject.Provides;
* @see IntegrationTestClient
* @author Adrian Cole
*/
-public interface IntegrationTestAsyncClient {
+public interface IntegrationTestAsyncClient extends Closeable {
@Target({ ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@HttpMethod("ROWDY")
diff --git a/core/src/test/java/org/jclouds/http/IntegrationTestClient.java b/core/src/test/java/org/jclouds/http/IntegrationTestClient.java
index 551857977f..822abb12de 100644
--- a/core/src/test/java/org/jclouds/http/IntegrationTestClient.java
+++ b/core/src/test/java/org/jclouds/http/IntegrationTestClient.java
@@ -18,6 +18,8 @@
*/
package org.jclouds.http;
+import java.io.Closeable;
+
import org.jclouds.http.options.HttpRequestOptions;
import org.jclouds.io.Payload;
@@ -29,7 +31,7 @@ import com.google.inject.Provides;
*
* @author Adrian Cole
*/
-public interface IntegrationTestClient {
+public interface IntegrationTestClient extends Closeable {
String rowdy(String path);
boolean exists(String path);
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);
+ }
+}
diff --git a/core/src/test/java/org/jclouds/rest/annotationparsing/JAXBResponseParserAnnotationExpectTest.java b/core/src/test/java/org/jclouds/rest/annotationparsing/JAXBResponseParserAnnotationExpectTest.java
index f4a918cf12..04e83200d2 100644
--- a/core/src/test/java/org/jclouds/rest/annotationparsing/JAXBResponseParserAnnotationExpectTest.java
+++ b/core/src/test/java/org/jclouds/rest/annotationparsing/JAXBResponseParserAnnotationExpectTest.java
@@ -22,6 +22,8 @@ import static org.jclouds.providers.AnonymousProviderMetadata.forClientMappedToA
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertTrue;
+import java.io.Closeable;
+
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
@@ -68,7 +70,7 @@ public class JAXBResponseParserAnnotationExpectTest extends
}
- public interface TestJAXBApi {
+ public interface TestJAXBApi extends Closeable {
public TestJAXBDomain jaxbGetWithAnnotation();
public Object jaxbGetWithAnnotationAndCustomClass();
@@ -78,7 +80,7 @@ public class JAXBResponseParserAnnotationExpectTest extends
public String jaxbGetWithTransformer();
}
- public interface TestJAXBAsyncApi {
+ public interface TestJAXBAsyncApi extends Closeable {
@GET
@Path("/jaxb/annotation")
@JAXBResponseParser
diff --git a/core/src/test/java/org/jclouds/rest/annotationparsing/ProvidesAnnotationExpectTest.java b/core/src/test/java/org/jclouds/rest/annotationparsing/ProvidesAnnotationExpectTest.java
index 88aab9d98b..d2056c598a 100644
--- a/core/src/test/java/org/jclouds/rest/annotationparsing/ProvidesAnnotationExpectTest.java
+++ b/core/src/test/java/org/jclouds/rest/annotationparsing/ProvidesAnnotationExpectTest.java
@@ -21,6 +21,7 @@ package org.jclouds.rest.annotationparsing;
import static org.jclouds.providers.AnonymousProviderMetadata.forClientMappedToAsyncClientOnEndpoint;
import static org.testng.Assert.assertEquals;
+import java.io.Closeable;
import java.util.NoSuchElementException;
import java.util.Set;
@@ -50,7 +51,7 @@ import com.google.inject.name.Names;
@Test(groups = "unit", testName = "ProvidesAnnotationExpectTest")
public class ProvidesAnnotationExpectTest extends BaseRestClientExpectTest {
- static interface ProvidingApi {
+ static interface ProvidingApi extends Closeable {
@Provides
Set set();
@@ -67,7 +68,7 @@ public class ProvidesAnnotationExpectTest extends BaseRestClientExpectTest noSuchElementException();
}
- static interface ProvidingAsyncApi {
+ static interface ProvidingAsyncApi extends Closeable {
@Provides
Set set();
diff --git a/core/src/test/java/org/jclouds/rest/internal/RestAnnotationProcessorTest.java b/core/src/test/java/org/jclouds/rest/internal/RestAnnotationProcessorTest.java
index 9134fc691b..4986c4c270 100644
--- a/core/src/test/java/org/jclouds/rest/internal/RestAnnotationProcessorTest.java
+++ b/core/src/test/java/org/jclouds/rest/internal/RestAnnotationProcessorTest.java
@@ -28,6 +28,7 @@ import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNull;
import static org.testng.Assert.fail;
+import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
@@ -175,7 +176,7 @@ public class RestAnnotationProcessorTest extends BaseRestApiTest {
}
@Path("/client/{jclouds.api-version}")
- public static interface AsyncCallee {
+ public static interface AsyncCallee extends Closeable {
@GET
@Path("/{path}")
ListenableFuture onePath(@PathParam("path") String path);
@@ -189,7 +190,7 @@ public class RestAnnotationProcessorTest extends BaseRestApiTest {
}
@Endpoint(Localhost2.class)
- public static interface Caller {
+ public static interface Caller extends Closeable {
// tests that we can pull from suppliers
@Provides
@@ -213,7 +214,7 @@ public class RestAnnotationProcessorTest extends BaseRestApiTest {
public Callee getCalleeWithPath(@EndpointParam URI endpoint, @PathParam("wibble") String wibble);
}
- public static interface Callee {
+ public static interface Callee extends Closeable {
void onePath(String path);
}
@@ -221,7 +222,7 @@ public class RestAnnotationProcessorTest extends BaseRestApiTest {
void onePath(String path);
}
- public static interface AsyncCaller {
+ public static interface AsyncCaller extends Closeable {
@Provides
@Localhost2
URI getURI();