update javadoc and suggest areas for improvement

This commit is contained in:
Adrian Cole 2012-01-22 12:12:19 +08:00
parent 345f83581c
commit 4816bb8a08
3 changed files with 84 additions and 26 deletions

View File

@ -18,11 +18,7 @@
*/ */
package org.jclouds.rest.internal; package org.jclouds.rest.internal;
/**
* Generates RESTful clients from appropriately annotated interfaces.
*
* @author Adrian Cole
*/
import java.lang.annotation.Annotation; import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method; import java.lang.reflect.Method;
@ -60,6 +56,30 @@ import com.google.inject.Provides;
import com.google.inject.ProvisionException; import com.google.inject.ProvisionException;
import com.google.inject.TypeLiteral; import com.google.inject.TypeLiteral;
/**
* Generates RESTful clients from appropriately annotated interfaces.
* <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>
* <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>
* </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>
* </ol>
*
* @author Adrian Cole
*/
@Singleton @Singleton
public class AsyncRestClientProxy<T> implements InvocationHandler { public class AsyncRestClientProxy<T> implements InvocationHandler {
private final Injector injector; private final Injector injector;
@ -106,31 +126,43 @@ public class AsyncRestClientProxy<T> implements InvocationHandler {
} else if (method.getName().equals("hashCode")) { } else if (method.getName().equals("hashCode")) {
return this.hashCode(); return this.hashCode();
} else if (method.isAnnotationPresent(Provides.class)) { } else if (method.isAnnotationPresent(Provides.class)) {
try { return lookupValueFromGuice(method);
try {
Annotation qualifier = Iterables.find(ImmutableList.copyOf(method.getAnnotations()), isQualifierPresent);
return injector.getInstance(Key.get(method.getGenericReturnType(), qualifier));
} catch (NoSuchElementException e) {
return injector.getInstance(Key.get(method.getGenericReturnType()));
}
} catch (ProvisionException e) {
AuthorizationException aex = Throwables2.getFirstThrowableOfType(e, AuthorizationException.class);
if (aex != null)
throw aex;
throw e;
}
} else if (method.isAnnotationPresent(Delegate.class)) { } else if (method.isAnnotationPresent(Delegate.class)) {
return delegateMap.get(new ClassMethodArgs(method.getReturnType(), method, args)); return propagateContextToDelegate(method, args);
} else if (annotationProcessor.getDelegateOrNull(method) != null } else if (isRestCall(method)) {
&& ListenableFuture.class.isAssignableFrom(method.getReturnType())) { return createListenableFutureForHttpRequestMappedToMethodAndArgs(method, args);
return createListenableFuture(method, args);
} else { } else {
throw new RuntimeException("method is intended solely to set constants: " + method); throw new RuntimeException("method is intended solely to set constants: " + method);
} }
} }
public boolean isRestCall(Method method) {
return annotationProcessor.getDelegateOrNull(method) != null
&& ListenableFuture.class.isAssignableFrom(method.getReturnType());
}
public Object propagateContextToDelegate(Method method, Object[] args) throws ExecutionException {
return delegateMap.get(new ClassMethodArgs(method.getReturnType(), method, args));
}
public Object lookupValueFromGuice(Method method) {
try {
try {
Annotation qualifier = Iterables.find(ImmutableList.copyOf(method.getAnnotations()), isQualifierPresent);
return injector.getInstance(Key.get(method.getGenericReturnType(), qualifier));
} catch (NoSuchElementException e) {
return injector.getInstance(Key.get(method.getGenericReturnType()));
}
} catch (ProvisionException e) {
AuthorizationException aex = Throwables2.getFirstThrowableOfType(e, AuthorizationException.class);
if (aex != null)
throw aex;
throw e;
}
}
@SuppressWarnings( { "unchecked", "rawtypes" }) @SuppressWarnings( { "unchecked", "rawtypes" })
private ListenableFuture<?> createListenableFuture(Method method, Object[] args) throws ExecutionException { private ListenableFuture<?> createListenableFutureForHttpRequestMappedToMethodAndArgs(Method method, Object[] args) 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

View File

@ -184,6 +184,17 @@ public class RestAnnotationProcessor<T> {
static final LoadingCache<Method, LoadingCache<Integer, Set<Annotation>>> methodToIndexOfParamToPostParamAnnotations = createMethodToIndexOfParamToAnnotation(PayloadParam.class); static final LoadingCache<Method, LoadingCache<Integer, Set<Annotation>>> methodToIndexOfParamToPostParamAnnotations = createMethodToIndexOfParamToAnnotation(PayloadParam.class);
static final LoadingCache<Method, LoadingCache<Integer, Set<Annotation>>> methodToIndexOfParamToPartParamAnnotations = createMethodToIndexOfParamToAnnotation(PartParam.class); static final LoadingCache<Method, LoadingCache<Integer, Set<Annotation>>> methodToIndexOfParamToPartParamAnnotations = createMethodToIndexOfParamToAnnotation(PartParam.class);
static final LoadingCache<Method, LoadingCache<Integer, Set<Annotation>>> methodToIndexOfParamToParamParserAnnotations = createMethodToIndexOfParamToAnnotation(ParamParser.class); static final LoadingCache<Method, LoadingCache<Integer, Set<Annotation>>> methodToIndexOfParamToParamParserAnnotations = createMethodToIndexOfParamToAnnotation(ParamParser.class);
/**
* Shared for all types of rest clients. this is read-only in this class, and
* currently populated only by {@link SeedAnnotationCache}
*
* @see SeedAnnotationCache
*/
// TODO: change this to a private final LoadingCache<MethodKey, Method>
// supplied by guice. The CacheLoader<MethodKey, Method> can be refactored
// out from SeedAnnotationCache. Potentially, preseed the cache, but only if
// this is uncomplicated.
static final Map<MethodKey, Method> delegationMap = newHashMap(); static final Map<MethodKey, Method> delegationMap = newHashMap();
static LoadingCache<Method, LoadingCache<Integer, Set<Annotation>>> createMethodToIndexOfParamToAnnotation( static LoadingCache<Method, LoadingCache<Integer, Set<Annotation>>> createMethodToIndexOfParamToAnnotation(
@ -681,6 +692,7 @@ public class RestAnnotationProcessor<T> {
} }
} }
//TODO: change to LoadingCache<Method, List<HttpRequestFilter>> and move this logic to the CacheLoader.
@VisibleForTesting @VisibleForTesting
List<HttpRequestFilter> getFiltersIfAnnotated(Method method) { List<HttpRequestFilter> getFiltersIfAnnotated(Method method) {
List<HttpRequestFilter> filters = Lists.newArrayList(); List<HttpRequestFilter> filters = Lists.newArrayList();
@ -703,6 +715,7 @@ public class RestAnnotationProcessor<T> {
return filters; return filters;
} }
//TODO: change to LoadingCache<ClassMethodArgs, Optional<URI>> and move this logic to the CacheLoader.
@VisibleForTesting @VisibleForTesting
public static URI getEndpointInParametersOrNull(Method method, final Object[] args, Injector injector) public static URI getEndpointInParametersOrNull(Method method, final Object[] args, Injector injector)
throws ExecutionException { throws ExecutionException {
@ -746,6 +759,7 @@ public class RestAnnotationProcessor<T> {
return null; return null;
} }
//TODO: change to LoadingCache<ClassMethodArgs, URI> and move this logic to the CacheLoader.
public static URI getEndpointFor(Method method, Object[] args, Injector injector) throws ExecutionException { public static URI getEndpointFor(Method method, Object[] args, Injector injector) throws ExecutionException {
URI endpoint = getEndpointInParametersOrNull(method, args, injector); URI endpoint = getEndpointInParametersOrNull(method, args, injector);
if (endpoint == null) { if (endpoint == null) {
@ -784,6 +798,7 @@ public class RestAnnotationProcessor<T> {
public static final TypeLiteral<ListenableFuture<HttpResponse>> futureHttpResponseLiteral = new TypeLiteral<ListenableFuture<HttpResponse>>() { public static final TypeLiteral<ListenableFuture<HttpResponse>> futureHttpResponseLiteral = new TypeLiteral<ListenableFuture<HttpResponse>>() {
}; };
//TODO: change to LoadingCache<Method, Key<? extends Function<HttpResponse, ?>>> and move this logic to the CacheLoader.
@SuppressWarnings({ "unchecked", "rawtypes" }) @SuppressWarnings({ "unchecked", "rawtypes" })
public static Key<? extends Function<HttpResponse, ?>> getParserOrThrowException(Method method) { public static Key<? extends Function<HttpResponse, ?>> getParserOrThrowException(Method method) {
ResponseParser annotation = method.getAnnotation(ResponseParser.class); ResponseParser annotation = method.getAnnotation(ResponseParser.class);
@ -864,6 +879,7 @@ public class RestAnnotationProcessor<T> {
return null; return null;
} }
//TODO: change to LoadingCache<ClassMethodArgs, Optional<MapBinder>> and move this logic to the CacheLoader.
public org.jclouds.rest.MapBinder getMapPayloadBinderOrNull(Method method, Object... args) { public org.jclouds.rest.MapBinder getMapPayloadBinderOrNull(Method method, Object... args) {
if (args != null) { if (args != null) {
for (Object arg : args) { for (Object arg : args) {
@ -1020,7 +1036,8 @@ public class RestAnnotationProcessor<T> {
return indexToPayloadAnnotation; return indexToPayloadAnnotation;
} }
private HttpRequestOptions findOptionsIn(Method method, Object... args) throws ExecutionException { //TODO: change to LoadingCache<ClassMethodArgs, HttpRequestOptions and move this logic to the CacheLoader.
private HttpRequestOptions findOptionsIn(Method method, Object... args) throws ExecutionException {
for (int index : methodToIndexesOfOptions.get(method)) { for (int index : methodToIndexesOfOptions.get(method)) {
if (args.length >= index + 1) {// accomodate varargs if (args.length >= index + 1) {// accomodate varargs
if (args[index] instanceof Object[]) { if (args[index] instanceof Object[]) {
@ -1155,6 +1172,7 @@ public class RestAnnotationProcessor<T> {
return null; return null;
} }
//TODO: change to LoadingCache<ClassMethodArgs, Multimap<String,String> and move this logic to the CacheLoader.
private Multimap<String, String> getPathParamKeyValues(Method method, Object... args) throws ExecutionException { private Multimap<String, String> getPathParamKeyValues(Method method, Object... args) throws ExecutionException {
Multimap<String, String> pathParamValues = LinkedHashMultimap.create(); Multimap<String, String> pathParamValues = LinkedHashMultimap.create();
LoadingCache<Integer, Set<Annotation>> indexToPathParam = methodToIndexOfParamToPathParamAnnotations.get(method); LoadingCache<Integer, Set<Annotation>> indexToPathParam = methodToIndexOfParamToPathParamAnnotations.get(method);
@ -1192,6 +1210,7 @@ public class RestAnnotationProcessor<T> {
return encoded; return encoded;
} }
//TODO: change to LoadingCache<ClassMethodArgs, Multimap<String,String> and move this logic to the CacheLoader.
private Multimap<String, String> getMatrixParamKeyValues(Method method, Object... args) throws ExecutionException { private Multimap<String, String> getMatrixParamKeyValues(Method method, Object... args) throws ExecutionException {
Multimap<String, String> matrixParamValues = LinkedHashMultimap.create(); Multimap<String, String> matrixParamValues = LinkedHashMultimap.create();
LoadingCache<Integer, Set<Annotation>> indexToMatrixParam = methodToIndexOfParamToMatrixParamAnnotations.get(method); LoadingCache<Integer, Set<Annotation>> indexToMatrixParam = methodToIndexOfParamToMatrixParamAnnotations.get(method);
@ -1221,6 +1240,8 @@ public class RestAnnotationProcessor<T> {
return matrixParamValues; return matrixParamValues;
} }
//TODO: change to LoadingCache<ClassMethodArgs, Multimap<String,String> and move this logic to the CacheLoader.
//take care to manage size of this cache
private Multimap<String, String> getFormParamKeyValues(Method method, Object... args) throws ExecutionException { private Multimap<String, String> getFormParamKeyValues(Method method, Object... args) throws ExecutionException {
Multimap<String, String> formParamValues = LinkedHashMultimap.create(); Multimap<String, String> formParamValues = LinkedHashMultimap.create();
LoadingCache<Integer, Set<Annotation>> indexToFormParam = methodToIndexOfParamToFormParamAnnotations.get(method); LoadingCache<Integer, Set<Annotation>> indexToFormParam = methodToIndexOfParamToFormParamAnnotations.get(method);
@ -1252,6 +1273,7 @@ public class RestAnnotationProcessor<T> {
return formParamValues; return formParamValues;
} }
//TODO: change to LoadingCache<ClassMethodArgs, Multimap<String,String> and move this logic to the CacheLoader.
private Multimap<String, String> getQueryParamKeyValues(Method method, Object... args) throws ExecutionException { private Multimap<String, String> getQueryParamKeyValues(Method method, Object... args) throws ExecutionException {
Multimap<String, String> queryParamValues = LinkedHashMultimap.create(); Multimap<String, String> queryParamValues = LinkedHashMultimap.create();
LoadingCache<Integer, Set<Annotation>> indexToQueryParam = methodToIndexOfParamToQueryParamAnnotations.get(method); LoadingCache<Integer, Set<Annotation>> indexToQueryParam = methodToIndexOfParamToQueryParamAnnotations.get(method);
@ -1281,6 +1303,8 @@ public class RestAnnotationProcessor<T> {
return queryParamValues; return queryParamValues;
} }
//TODO: change to LoadingCache<ClassMethodArgs, Map<String,String> and move this logic to the CacheLoader.
//take care to manage size of this cache
private Map<String, String> buildPostParams(Method method, Object... args) throws ExecutionException { private Map<String, String> buildPostParams(Method method, Object... args) throws ExecutionException {
Map<String, String> postParams = newHashMap(); Map<String, String> postParams = newHashMap();
LoadingCache<Integer, Set<Annotation>> indexToPathParam = methodToIndexOfParamToPostParamAnnotations.get(method); LoadingCache<Integer, Set<Annotation>> indexToPathParam = methodToIndexOfParamToPostParamAnnotations.get(method);

View File

@ -58,11 +58,13 @@ import com.google.inject.Key;
import com.google.inject.Provides; import com.google.inject.Provides;
/** /**
* seeds the annotation cache * seeds the annotation cache located at
* {@link RestAnnotationProcessor#delegationMap}. Note this call is only
* intended to be called once per {@link RestContext} and avoids expensive
* lookups on each call.
* *
* @author Adrian Cole * @author Adrian Cole
*/ */
@Singleton @Singleton
public class SeedAnnotationCache extends CacheLoader<Class<?>, Boolean> { public class SeedAnnotationCache extends CacheLoader<Class<?>, Boolean> {
@Resource @Resource