mirror of https://github.com/apache/jclouds.git
Issue 870: added more context to optionalconverter
This commit is contained in:
parent
16a44b2e02
commit
3d0d0c6094
|
@ -34,6 +34,7 @@ import javax.inject.Named;
|
|||
|
||||
import org.jclouds.concurrent.Timeout;
|
||||
import org.jclouds.internal.ClassMethodArgs;
|
||||
import org.jclouds.internal.ClassMethodArgsAndReturnVal;
|
||||
import org.jclouds.rest.annotations.Delegate;
|
||||
import org.jclouds.util.Optionals2;
|
||||
import org.jclouds.util.Throwables2;
|
||||
|
@ -54,7 +55,7 @@ import com.google.inject.ProvisionException;
|
|||
public class SyncProxy implements InvocationHandler {
|
||||
|
||||
@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,
|
||||
Map<Class<?>, Class<?>> sync2Async, Map<String, Long> timeouts) throws IllegalArgumentException, SecurityException,
|
||||
NoSuchMethodException {
|
||||
|
@ -62,7 +63,7 @@ public class SyncProxy implements InvocationHandler {
|
|||
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 Class<?> declaring;
|
||||
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());
|
||||
|
||||
@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<?>,
|
||||
Class<?>> sync2Async, final Map<String, Long> timeouts)
|
||||
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) {
|
||||
Long timeout = overrideTimeout(method, timeouts);
|
||||
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
|
||||
// ex. getClientForRegion("north") might return an instance whose endpoint is
|
||||
// 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)){
|
||||
return optionalConverter.apply(returnVal);
|
||||
ClassMethodArgsAndReturnVal cmar = ClassMethodArgsAndReturnVal.builder().fromClassMethodArgs(cma)
|
||||
.returnVal(returnVal).build();
|
||||
return optionalConverter.apply(cmar);
|
||||
}
|
||||
return returnVal;
|
||||
} else if (syncMethodMap.containsKey(method)) {
|
||||
|
|
|
@ -18,32 +18,91 @@
|
|||
*/
|
||||
package org.jclouds.internal;
|
||||
|
||||
import static com.google.common.base.Objects.equal;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.jclouds.javax.annotation.Nullable;
|
||||
|
||||
import com.google.common.base.Objects;
|
||||
import com.google.common.base.Objects.ToStringHelper;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Adrian Cole
|
||||
*/
|
||||
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 Object[] args;
|
||||
private final Class<?> asyncClass;
|
||||
|
||||
public ClassMethodArgs(Class<?> asyncClass, Method method, @Nullable Object[] args) {
|
||||
this.asyncClass = checkNotNull(asyncClass, "asyncClass");
|
||||
public ClassMethodArgs(Builder<?> builder) {
|
||||
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.args = args;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "[class=" + asyncClass.getSimpleName() + ", method=" + method.getName() + ", args="
|
||||
+ Arrays.toString(args) + "]";
|
||||
public Class<?> getClazz() {
|
||||
return clazz;
|
||||
}
|
||||
|
||||
public Method getMethod() {
|
||||
|
@ -55,40 +114,26 @@ public class ClassMethodArgs {
|
|||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + Arrays.hashCode(args);
|
||||
result = prime * result + ((asyncClass == null) ? 0 : asyncClass.hashCode());
|
||||
result = prime * result + ((method == null) ? 0 : method.hashCode());
|
||||
return result;
|
||||
public boolean equals(Object o) {
|
||||
if (this == o)
|
||||
return true;
|
||||
if (o == null || getClass() != o.getClass())
|
||||
return false;
|
||||
ClassMethodArgs that = ClassMethodArgs.class.cast(o);
|
||||
return equal(this.clazz, that.clazz) && equal(this.method, that.method) && equal(this.args, that.args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj)
|
||||
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 int hashCode() {
|
||||
return Objects.hashCode(clazz, method, args);
|
||||
}
|
||||
|
||||
public Class<?> getAsyncClass() {
|
||||
return asyncClass;
|
||||
@Override
|
||||
public String toString() {
|
||||
return string().toString();
|
||||
}
|
||||
|
||||
protected ToStringHelper string() {
|
||||
return Objects.toStringHelper("").add("clazz", clazz).add("method", method).add("args", args);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -25,6 +25,7 @@ import javax.inject.Singleton;
|
|||
|
||||
import org.jclouds.concurrent.internal.SyncProxy;
|
||||
import org.jclouds.internal.ClassMethodArgs;
|
||||
import org.jclouds.internal.ClassMethodArgsAndReturnVal;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.base.Optional;
|
||||
|
@ -61,7 +62,7 @@ public class ClientProvider<S, A> implements Provider<S> {
|
|||
@Singleton
|
||||
public S get() {
|
||||
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(
|
||||
new TypeLiteral<LoadingCache<ClassMethodArgs, Object>>() {
|
||||
|
|
|
@ -27,19 +27,20 @@ import javax.inject.Inject;
|
|||
import javax.inject.Named;
|
||||
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.internal.ClassMethodArgs;
|
||||
import org.jclouds.internal.ClassMethodArgsAndReturnVal;
|
||||
import org.jclouds.util.Optionals2;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.base.Optional;
|
||||
import com.google.common.base.Throwables;
|
||||
import com.google.common.cache.LoadingCache;
|
||||
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,
|
||||
|
@ -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");
|
||||
Object asyncClient = asyncMap.get(from);
|
||||
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>>() {
|
||||
}, Names.named("TIMEOUTS")));
|
||||
|
|
|
@ -27,6 +27,7 @@ import javax.inject.Singleton;
|
|||
|
||||
import org.jclouds.http.RequiresHttp;
|
||||
import org.jclouds.internal.ClassMethodArgs;
|
||||
import org.jclouds.internal.ClassMethodArgsAndReturnVal;
|
||||
import org.jclouds.location.config.LocationModule;
|
||||
import org.jclouds.rest.AuthorizationException;
|
||||
import org.jclouds.rest.ConfiguresRestClient;
|
||||
|
@ -80,7 +81,7 @@ public class RestClientModule<S, A> extends AbstractModule {
|
|||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
@Override
|
||||
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
|
||||
bind(new TypeLiteral<AtomicReference<AuthorizationException>>(){}).toInstance(authException);
|
||||
// Ensures the restcontext can be looked up without generic types.
|
||||
|
|
|
@ -40,9 +40,9 @@ import org.jclouds.rest.internal.RestAnnotationProcessor;
|
|||
import org.jclouds.rest.internal.SeedAnnotationCache;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.cache.LoadingCache;
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import com.google.common.cache.CacheLoader;
|
||||
import com.google.common.cache.LoadingCache;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.inject.AbstractModule;
|
||||
import com.google.inject.Inject;
|
||||
|
@ -101,7 +101,7 @@ public class RestModule extends AbstractModule {
|
|||
@SuppressWarnings( { "unchecked", "rawtypes" })
|
||||
@Override
|
||||
public Object load(final ClassMethodArgs from) {
|
||||
Class clazz = from.getAsyncClass();
|
||||
Class clazz = from.getClazz();
|
||||
TypeLiteral typeLiteral = TypeLiteral.get(clazz);
|
||||
RestAnnotationProcessor util = (RestAnnotationProcessor) injector.getInstance(Key.get(TypeLiteral.get(Types
|
||||
.newParameterizedType(RestAnnotationProcessor.class, clazz))));
|
||||
|
|
|
@ -18,17 +18,21 @@
|
|||
*/
|
||||
package org.jclouds.rest.functions;
|
||||
|
||||
import org.jclouds.internal.ClassMethodArgsAndReturnVal;
|
||||
|
||||
import com.google.common.annotations.Beta;
|
||||
import com.google.common.base.Optional;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Adrian Cole
|
||||
*/
|
||||
@Beta
|
||||
public class AlwaysPresentImplicitOptionalConverter implements ImplicitOptionalConverter {
|
||||
|
||||
@Override
|
||||
public Optional<Object> apply(Object input) {
|
||||
return Optional.of(input);
|
||||
public Optional<Object> apply(ClassMethodArgsAndReturnVal input) {
|
||||
return Optional.of(input.getReturnVal());
|
||||
}
|
||||
|
||||
}
|
|
@ -18,15 +18,66 @@
|
|||
*/
|
||||
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.Optional;
|
||||
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 {
|
||||
* @Delegate
|
||||
* Optional<KeyPairClient> 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
|
||||
*/
|
||||
@Beta
|
||||
@ImplementedBy(AlwaysPresentImplicitOptionalConverter.class)
|
||||
public interface ImplicitOptionalConverter extends Function<Object, Optional<Object>> {
|
||||
public interface ImplicitOptionalConverter extends Function<ClassMethodArgsAndReturnVal, Optional<Object>> {
|
||||
|
||||
}
|
|
@ -37,6 +37,7 @@ import org.jclouds.http.HttpRequest;
|
|||
import org.jclouds.http.HttpResponse;
|
||||
import org.jclouds.http.TransformingHttpCommand;
|
||||
import org.jclouds.internal.ClassMethodArgs;
|
||||
import org.jclouds.internal.ClassMethodArgsAndReturnVal;
|
||||
import org.jclouds.logging.Logger;
|
||||
import org.jclouds.rest.AuthorizationException;
|
||||
import org.jclouds.rest.InvocationContext;
|
||||
|
@ -67,24 +68,31 @@ import com.google.inject.util.Types;
|
|||
* <p/>
|
||||
* Particularly, this code delegates calls to other things.
|
||||
* <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 Delegate} annotation, it responds with an instance of interface
|
||||
* set in returnVal, adding the current JAXrs annotations to whatever are on that class.</li>
|
||||
* <li>if the method has a {@link Provides} annotation, it responds via a
|
||||
* {@link Injector} lookup</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>
|
||||
* <li>ex. if the method with {@link Delegate} has a {@link Path} annotation, and the returnval
|
||||
* interface also has {@link Path}, these values are combined.</li>
|
||||
* <li>ex. if the method with {@link Delegate} has a {@link Path} annotation,
|
||||
* and the returnval interface also has {@link Path}, these values are combined.
|
||||
* </li>
|
||||
* </ul>
|
||||
* <li>if {@link RestAnnotationProcessor#delegationMap} contains a mapping for this, and the
|
||||
* returnVal is properly assigned as a {@link ListenableFuture}, it responds with an http
|
||||
* implementation.</li>
|
||||
* <li>otherwise a RuntimeException is thrown with a message including: {@code method is intended
|
||||
* solely to set constants}</li>
|
||||
* <li>if {@link RestAnnotationProcessor#delegationMap} contains a mapping for
|
||||
* this, and the returnVal is properly assigned as a {@link ListenableFuture},
|
||||
* it responds with an http implementation.</li>
|
||||
* <li>otherwise a RuntimeException is thrown with a message including:
|
||||
* {@code method is intended solely to set constants}</li>
|
||||
* </ol>
|
||||
*
|
||||
* @author Adrian Cole
|
||||
*/
|
||||
@Singleton
|
||||
public class AsyncRestClientProxy<T> implements InvocationHandler {
|
||||
public Class<T> getDeclaring() {
|
||||
return declaring;
|
||||
}
|
||||
|
||||
private final Injector injector;
|
||||
private final RestAnnotationProcessor<T> annotationProcessor;
|
||||
private final Class<T> declaring;
|
||||
|
@ -99,16 +107,17 @@ public class AsyncRestClientProxy<T> implements InvocationHandler {
|
|||
|
||||
@Resource
|
||||
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;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Inject
|
||||
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.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.declaring = (Class<T>) typeLiteral.getRawType();
|
||||
this.commandFactory = factory;
|
||||
|
@ -139,27 +148,30 @@ public class AsyncRestClientProxy<T> implements InvocationHandler {
|
|||
return createListenableFutureForHttpRequestMappedToMethodAndArgs(method, args);
|
||||
} else {
|
||||
throw new RuntimeException(String.format("Method is not annotated as either http or provider method: %s",
|
||||
method));
|
||||
method));
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isRestCall(Method method) {
|
||||
return annotationProcessor.getDelegateOrNull(method) != null
|
||||
&& ListenableFuture.class.isAssignableFrom(method.getReturnType());
|
||||
&& ListenableFuture.class.isAssignableFrom(method.getReturnType());
|
||||
}
|
||||
|
||||
public Object propagateContextToDelegate(Method method, Object[] args) throws ExecutionException {
|
||||
Class<?> asyncClass = Optionals2.returnTypeOrTypeOfOptional(method);
|
||||
Object returnVal = delegateMap.get(new ClassMethodArgs(asyncClass, method, args));
|
||||
if (Optionals2.isReturnTypeOptional(method)){
|
||||
return optionalConverter.apply(returnVal);
|
||||
ClassMethodArgs cma = new ClassMethodArgs(asyncClass, method, args);
|
||||
Object returnVal = delegateMap.get(cma);
|
||||
if (Optionals2.isReturnTypeOptional(method)) {
|
||||
ClassMethodArgsAndReturnVal cmar = ClassMethodArgsAndReturnVal.builder().fromClassMethodArgs(cma)
|
||||
.returnVal(returnVal).build();
|
||||
return optionalConverter.apply(cmar);
|
||||
}
|
||||
return returnVal;
|
||||
}
|
||||
|
||||
public Object lookupValueFromGuice(Method method) {
|
||||
try {
|
||||
//TODO: tidy
|
||||
// TODO: tidy
|
||||
Type genericReturnType = method.getGenericReturnType();
|
||||
try {
|
||||
Annotation qualifier = Iterables.find(ImmutableList.copyOf(method.getAnnotations()), isQualifierPresent);
|
||||
|
@ -200,7 +212,7 @@ public class AsyncRestClientProxy<T> implements InvocationHandler {
|
|||
|
||||
// then, try looking via supplier
|
||||
binding = injector.getExistingBinding(Key.get(Types.newParameterizedType(Supplier.class, genericReturnType),
|
||||
qualifier));
|
||||
qualifier));
|
||||
if (binding != null)
|
||||
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));
|
||||
}
|
||||
|
||||
@SuppressWarnings( { "unchecked", "rawtypes" })
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
private ListenableFuture<?> createListenableFutureForHttpRequestMappedToMethodAndArgs(Method method, Object[] args)
|
||||
throws ExecutionException {
|
||||
throws ExecutionException {
|
||||
method = annotationProcessor.getDelegateOrNull(method);
|
||||
logger.trace("Converting %s.%s", declaring.getSimpleName(), method.getName());
|
||||
Function<Exception, ?> exceptionParser = annotationProcessor
|
||||
.createExceptionParserOrThrowResourceNotFoundOn404IfNoAnnotation(method);
|
||||
.createExceptionParserOrThrowResourceNotFoundOn404IfNoAnnotation(method);
|
||||
// in case there is an exception creating the request, we should at least
|
||||
// pass in args
|
||||
if (exceptionParser instanceof InvocationContext) {
|
||||
|
@ -230,7 +242,7 @@ public class AsyncRestClientProxy<T> implements InvocationHandler {
|
|||
|
||||
Function<HttpResponse, ?> transformer = annotationProcessor.createResponseParser(method, request);
|
||||
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());
|
||||
result = commandFactory.create(request, transformer).execute();
|
||||
|
@ -251,7 +263,7 @@ public class AsyncRestClientProxy<T> implements InvocationHandler {
|
|||
|
||||
if (exceptionParser != null) {
|
||||
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);
|
||||
}
|
||||
return result;
|
||||
|
|
|
@ -47,9 +47,9 @@ import java.util.Collections;
|
|||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
import java.util.SortedSet;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
@ -77,6 +77,7 @@ import org.jclouds.http.HttpUtils;
|
|||
import org.jclouds.http.functions.ParseFirstJsonValueNamed;
|
||||
import org.jclouds.http.functions.ParseJson;
|
||||
import org.jclouds.http.functions.ParseSax;
|
||||
import org.jclouds.http.functions.ParseSax.HandlerWithResult;
|
||||
import org.jclouds.http.functions.ParseURIFromListOrLocationHeaderIf20x;
|
||||
import org.jclouds.http.functions.ParseXMLWithJAXB;
|
||||
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.ReturnTrueIf2xx;
|
||||
import org.jclouds.http.functions.UnwrapOnlyJsonValue;
|
||||
import org.jclouds.http.functions.ParseSax.HandlerWithResult;
|
||||
import org.jclouds.http.options.HttpRequestOptions;
|
||||
import org.jclouds.http.utils.ModifyRequest;
|
||||
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.ImmutableMultimap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.ImmutableSet.Builder;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.LinkedHashMultimap;
|
||||
import com.google.common.collect.LinkedListMultimap;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Multimap;
|
||||
import com.google.common.collect.ImmutableSet.Builder;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Injector;
|
||||
|
|
|
@ -96,6 +96,7 @@ import org.jclouds.http.internal.PayloadEnclosingImpl;
|
|||
import org.jclouds.http.options.BaseHttpRequestOptions;
|
||||
import org.jclouds.http.options.GetOptions;
|
||||
import org.jclouds.http.options.HttpRequestOptions;
|
||||
import org.jclouds.internal.ClassMethodArgsAndReturnVal;
|
||||
import org.jclouds.io.Payload;
|
||||
import org.jclouds.io.PayloadEnclosing;
|
||||
import org.jclouds.io.Payloads;
|
||||
|
@ -355,7 +356,7 @@ public class RestAnnotationProcessorTest extends BaseRestClientTest {
|
|||
bind(ImplicitOptionalConverter.class).toInstance(new ImplicitOptionalConverter() {
|
||||
|
||||
@Override
|
||||
public Optional<Object> apply(Object input) {
|
||||
public Optional<Object> apply(ClassMethodArgsAndReturnVal input) {
|
||||
return Optional.absent();
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue