Issue 870: added more context to optionalconverter

This commit is contained in:
Adrian Cole 2012-03-16 02:30:19 -07:00
parent 16a44b2e02
commit 3d0d0c6094
12 changed files with 315 additions and 86 deletions

View File

@ -34,6 +34,7 @@ import javax.inject.Named;
import org.jclouds.concurrent.Timeout; import org.jclouds.concurrent.Timeout;
import org.jclouds.internal.ClassMethodArgs; import org.jclouds.internal.ClassMethodArgs;
import org.jclouds.internal.ClassMethodArgsAndReturnVal;
import org.jclouds.rest.annotations.Delegate; import org.jclouds.rest.annotations.Delegate;
import org.jclouds.util.Optionals2; import org.jclouds.util.Optionals2;
import org.jclouds.util.Throwables2; import org.jclouds.util.Throwables2;
@ -54,7 +55,7 @@ import com.google.inject.ProvisionException;
public class SyncProxy implements InvocationHandler { public class SyncProxy implements InvocationHandler {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public static <T> T proxy(Function<Object, Optional<Object>> optionalConverter, Class<T> clazz, Object async, public static <T> T proxy(Function<ClassMethodArgsAndReturnVal, Optional<Object>> optionalConverter, Class<T> clazz, Object async,
@Named("sync") LoadingCache<ClassMethodArgs, Object> delegateMap, @Named("sync") LoadingCache<ClassMethodArgs, Object> delegateMap,
Map<Class<?>, Class<?>> sync2Async, Map<String, Long> timeouts) throws IllegalArgumentException, SecurityException, Map<Class<?>, Class<?>> sync2Async, Map<String, Long> timeouts) throws IllegalArgumentException, SecurityException,
NoSuchMethodException { NoSuchMethodException {
@ -62,7 +63,7 @@ public class SyncProxy implements InvocationHandler {
new SyncProxy(optionalConverter, clazz, async, delegateMap, sync2Async, timeouts)); new SyncProxy(optionalConverter, clazz, async, delegateMap, sync2Async, timeouts));
} }
private final Function<Object, Optional<Object>> optionalConverter; private final Function<ClassMethodArgsAndReturnVal, Optional<Object>> optionalConverter;
private final Object delegate; private final Object delegate;
private final Class<?> declaring; private final Class<?> declaring;
private final Map<Method, Method> methodMap; private final Map<Method, Method> methodMap;
@ -73,7 +74,7 @@ public class SyncProxy implements InvocationHandler {
private static final Set<Method> objectMethods = ImmutableSet.copyOf(Object.class.getMethods()); private static final Set<Method> objectMethods = ImmutableSet.copyOf(Object.class.getMethods());
@Inject @Inject
private SyncProxy(Function<Object, Optional<Object>> optionalConverter, Class<?> declaring, Object async, private SyncProxy(Function<ClassMethodArgsAndReturnVal, Optional<Object>> optionalConverter, Class<?> declaring, Object async,
@Named("sync") LoadingCache<ClassMethodArgs, Object> delegateMap, Map<Class<?>, @Named("sync") LoadingCache<ClassMethodArgs, Object> delegateMap, Map<Class<?>,
Class<?>> sync2Async, final Map<String, Long> timeouts) Class<?>> sync2Async, final Map<String, Long> timeouts)
throws SecurityException, NoSuchMethodException { throws SecurityException, NoSuchMethodException {
@ -107,6 +108,10 @@ public class SyncProxy implements InvocationHandler {
} }
} }
public Class<?> getDeclaring() {
return declaring;
}
private Long getTimeout(Method method, long typeNanos, final Map<String,Long> timeouts) { private Long getTimeout(Method method, long typeNanos, final Map<String,Long> timeouts) {
Long timeout = overrideTimeout(method, timeouts); Long timeout = overrideTimeout(method, timeouts);
if (timeout == null && method.isAnnotationPresent(Timeout.class)) { if (timeout == null && method.isAnnotationPresent(Timeout.class)) {
@ -139,9 +144,12 @@ public class SyncProxy implements InvocationHandler {
// pass any parameters necessary to get a relevant instance of that async class // pass any parameters necessary to get a relevant instance of that async class
// ex. getClientForRegion("north") might return an instance whose endpoint is // ex. getClientForRegion("north") might return an instance whose endpoint is
// different that "south" // different that "south"
Object returnVal = delegateMap.get(new ClassMethodArgs(asyncClass, method, args)); ClassMethodArgs cma = new ClassMethodArgs(asyncClass, method, args);
Object returnVal = delegateMap.get(cma);
if (Optionals2.isReturnTypeOptional(method)){ if (Optionals2.isReturnTypeOptional(method)){
return optionalConverter.apply(returnVal); ClassMethodArgsAndReturnVal cmar = ClassMethodArgsAndReturnVal.builder().fromClassMethodArgs(cma)
.returnVal(returnVal).build();
return optionalConverter.apply(cmar);
} }
return returnVal; return returnVal;
} else if (syncMethodMap.containsKey(method)) { } else if (syncMethodMap.containsKey(method)) {

View File

@ -18,32 +18,91 @@
*/ */
package org.jclouds.internal; package org.jclouds.internal;
import static com.google.common.base.Objects.equal;
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.Arrays;
import org.jclouds.javax.annotation.Nullable; import org.jclouds.javax.annotation.Nullable;
import com.google.common.base.Objects;
import com.google.common.base.Objects.ToStringHelper;
/** /**
* *
* @author Adrian Cole * @author Adrian Cole
*/ */
public class ClassMethodArgs { public class ClassMethodArgs {
public static Builder<?> builder() {
return new ConcreteBuilder();
}
public Builder<?> toBuilder() {
return builder().fromClassMethodArgs(this);
}
private static class ConcreteBuilder extends Builder<ConcreteBuilder> {
}
public static abstract class Builder<B extends Builder<B>> {
private Class<?> clazz;
private Method method;
private Object[] args;
@SuppressWarnings("unchecked")
protected B self() {
return (B) this;
}
/**
* @see ClassMethodArgs#getClazz()
*/
public B clazz(Class<?> clazz) {
this.clazz = clazz;
return self();
}
/**
* @see ClassMethodArgs#getMethod()
*/
public B method(Method method) {
this.method = method;
return self();
}
/**
* @see ClassMethodArgs#getArgs()
*/
public B args(Object[] args) {
this.args = args;
return self();
}
public ClassMethodArgs build() {
return new ClassMethodArgs(this);
}
public B fromClassMethodArgs(ClassMethodArgs in) {
return clazz(in.getClazz()).method(in.getMethod()).args(in.getArgs());
}
}
private final Class<?> clazz;
private final Method method; private final Method method;
private final Object[] args; private final Object[] args;
private final Class<?> asyncClass;
public ClassMethodArgs(Class<?> asyncClass, Method method, @Nullable Object[] args) { public ClassMethodArgs(Builder<?> builder) {
this.asyncClass = checkNotNull(asyncClass, "asyncClass"); this(builder.clazz, builder.method, builder.args);
}
public ClassMethodArgs(Class<?> clazz, Method method, @Nullable Object[] args) {
this.clazz = checkNotNull(clazz, "clazz");
this.method = checkNotNull(method, "method"); this.method = checkNotNull(method, "method");
this.args = args; this.args = args;
} }
@Override public Class<?> getClazz() {
public String toString() { return clazz;
return "[class=" + asyncClass.getSimpleName() + ", method=" + method.getName() + ", args="
+ Arrays.toString(args) + "]";
} }
public Method getMethod() { public Method getMethod() {
@ -55,40 +114,26 @@ public class ClassMethodArgs {
} }
@Override @Override
public int hashCode() { public boolean equals(Object o) {
final int prime = 31; if (this == o)
int result = 1; return true;
result = prime * result + Arrays.hashCode(args); if (o == null || getClass() != o.getClass())
result = prime * result + ((asyncClass == null) ? 0 : asyncClass.hashCode()); return false;
result = prime * result + ((method == null) ? 0 : method.hashCode()); ClassMethodArgs that = ClassMethodArgs.class.cast(o);
return result; return equal(this.clazz, that.clazz) && equal(this.method, that.method) && equal(this.args, that.args);
} }
@Override @Override
public boolean equals(Object obj) { public int hashCode() {
if (this == obj) return Objects.hashCode(clazz, method, args);
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
ClassMethodArgs other = (ClassMethodArgs) obj;
if (!Arrays.equals(args, other.args))
return false;
if (asyncClass == null) {
if (other.asyncClass != null)
return false;
} else if (!asyncClass.equals(other.asyncClass))
return false;
if (method == null) {
if (other.method != null)
return false;
} else if (!method.equals(other.method))
return false;
return true;
} }
public Class<?> getAsyncClass() { @Override
return asyncClass; public String toString() {
return string().toString();
}
protected ToStringHelper string() {
return Objects.toStringHelper("").add("clazz", clazz).add("method", method).add("args", args);
} }
} }

View File

@ -0,0 +1,105 @@
/**
* 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.internal;
import static com.google.common.base.Objects.equal;
import java.lang.reflect.Method;
import org.jclouds.javax.annotation.Nullable;
import com.google.common.base.Objects;
import com.google.common.base.Objects.ToStringHelper;
/**
*
* @author Adrian Cole
*/
public class ClassMethodArgsAndReturnVal extends ClassMethodArgs {
public static Builder<?> builder() {
return new ConcreteBuilder();
}
public Builder<?> toBuilder() {
return builder().fromClassMethodArgsAndReturnVal(this);
}
public static class Builder<B extends Builder<B>> extends ClassMethodArgs.Builder<B> {
private Object returnVal;
/**
* @see ClassMethodArgsAndReturnVal#getReturnVal()
*/
public B returnVal(Object returnVal) {
this.returnVal = returnVal;
return self();
}
@Override
public ClassMethodArgsAndReturnVal build() {
return new ClassMethodArgsAndReturnVal(this);
}
public B fromClassMethodArgsAndReturnVal(ClassMethodArgsAndReturnVal in) {
return fromClassMethodArgs(in).returnVal(in.getReturnVal());
}
}
private static class ConcreteBuilder extends Builder<ConcreteBuilder> {
}
private final Object returnVal;
public ClassMethodArgsAndReturnVal(Class<?> clazz, Method method, @Nullable Object[] args, Object returnVal) {
super(clazz, method, args);
this.returnVal = returnVal;
}
public ClassMethodArgsAndReturnVal(Builder<?> builder) {
super(builder);
this.returnVal = builder.returnVal;
}
public Object getReturnVal() {
return returnVal;
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
ClassMethodArgsAndReturnVal that = ClassMethodArgsAndReturnVal.class.cast(o);
return super.equals(that) && equal(this.returnVal, that.returnVal);
}
@Override
public int hashCode() {
return Objects.hashCode(super.hashCode(), returnVal);
}
@Override
public ToStringHelper string() {
return super.string().add("returnVal", returnVal);
}
}

View File

@ -25,6 +25,7 @@ import javax.inject.Singleton;
import org.jclouds.concurrent.internal.SyncProxy; import org.jclouds.concurrent.internal.SyncProxy;
import org.jclouds.internal.ClassMethodArgs; import org.jclouds.internal.ClassMethodArgs;
import org.jclouds.internal.ClassMethodArgsAndReturnVal;
import com.google.common.base.Function; import com.google.common.base.Function;
import com.google.common.base.Optional; import com.google.common.base.Optional;
@ -61,7 +62,7 @@ public class ClientProvider<S, A> implements Provider<S> {
@Singleton @Singleton
public S get() { public S get() {
A client = (A) injector.getInstance(Key.get(asyncClientType)); A client = (A) injector.getInstance(Key.get(asyncClientType));
Function<Object, Optional<Object>> optionalConverter = injector.getInstance(Key.get(new TypeLiteral<Function<Object, Optional<Object>>>() { Function<ClassMethodArgsAndReturnVal, Optional<Object>> optionalConverter = injector.getInstance(Key.get(new TypeLiteral<Function<ClassMethodArgsAndReturnVal, Optional<Object>>>() {
})); }));
LoadingCache<ClassMethodArgs, Object> delegateMap = injector.getInstance(Key.get( LoadingCache<ClassMethodArgs, Object> delegateMap = injector.getInstance(Key.get(
new TypeLiteral<LoadingCache<ClassMethodArgs, Object>>() { new TypeLiteral<LoadingCache<ClassMethodArgs, Object>>() {

View File

@ -27,19 +27,20 @@ import javax.inject.Inject;
import javax.inject.Named; import javax.inject.Named;
import javax.inject.Provider; import javax.inject.Provider;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.TypeLiteral;
import com.google.inject.name.Names;
import org.jclouds.concurrent.internal.SyncProxy; import org.jclouds.concurrent.internal.SyncProxy;
import org.jclouds.internal.ClassMethodArgs; import org.jclouds.internal.ClassMethodArgs;
import org.jclouds.internal.ClassMethodArgsAndReturnVal;
import org.jclouds.util.Optionals2; import org.jclouds.util.Optionals2;
import com.google.common.base.Function; import com.google.common.base.Function;
import com.google.common.base.Optional; import com.google.common.base.Optional;
import com.google.common.base.Throwables; import com.google.common.base.Throwables;
import com.google.common.cache.LoadingCache;
import com.google.common.cache.CacheLoader; import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.TypeLiteral;
import com.google.inject.name.Names;
/** /**
* CreateClientForCaller is parameterized, so clients it creates aren't singletons. For example, * CreateClientForCaller is parameterized, so clients it creates aren't singletons. For example,
@ -69,7 +70,7 @@ public class CreateClientForCaller extends CacheLoader<ClassMethodArgs, Object>
checkState(asyncClass != null, "configuration error, sync class " + syncClass + " not mapped to an async class"); checkState(asyncClass != null, "configuration error, sync class " + syncClass + " not mapped to an async class");
Object asyncClient = asyncMap.get(from); Object asyncClient = asyncMap.get(from);
checkState(asyncClient != null, "configuration error, sync client for " + from + " not found"); checkState(asyncClient != null, "configuration error, sync client for " + from + " not found");
Function<Object, Optional<Object>> optionalConverter = injector.getInstance(Key.get(new TypeLiteral<Function<Object, Optional<Object>>>() { Function<ClassMethodArgsAndReturnVal, Optional<Object>> optionalConverter = injector.getInstance(Key.get(new TypeLiteral<Function<ClassMethodArgsAndReturnVal, Optional<Object>>>() {
})); }));
Map<String, Long> timeoutsMap = injector.getInstance(Key.get(new TypeLiteral<Map<String, Long>>() { Map<String, Long> timeoutsMap = injector.getInstance(Key.get(new TypeLiteral<Map<String, Long>>() {
}, Names.named("TIMEOUTS"))); }, Names.named("TIMEOUTS")));

View File

@ -27,6 +27,7 @@ import javax.inject.Singleton;
import org.jclouds.http.RequiresHttp; import org.jclouds.http.RequiresHttp;
import org.jclouds.internal.ClassMethodArgs; import org.jclouds.internal.ClassMethodArgs;
import org.jclouds.internal.ClassMethodArgsAndReturnVal;
import org.jclouds.location.config.LocationModule; import org.jclouds.location.config.LocationModule;
import org.jclouds.rest.AuthorizationException; import org.jclouds.rest.AuthorizationException;
import org.jclouds.rest.ConfiguresRestClient; import org.jclouds.rest.ConfiguresRestClient;
@ -80,7 +81,7 @@ public class RestClientModule<S, A> extends AbstractModule {
@SuppressWarnings({ "unchecked", "rawtypes" }) @SuppressWarnings({ "unchecked", "rawtypes" })
@Override @Override
protected void configure() { protected void configure() {
bind(new TypeLiteral<Function<Object, Optional<Object>>>(){}).to(ImplicitOptionalConverter.class); bind(new TypeLiteral<Function<ClassMethodArgsAndReturnVal, Optional<Object>>>(){}).to(ImplicitOptionalConverter.class);
// this will help short circuit scenarios that can otherwise lock out users // this will help short circuit scenarios that can otherwise lock out users
bind(new TypeLiteral<AtomicReference<AuthorizationException>>(){}).toInstance(authException); bind(new TypeLiteral<AtomicReference<AuthorizationException>>(){}).toInstance(authException);
// Ensures the restcontext can be looked up without generic types. // Ensures the restcontext can be looked up without generic types.

View File

@ -40,9 +40,9 @@ import org.jclouds.rest.internal.RestAnnotationProcessor;
import org.jclouds.rest.internal.SeedAnnotationCache; import org.jclouds.rest.internal.SeedAnnotationCache;
import com.google.common.base.Function; import com.google.common.base.Function;
import com.google.common.cache.LoadingCache;
import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader; import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.google.inject.AbstractModule; import com.google.inject.AbstractModule;
import com.google.inject.Inject; import com.google.inject.Inject;
@ -101,7 +101,7 @@ public class RestModule extends AbstractModule {
@SuppressWarnings( { "unchecked", "rawtypes" }) @SuppressWarnings( { "unchecked", "rawtypes" })
@Override @Override
public Object load(final ClassMethodArgs from) { public Object load(final ClassMethodArgs from) {
Class clazz = from.getAsyncClass(); Class clazz = from.getClazz();
TypeLiteral typeLiteral = TypeLiteral.get(clazz); TypeLiteral typeLiteral = TypeLiteral.get(clazz);
RestAnnotationProcessor util = (RestAnnotationProcessor) injector.getInstance(Key.get(TypeLiteral.get(Types RestAnnotationProcessor util = (RestAnnotationProcessor) injector.getInstance(Key.get(TypeLiteral.get(Types
.newParameterizedType(RestAnnotationProcessor.class, clazz)))); .newParameterizedType(RestAnnotationProcessor.class, clazz))));

View File

@ -18,17 +18,21 @@
*/ */
package org.jclouds.rest.functions; package org.jclouds.rest.functions;
import org.jclouds.internal.ClassMethodArgsAndReturnVal;
import com.google.common.annotations.Beta;
import com.google.common.base.Optional; import com.google.common.base.Optional;
/** /**
* *
* @author Adrian Cole * @author Adrian Cole
*/ */
@Beta
public class AlwaysPresentImplicitOptionalConverter implements ImplicitOptionalConverter { public class AlwaysPresentImplicitOptionalConverter implements ImplicitOptionalConverter {
@Override @Override
public Optional<Object> apply(Object input) { public Optional<Object> apply(ClassMethodArgsAndReturnVal input) {
return Optional.of(input); return Optional.of(input.getReturnVal());
} }
} }

View File

@ -18,15 +18,66 @@
*/ */
package org.jclouds.rest.functions; package org.jclouds.rest.functions;
import org.jclouds.internal.ClassMethodArgsAndReturnVal;
import org.jclouds.rest.config.RestClientModule;
import com.google.common.annotations.Beta;
import com.google.common.base.Function; import com.google.common.base.Function;
import com.google.common.base.Optional; import com.google.common.base.Optional;
import com.google.inject.ImplementedBy; import com.google.inject.ImplementedBy;
/** /**
* When a client marked @Delegate is optional, the implementation of this is
* responsible for creating the optional object.
*
* For example.
*
* <pre>
* interface MyCloud {
* &#064;Delegate
* Optional&lt;KeyPairClient&gt; getKeyPairExtensionForRegion(String region);
* }
* </pre>
*
* The input object of type {@link ClassMethodArgsAndReturnVal} will include the
* following.
* <ol>
* <li>the class declaring the method that returns optional:
* {@link ClassMethodArgsAndReturnVal#getClazz}; in the example above,
* {@code MyCloud}</li>
* <li>the method returning the optional:
* {@link ClassMethodArgsAndReturnVal#getMethod}; in the example above,
* {@code getKeyPairExtensionForRegion}</li>
* <li>the args passed to that method at runtime:
* {@link ClassMethodArgsAndReturnVal#getArgs}; for example {@code North}</li>
* <li>the rest client to be enclosed in the optional, should you choose to
* return it: {@link ClassMethodArgsAndReturnVal#getReturnVal}; in the example
* above, an implementation of {@code KeyPairClient}</li>
* </ol>
*
* Using this context, your implementation of {@link ImplicitOptionalConverter}
* can perform whatever you need, when deciding if the the returnVal is present
* and available. Here are some ideas:
* <ul>
* <li>call a smoke test command</li>
* <li>look at the annotations on the class and compare those against a
* configuration switch enabling the extension</li>
* <li>inspect the health of the client, perhaps looking for error status</li>
* <li>call another api which can validate the feature can be presented</li>
* </ul>
*
* The {@link AlwaysPresentImplicitOptionalConverter default implementation}
* always returns present. To override this, add the following in your subclass
* override of {@link RestClientModule#configure} method:
*
* <pre>
* bind(ImplicitOptionalConverter.class).to(MyCustomOptionalConverter.class);
* </pre>
* *
* @author Adrian Cole * @author Adrian Cole
*/ */
@Beta
@ImplementedBy(AlwaysPresentImplicitOptionalConverter.class) @ImplementedBy(AlwaysPresentImplicitOptionalConverter.class)
public interface ImplicitOptionalConverter extends Function<Object, Optional<Object>> { public interface ImplicitOptionalConverter extends Function<ClassMethodArgsAndReturnVal, Optional<Object>> {
} }

View File

@ -37,6 +37,7 @@ import org.jclouds.http.HttpRequest;
import org.jclouds.http.HttpResponse; import org.jclouds.http.HttpResponse;
import org.jclouds.http.TransformingHttpCommand; import org.jclouds.http.TransformingHttpCommand;
import org.jclouds.internal.ClassMethodArgs; import org.jclouds.internal.ClassMethodArgs;
import org.jclouds.internal.ClassMethodArgsAndReturnVal;
import org.jclouds.logging.Logger; import org.jclouds.logging.Logger;
import org.jclouds.rest.AuthorizationException; import org.jclouds.rest.AuthorizationException;
import org.jclouds.rest.InvocationContext; import org.jclouds.rest.InvocationContext;
@ -67,24 +68,31 @@ import com.google.inject.util.Types;
* <p/> * <p/>
* Particularly, this code delegates calls to other things. * Particularly, this code delegates calls to other things.
* <ol> * <ol>
* <li>if the method has a {@link Provides} annotation, it responds via a {@link Injector} lookup</li> * <li>if the method has a {@link Provides} annotation, it responds via a
* <li>if the method has a {@link Delegate} annotation, it responds with an instance of interface * {@link Injector} lookup</li>
* set in returnVal, adding the current JAXrs annotations to whatever are on that class.</li> * <li>if the method has a {@link Delegate} annotation, it responds with an
* instance of interface set in returnVal, adding the current JAXrs annotations
* to whatever are on that class.</li>
* <ul> * <ul>
* <li>ex. if the method with {@link Delegate} has a {@link Path} annotation, and the returnval * <li>ex. if the method with {@link Delegate} has a {@link Path} annotation,
* interface also has {@link Path}, these values are combined.</li> * and the returnval interface also has {@link Path}, these values are combined.
* </li>
* </ul> * </ul>
* <li>if {@link RestAnnotationProcessor#delegationMap} contains a mapping for this, and the * <li>if {@link RestAnnotationProcessor#delegationMap} contains a mapping for
* returnVal is properly assigned as a {@link ListenableFuture}, it responds with an http * this, and the returnVal is properly assigned as a {@link ListenableFuture},
* implementation.</li> * it responds with an http implementation.</li>
* <li>otherwise a RuntimeException is thrown with a message including: {@code method is intended * <li>otherwise a RuntimeException is thrown with a message including:
* solely to set constants}</li> * {@code method is intended solely to set constants}</li>
* </ol> * </ol>
* *
* @author Adrian Cole * @author Adrian Cole
*/ */
@Singleton @Singleton
public class AsyncRestClientProxy<T> implements InvocationHandler { public class AsyncRestClientProxy<T> implements InvocationHandler {
public Class<T> getDeclaring() {
return declaring;
}
private final Injector injector; private final Injector injector;
private final RestAnnotationProcessor<T> annotationProcessor; private final RestAnnotationProcessor<T> annotationProcessor;
private final Class<T> declaring; private final Class<T> declaring;
@ -99,16 +107,17 @@ public class AsyncRestClientProxy<T> implements InvocationHandler {
@Resource @Resource
protected Logger logger = Logger.NULL; protected Logger logger = Logger.NULL;
private final Function<Object, Optional<Object>> optionalConverter; private final Function<ClassMethodArgsAndReturnVal, Optional<Object>> optionalConverter;
private final LoadingCache<ClassMethodArgs, Object> delegateMap; private final LoadingCache<ClassMethodArgs, Object> delegateMap;
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Inject @Inject
public AsyncRestClientProxy(Injector injector, Factory factory, RestAnnotationProcessor<T> util, public AsyncRestClientProxy(Injector injector, Factory factory, RestAnnotationProcessor<T> util,
TypeLiteral<T> typeLiteral, @Named("async") LoadingCache<ClassMethodArgs, Object> delegateMap) { TypeLiteral<T> typeLiteral, @Named("async") LoadingCache<ClassMethodArgs, Object> delegateMap) {
this.injector = injector; this.injector = injector;
this.optionalConverter = injector.getInstance(Key.get(new TypeLiteral<Function<Object, Optional<Object>>>() { this.optionalConverter = injector.getInstance(Key
})); .get(new TypeLiteral<Function<ClassMethodArgsAndReturnVal, Optional<Object>>>() {
}));
this.annotationProcessor = util; this.annotationProcessor = util;
this.declaring = (Class<T>) typeLiteral.getRawType(); this.declaring = (Class<T>) typeLiteral.getRawType();
this.commandFactory = factory; this.commandFactory = factory;
@ -139,27 +148,30 @@ public class AsyncRestClientProxy<T> implements InvocationHandler {
return createListenableFutureForHttpRequestMappedToMethodAndArgs(method, args); return createListenableFutureForHttpRequestMappedToMethodAndArgs(method, args);
} else { } else {
throw new RuntimeException(String.format("Method is not annotated as either http or provider method: %s", throw new RuntimeException(String.format("Method is not annotated as either http or provider method: %s",
method)); method));
} }
} }
public boolean isRestCall(Method method) { public boolean isRestCall(Method method) {
return annotationProcessor.getDelegateOrNull(method) != null return annotationProcessor.getDelegateOrNull(method) != null
&& ListenableFuture.class.isAssignableFrom(method.getReturnType()); && ListenableFuture.class.isAssignableFrom(method.getReturnType());
} }
public Object propagateContextToDelegate(Method method, Object[] args) throws ExecutionException { public Object propagateContextToDelegate(Method method, Object[] args) throws ExecutionException {
Class<?> asyncClass = Optionals2.returnTypeOrTypeOfOptional(method); Class<?> asyncClass = Optionals2.returnTypeOrTypeOfOptional(method);
Object returnVal = delegateMap.get(new ClassMethodArgs(asyncClass, method, args)); ClassMethodArgs cma = new ClassMethodArgs(asyncClass, method, args);
if (Optionals2.isReturnTypeOptional(method)){ Object returnVal = delegateMap.get(cma);
return optionalConverter.apply(returnVal); if (Optionals2.isReturnTypeOptional(method)) {
ClassMethodArgsAndReturnVal cmar = ClassMethodArgsAndReturnVal.builder().fromClassMethodArgs(cma)
.returnVal(returnVal).build();
return optionalConverter.apply(cmar);
} }
return returnVal; return returnVal;
} }
public Object lookupValueFromGuice(Method method) { public Object lookupValueFromGuice(Method method) {
try { try {
//TODO: tidy // TODO: tidy
Type genericReturnType = method.getGenericReturnType(); Type genericReturnType = method.getGenericReturnType();
try { try {
Annotation qualifier = Iterables.find(ImmutableList.copyOf(method.getAnnotations()), isQualifierPresent); Annotation qualifier = Iterables.find(ImmutableList.copyOf(method.getAnnotations()), isQualifierPresent);
@ -200,7 +212,7 @@ public class AsyncRestClientProxy<T> implements InvocationHandler {
// then, try looking via supplier // then, try looking via supplier
binding = injector.getExistingBinding(Key.get(Types.newParameterizedType(Supplier.class, genericReturnType), binding = injector.getExistingBinding(Key.get(Types.newParameterizedType(Supplier.class, genericReturnType),
qualifier)); qualifier));
if (binding != null) if (binding != null)
return Supplier.class.cast(binding.getProvider().get()).get(); return Supplier.class.cast(binding.getProvider().get()).get();
@ -208,13 +220,13 @@ public class AsyncRestClientProxy<T> implements InvocationHandler {
return injector.getInstance(Key.get(genericReturnType, qualifier)); return injector.getInstance(Key.get(genericReturnType, qualifier));
} }
@SuppressWarnings( { "unchecked", "rawtypes" }) @SuppressWarnings({ "unchecked", "rawtypes" })
private ListenableFuture<?> createListenableFutureForHttpRequestMappedToMethodAndArgs(Method method, Object[] args) private ListenableFuture<?> createListenableFutureForHttpRequestMappedToMethodAndArgs(Method method, Object[] args)
throws ExecutionException { throws ExecutionException {
method = annotationProcessor.getDelegateOrNull(method); method = annotationProcessor.getDelegateOrNull(method);
logger.trace("Converting %s.%s", declaring.getSimpleName(), method.getName()); logger.trace("Converting %s.%s", declaring.getSimpleName(), method.getName());
Function<Exception, ?> exceptionParser = annotationProcessor Function<Exception, ?> exceptionParser = annotationProcessor
.createExceptionParserOrThrowResourceNotFoundOn404IfNoAnnotation(method); .createExceptionParserOrThrowResourceNotFoundOn404IfNoAnnotation(method);
// in case there is an exception creating the request, we should at least // in case there is an exception creating the request, we should at least
// pass in args // pass in args
if (exceptionParser instanceof InvocationContext) { if (exceptionParser instanceof InvocationContext) {
@ -230,7 +242,7 @@ public class AsyncRestClientProxy<T> implements InvocationHandler {
Function<HttpResponse, ?> transformer = annotationProcessor.createResponseParser(method, request); Function<HttpResponse, ?> transformer = annotationProcessor.createResponseParser(method, request);
logger.trace("Response from %s.%s is parsed by %s", declaring.getSimpleName(), method.getName(), transformer logger.trace("Response from %s.%s is parsed by %s", declaring.getSimpleName(), method.getName(), transformer
.getClass().getSimpleName()); .getClass().getSimpleName());
logger.debug("Invoking %s.%s", declaring.getSimpleName(), method.getName()); logger.debug("Invoking %s.%s", declaring.getSimpleName(), method.getName());
result = commandFactory.create(request, transformer).execute(); result = commandFactory.create(request, transformer).execute();
@ -251,7 +263,7 @@ public class AsyncRestClientProxy<T> implements InvocationHandler {
if (exceptionParser != null) { if (exceptionParser != null) {
logger.trace("Exceptions from %s.%s are parsed by %s", declaring.getSimpleName(), method.getName(), logger.trace("Exceptions from %s.%s are parsed by %s", declaring.getSimpleName(), method.getName(),
exceptionParser.getClass().getSimpleName()); exceptionParser.getClass().getSimpleName());
result = new ExceptionParsingListenableFuture(result, exceptionParser); result = new ExceptionParsingListenableFuture(result, exceptionParser);
} }
return result; return result;

View File

@ -47,9 +47,9 @@ import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry;
import java.util.Set; import java.util.Set;
import java.util.SortedSet; import java.util.SortedSet;
import java.util.Map.Entry;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import javax.annotation.Resource; import javax.annotation.Resource;
@ -77,6 +77,7 @@ import org.jclouds.http.HttpUtils;
import org.jclouds.http.functions.ParseFirstJsonValueNamed; import org.jclouds.http.functions.ParseFirstJsonValueNamed;
import org.jclouds.http.functions.ParseJson; import org.jclouds.http.functions.ParseJson;
import org.jclouds.http.functions.ParseSax; import org.jclouds.http.functions.ParseSax;
import org.jclouds.http.functions.ParseSax.HandlerWithResult;
import org.jclouds.http.functions.ParseURIFromListOrLocationHeaderIf20x; import org.jclouds.http.functions.ParseURIFromListOrLocationHeaderIf20x;
import org.jclouds.http.functions.ParseXMLWithJAXB; import org.jclouds.http.functions.ParseXMLWithJAXB;
import org.jclouds.http.functions.ReleasePayloadAndReturn; import org.jclouds.http.functions.ReleasePayloadAndReturn;
@ -84,7 +85,6 @@ import org.jclouds.http.functions.ReturnInputStream;
import org.jclouds.http.functions.ReturnStringIf2xx; import org.jclouds.http.functions.ReturnStringIf2xx;
import org.jclouds.http.functions.ReturnTrueIf2xx; import org.jclouds.http.functions.ReturnTrueIf2xx;
import org.jclouds.http.functions.UnwrapOnlyJsonValue; import org.jclouds.http.functions.UnwrapOnlyJsonValue;
import org.jclouds.http.functions.ParseSax.HandlerWithResult;
import org.jclouds.http.options.HttpRequestOptions; import org.jclouds.http.options.HttpRequestOptions;
import org.jclouds.http.utils.ModifyRequest; import org.jclouds.http.utils.ModifyRequest;
import org.jclouds.internal.ClassMethodArgs; import org.jclouds.internal.ClassMethodArgs;
@ -147,12 +147,12 @@ import com.google.common.cache.LoadingCache;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMultimap; import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSet.Builder;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.google.common.collect.LinkedHashMultimap; import com.google.common.collect.LinkedHashMultimap;
import com.google.common.collect.LinkedListMultimap; import com.google.common.collect.LinkedListMultimap;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.common.collect.Multimap; import com.google.common.collect.Multimap;
import com.google.common.collect.ImmutableSet.Builder;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import com.google.inject.Inject; import com.google.inject.Inject;
import com.google.inject.Injector; import com.google.inject.Injector;

View File

@ -96,6 +96,7 @@ import org.jclouds.http.internal.PayloadEnclosingImpl;
import org.jclouds.http.options.BaseHttpRequestOptions; import org.jclouds.http.options.BaseHttpRequestOptions;
import org.jclouds.http.options.GetOptions; import org.jclouds.http.options.GetOptions;
import org.jclouds.http.options.HttpRequestOptions; import org.jclouds.http.options.HttpRequestOptions;
import org.jclouds.internal.ClassMethodArgsAndReturnVal;
import org.jclouds.io.Payload; import org.jclouds.io.Payload;
import org.jclouds.io.PayloadEnclosing; import org.jclouds.io.PayloadEnclosing;
import org.jclouds.io.Payloads; import org.jclouds.io.Payloads;
@ -355,7 +356,7 @@ public class RestAnnotationProcessorTest extends BaseRestClientTest {
bind(ImplicitOptionalConverter.class).toInstance(new ImplicitOptionalConverter() { bind(ImplicitOptionalConverter.class).toInstance(new ImplicitOptionalConverter() {
@Override @Override
public Optional<Object> apply(Object input) { public Optional<Object> apply(ClassMethodArgsAndReturnVal input) {
return Optional.absent(); return Optional.absent();
} }