Cleaned up scoping of delegated methods

This commit is contained in:
Adrian Cole 2010-07-13 02:39:52 -05:00
parent 436037c7be
commit 891484a2aa
4 changed files with 502 additions and 304 deletions

View File

@ -0,0 +1,38 @@
/**
*
* Copyright (C) 2009 Cloud Conscious, LLC. <info@cloudconscious.com>
*
* ====================================================================
* Licensed 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.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.inject.Qualifier;
/**
* Apply this to implementation classes when you want Access to items from the
* scope of a delegate.
*
* @author Adrian Cole
*/
@Target( { ElementType.TYPE, ElementType.METHOD, ElementType.PARAMETER , ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Caller {
}

View File

@ -58,8 +58,8 @@ public class RestClientModule<S, A> extends AbstractModule {
} }
public RestClientModule(Class<S> syncClientType, Class<A> asyncClientType) { public RestClientModule(Class<S> syncClientType, Class<A> asyncClientType) {
this(syncClientType, asyncClientType, ImmutableMap.<Class<?>, Class<?>> of(syncClientType, this(syncClientType, asyncClientType, ImmutableMap
asyncClientType)); .<Class<?>, Class<?>> of(syncClientType, asyncClientType));
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@ -68,8 +68,9 @@ public class RestClientModule<S, A> extends AbstractModule {
// Ensures the restcontext can be looked up without generic types. // Ensures the restcontext can be looked up without generic types.
bind(new TypeLiteral<RestContext>() { bind(new TypeLiteral<RestContext>() {
}).to( }).to(
(TypeLiteral) TypeLiteral.get(Types.newParameterizedType(RestContextImpl.class, (TypeLiteral) TypeLiteral.get(Types.newParameterizedType(
syncClientType, asyncClientType))).in(Scopes.SINGLETON); RestContextImpl.class, syncClientType, asyncClientType))).in(
Scopes.SINGLETON);
bindAsyncClient(); bindAsyncClient();
bindClient(); bindClient();
bindErrorHandlers(); bindErrorHandlers();
@ -82,8 +83,10 @@ public class RestClientModule<S, A> extends AbstractModule {
* ex. * ex.
* *
* <pre> * <pre>
* bind(HttpRetryHandler.class).annotatedWith(Redirection.class).to(AWSRedirectionRetryHandler.class); * bind(HttpRetryHandler.class).annotatedWith(Redirection.class).to(
* bind(HttpRetryHandler.class).annotatedWith(ClientError.class).to(AWSClientErrorRetryHandler.class); * AWSRedirectionRetryHandler.class);
* bind(HttpRetryHandler.class).annotatedWith(ClientError.class).to(
* AWSClientErrorRetryHandler.class);
* </pre> * </pre>
* *
*/ */
@ -96,9 +99,12 @@ public class RestClientModule<S, A> extends AbstractModule {
* ex. * ex.
* *
* <pre> * <pre>
* bind(HttpErrorHandler.class).annotatedWith(Redirection.class).to(ParseAWSErrorFromXmlContent.class); * bind(HttpErrorHandler.class).annotatedWith(Redirection.class).to(
* bind(HttpErrorHandler.class).annotatedWith(ClientError.class).to(ParseAWSErrorFromXmlContent.class); * ParseAWSErrorFromXmlContent.class);
* bind(HttpErrorHandler.class).annotatedWith(ServerError.class).to(ParseAWSErrorFromXmlContent.class); * bind(HttpErrorHandler.class).annotatedWith(ClientError.class).to(
* ParseAWSErrorFromXmlContent.class);
* bind(HttpErrorHandler.class).annotatedWith(ServerError.class).to(
* ParseAWSErrorFromXmlContent.class);
* </pre> * </pre>
* *
* *
@ -111,9 +117,11 @@ public class RestClientModule<S, A> extends AbstractModule {
} }
protected void bindClient() { protected void bindClient() {
BinderUtils.bindClient(binder(), syncClientType, asyncClientType, delegates); BinderUtils.bindClient(binder(), syncClientType, asyncClientType,
delegates);
} }
@Provides @Provides
@Singleton @Singleton
@Named("sync") @Named("sync")

View File

@ -18,9 +18,9 @@
*/ */
package org.jclouds.rest.config; package org.jclouds.rest.config;
import java.net.URI;
import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentMap;
import javax.inject.Inject;
import javax.inject.Named; import javax.inject.Named;
import javax.inject.Singleton; import javax.inject.Singleton;
import javax.ws.rs.core.UriBuilder; import javax.ws.rs.core.UriBuilder;
@ -36,6 +36,7 @@ import org.jclouds.internal.ClassMethodArgs;
import org.jclouds.rest.AsyncClientFactory; import org.jclouds.rest.AsyncClientFactory;
import org.jclouds.rest.HttpAsyncClient; import org.jclouds.rest.HttpAsyncClient;
import org.jclouds.rest.HttpClient; import org.jclouds.rest.HttpClient;
import org.jclouds.rest.annotations.Caller;
import org.jclouds.rest.internal.AsyncRestClientProxy; import org.jclouds.rest.internal.AsyncRestClientProxy;
import org.jclouds.rest.internal.RestAnnotationProcessor; import org.jclouds.rest.internal.RestAnnotationProcessor;
@ -43,8 +44,10 @@ import com.google.common.base.Function;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.google.common.collect.MapMaker; import com.google.common.collect.MapMaker;
import com.google.inject.AbstractModule; import com.google.inject.AbstractModule;
import com.google.inject.Inject;
import com.google.inject.Injector; import com.google.inject.Injector;
import com.google.inject.Key; import com.google.inject.Key;
import com.google.inject.Module;
import com.google.inject.Provides; import com.google.inject.Provides;
import com.google.inject.Scopes; import com.google.inject.Scopes;
import com.google.inject.TypeLiteral; import com.google.inject.TypeLiteral;
@ -61,10 +64,12 @@ public class RestModule extends AbstractModule {
protected void configure() { protected void configure() {
install(new ParserModule()); install(new ParserModule());
bind(UriBuilder.class).to(UriBuilderImpl.class); bind(UriBuilder.class).to(UriBuilderImpl.class);
bind(AsyncRestClientProxy.Factory.class).to(Factory.class).in(Scopes.SINGLETON); bind(AsyncRestClientProxy.Factory.class).to(Factory.class).in(
Scopes.SINGLETON);
BinderUtils.bindAsyncClient(binder(), HttpAsyncClient.class); BinderUtils.bindAsyncClient(binder(), HttpAsyncClient.class);
BinderUtils.bindClient(binder(), HttpClient.class, HttpAsyncClient.class, ImmutableMap BinderUtils.bindClient(binder(), HttpClient.class, HttpAsyncClient.class,
.<Class<?>, Class<?>> of(HttpClient.class, HttpAsyncClient.class)); ImmutableMap.<Class<?>, Class<?>> of(HttpClient.class,
HttpAsyncClient.class));
} }
@Provides @Provides
@ -75,36 +80,74 @@ public class RestModule extends AbstractModule {
return new MapMaker().makeComputingMap(createAsyncClientForCaller); return new MapMaker().makeComputingMap(createAsyncClientForCaller);
} }
static class CreateAsyncClientForCaller implements Function<ClassMethodArgs, Object> { static class CreateAsyncClientForCaller implements
Function<ClassMethodArgs, Object> {
private final Injector injector; private final Injector injector;
private final AsyncRestClientProxy.Factory factory; private final AsyncRestClientProxy.Factory factory;
@Caller
@Inject(optional = true)
Module callerModule = new CallerScopedBindingModule();
@Inject @Inject
CreateAsyncClientForCaller(Injector injector, AsyncRestClientProxy.Factory factory) { CreateAsyncClientForCaller(Injector injector,
AsyncRestClientProxy.Factory factory) {
this.injector = injector; this.injector = injector;
this.factory = factory; this.factory = factory;
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Override @Override
public Object apply(ClassMethodArgs from) { public Object apply(final ClassMethodArgs from) {
Class clazz = from.getAsyncClass(); Class clazz = from.getAsyncClass();
TypeLiteral typeLiteral = TypeLiteral.get(clazz); TypeLiteral typeLiteral = TypeLiteral.get(clazz);
RestAnnotationProcessor util = (RestAnnotationProcessor) injector.getInstance(Key Injector injector = this.injector.createChildInjector(callerModule,
.get(TypeLiteral.get(Types.newParameterizedType(RestAnnotationProcessor.class, new AbstractModule() {
clazz))));
util.setCaller(from);
ConcurrentMap<ClassMethodArgs, Object> delegateMap = injector.getInstance(Key.get( @Override
protected void configure() {
bind(ClassMethodArgs.class).annotatedWith(Caller.class)
.toInstance(from);
install(callerModule);
}
});
RestAnnotationProcessor util = (RestAnnotationProcessor) injector
.getInstance(Key.get(TypeLiteral.get(Types.newParameterizedType(
RestAnnotationProcessor.class, clazz))));
// not sure why we have to go back and re-inject this...
injector.injectMembers(util);
ConcurrentMap<ClassMethodArgs, Object> delegateMap = injector
.getInstance(Key.get(
new TypeLiteral<ConcurrentMap<ClassMethodArgs, Object>>() { new TypeLiteral<ConcurrentMap<ClassMethodArgs, Object>>() {
}, Names.named("async"))); }, Names.named("async")));
AsyncRestClientProxy proxy = new AsyncRestClientProxy(injector, factory, util, AsyncRestClientProxy proxy = new AsyncRestClientProxy(injector,
typeLiteral, delegateMap); factory, util, typeLiteral, delegateMap);
injector.injectMembers(proxy); injector.injectMembers(proxy);
return AsyncClientFactory.create(clazz, proxy); return AsyncClientFactory.create(clazz, proxy);
} }
} }
@Singleton
public static class CallerScopedBindingModule extends AbstractModule {
@Provides
@Caller
URI provideCallerScopedURI(Injector injector, @Caller ClassMethodArgs args) {
try {
return RestAnnotationProcessor.getEndpointFor(args.getMethod(),
args.getArgs(), injector);
} catch (IllegalStateException e) {
return null;
}
}
@Override
protected void configure() {
}
}
private static class Factory implements AsyncRestClientProxy.Factory { private static class Factory implements AsyncRestClientProxy.Factory {
@Inject @Inject
private TransformingHttpCommandExecutorService executorService; private TransformingHttpCommandExecutorService executorService;
@ -112,7 +155,8 @@ public class RestModule extends AbstractModule {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public TransformingHttpCommand<?> create(HttpRequest request, public TransformingHttpCommand<?> create(HttpRequest request,
Function<HttpResponse, ?> transformer) { Function<HttpResponse, ?> transformer) {
return new TransformingHttpCommandImpl(executorService, request, transformer); return new TransformingHttpCommandImpl(executorService, request,
transformer);
} }
} }

View File

@ -20,8 +20,6 @@ package org.jclouds.rest.internal;
import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.collect.Collections2.filter; import static com.google.common.collect.Collections2.filter;
import static com.google.common.collect.Collections2.transform;
import static com.google.common.collect.Iterables.concat;
import static com.google.common.collect.Lists.newArrayList; import static com.google.common.collect.Lists.newArrayList;
import static com.google.common.collect.Lists.newLinkedList; import static com.google.common.collect.Lists.newLinkedList;
import static com.google.common.collect.Maps.filterValues; import static com.google.common.collect.Maps.filterValues;
@ -37,8 +35,6 @@ import static org.jclouds.http.HttpUtils.makeQueryLine;
import static org.jclouds.http.HttpUtils.parseQueryToMap; import static org.jclouds.http.HttpUtils.parseQueryToMap;
import static org.jclouds.http.HttpUtils.urlEncode; import static org.jclouds.http.HttpUtils.urlEncode;
import static org.jclouds.http.Payloads.newPayload; import static org.jclouds.http.Payloads.newPayload;
import static org.jclouds.http.Payloads.newStringPayload;
import static org.jclouds.http.Payloads.newUrlEncodedFormPayload;
import static org.jclouds.util.Utils.replaceTokens; import static org.jclouds.util.Utils.replaceTokens;
import java.io.InputStream; import java.io.InputStream;
@ -76,6 +72,7 @@ import org.jclouds.http.HttpResponse;
import org.jclouds.http.HttpUtils; import org.jclouds.http.HttpUtils;
import org.jclouds.http.Payload; import org.jclouds.http.Payload;
import org.jclouds.http.PayloadEnclosing; import org.jclouds.http.PayloadEnclosing;
import org.jclouds.http.Payloads;
import org.jclouds.http.functions.ParseSax; import org.jclouds.http.functions.ParseSax;
import org.jclouds.http.functions.ParseURIFromListOrLocationHeaderIf20x; import org.jclouds.http.functions.ParseURIFromListOrLocationHeaderIf20x;
import org.jclouds.http.functions.ReleasePayloadAndReturn; import org.jclouds.http.functions.ReleasePayloadAndReturn;
@ -93,6 +90,7 @@ import org.jclouds.rest.Binder;
import org.jclouds.rest.InputParamValidator; import org.jclouds.rest.InputParamValidator;
import org.jclouds.rest.InvocationContext; import org.jclouds.rest.InvocationContext;
import org.jclouds.rest.annotations.BinderParam; import org.jclouds.rest.annotations.BinderParam;
import org.jclouds.rest.annotations.Caller;
import org.jclouds.rest.annotations.Delegate; import org.jclouds.rest.annotations.Delegate;
import org.jclouds.rest.annotations.Endpoint; import org.jclouds.rest.annotations.Endpoint;
import org.jclouds.rest.annotations.EndpointParam; import org.jclouds.rest.annotations.EndpointParam;
@ -116,6 +114,7 @@ import org.jclouds.rest.functions.MapHttp4xxCodesToExceptions;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Function; import com.google.common.base.Function;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
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.MapMaker; import com.google.common.collect.MapMaker;
@ -136,51 +135,48 @@ public class RestAnnotationProcessor<T> {
@Resource @Resource
protected Logger logger = Logger.NULL; protected Logger logger = Logger.NULL;
ClassMethodArgs caller;
@Inject(optional = true)
public void setCaller(@Named("caller") ClassMethodArgs caller) {
seedCache(caller.getMethod().getDeclaringClass());
this.caller = caller;
}
private final Class<T> declaring; private final Class<T> declaring;
private final Map<Method, Map<Integer, Set<Annotation>>> methodToIndexOfParamToDecoratorParamAnnotation = createMethodToIndexOfParamToAnnotation(BinderParam.class); static final Map<Method, Map<Integer, Set<Annotation>>> methodToIndexOfParamToDecoratorParamAnnotation = createMethodToIndexOfParamToAnnotation(BinderParam.class);
private final Map<Method, Map<Integer, Set<Annotation>>> methodToIndexOfParamToHeaderParamAnnotations = createMethodToIndexOfParamToAnnotation(HeaderParam.class); static final Map<Method, Map<Integer, Set<Annotation>>> methodToIndexOfParamToHeaderParamAnnotations = createMethodToIndexOfParamToAnnotation(HeaderParam.class);
private final Map<Method, Map<Integer, Set<Annotation>>> methodToIndexOfParamToEndpointAnnotations = createMethodToIndexOfParamToAnnotation(Endpoint.class); static final Map<Method, Map<Integer, Set<Annotation>>> methodToIndexOfParamToEndpointAnnotations = createMethodToIndexOfParamToAnnotation(Endpoint.class);
private final Map<Method, Map<Integer, Set<Annotation>>> methodToIndexOfParamToEndpointParamAnnotations = createMethodToIndexOfParamToAnnotation(EndpointParam.class); static final Map<Method, Map<Integer, Set<Annotation>>> methodToIndexOfParamToEndpointParamAnnotations = createMethodToIndexOfParamToAnnotation(EndpointParam.class);
private final Map<Method, Map<Integer, Set<Annotation>>> methodToIndexOfParamToMatrixParamAnnotations = createMethodToIndexOfParamToAnnotation(MatrixParam.class); static final Map<Method, Map<Integer, Set<Annotation>>> methodToIndexOfParamToMatrixParamAnnotations = createMethodToIndexOfParamToAnnotation(MatrixParam.class);
private final Map<Method, Map<Integer, Set<Annotation>>> methodToIndexOfParamToFormParamAnnotations = createMethodToIndexOfParamToAnnotation(FormParam.class); static final Map<Method, Map<Integer, Set<Annotation>>> methodToIndexOfParamToFormParamAnnotations = createMethodToIndexOfParamToAnnotation(FormParam.class);
private final Map<Method, Map<Integer, Set<Annotation>>> methodToIndexOfParamToQueryParamAnnotations = createMethodToIndexOfParamToAnnotation(QueryParam.class); static final Map<Method, Map<Integer, Set<Annotation>>> methodToIndexOfParamToQueryParamAnnotations = createMethodToIndexOfParamToAnnotation(QueryParam.class);
private final Map<Method, Map<Integer, Set<Annotation>>> methodToIndexOfParamToPathParamAnnotations = createMethodToIndexOfParamToAnnotation(PathParam.class); static final Map<Method, Map<Integer, Set<Annotation>>> methodToIndexOfParamToPathParamAnnotations = createMethodToIndexOfParamToAnnotation(PathParam.class);
private final Map<Method, Map<Integer, Set<Annotation>>> methodToIndexOfParamToPostParamAnnotations = createMethodToIndexOfParamToAnnotation(MapPayloadParam.class); static final Map<Method, Map<Integer, Set<Annotation>>> methodToIndexOfParamToPostParamAnnotations = createMethodToIndexOfParamToAnnotation(MapPayloadParam.class);
private final Map<Method, Map<Integer, Set<Annotation>>> methodToIndexOfParamToPartParamAnnotations = createMethodToIndexOfParamToAnnotation(PartParam.class); static final Map<Method, Map<Integer, Set<Annotation>>> methodToIndexOfParamToPartParamAnnotations = createMethodToIndexOfParamToAnnotation(PartParam.class);
private final Map<Method, Map<Integer, Set<Annotation>>> methodToIndexOfParamToParamParserAnnotations = createMethodToIndexOfParamToAnnotation(ParamParser.class); static final Map<Method, Map<Integer, Set<Annotation>>> methodToIndexOfParamToParamParserAnnotations = createMethodToIndexOfParamToAnnotation(ParamParser.class);
private final Map<MethodKey, Method> delegationMap = newHashMap(); static final Map<MethodKey, Method> delegationMap = newHashMap();
static Map<Method, Map<Integer, Set<Annotation>>> createMethodToIndexOfParamToAnnotation( static Map<Method, Map<Integer, Set<Annotation>>> createMethodToIndexOfParamToAnnotation(
final Class<? extends Annotation> annotation) { final Class<? extends Annotation> annotation) {
return new MapMaker().makeComputingMap(new Function<Method, Map<Integer, Set<Annotation>>>() { return new MapMaker()
.makeComputingMap(new Function<Method, Map<Integer, Set<Annotation>>>() {
public Map<Integer, Set<Annotation>> apply(final Method method) { public Map<Integer, Set<Annotation>> apply(final Method method) {
return new MapMaker().makeComputingMap(new GetAnnotationsForMethodParameterIndex( return new MapMaker()
.makeComputingMap(new GetAnnotationsForMethodParameterIndex(
method, annotation)); method, annotation));
} }
}); });
} }
static class GetAnnotationsForMethodParameterIndex implements Function<Integer, Set<Annotation>> { static class GetAnnotationsForMethodParameterIndex implements
Function<Integer, Set<Annotation>> {
private final Method method; private final Method method;
private final Class<?> clazz; private final Class<?> clazz;
protected GetAnnotationsForMethodParameterIndex(Method method, Class<?> clazz) { protected GetAnnotationsForMethodParameterIndex(Method method,
Class<?> clazz) {
this.method = method; this.method = method;
this.clazz = clazz; this.clazz = clazz;
} }
public Set<Annotation> apply(final Integer index) { public Set<Annotation> apply(final Integer index) {
Set<Annotation> keys = new HashSet<Annotation>(); Set<Annotation> keys = new HashSet<Annotation>();
List<Annotation> parameterAnnotations = newArrayList(method.getParameterAnnotations()[index]); List<Annotation> parameterAnnotations = newArrayList(method
.getParameterAnnotations()[index]);
Collection<Annotation> filtered = filter(parameterAnnotations, Collection<Annotation> filtered = filter(parameterAnnotations,
new Predicate<Annotation>() { new Predicate<Annotation>() {
public boolean apply(Annotation input) { public boolean apply(Annotation input) {
@ -231,13 +227,15 @@ public class RestAnnotationProcessor<T> {
private InputParamValidator inputParamValidator; private InputParamValidator inputParamValidator;
@VisibleForTesting @VisibleForTesting
Function<HttpResponse, ?> createResponseParser(Method method, HttpRequest request) { Function<HttpResponse, ?> createResponseParser(Method method,
HttpRequest request) {
return createResponseParser(parserFactory, injector, method, request); return createResponseParser(parserFactory, injector, method, request);
} }
@VisibleForTesting @VisibleForTesting
public static Function<HttpResponse, ?> createResponseParser(ParseSax.Factory parserFactory, public static Function<HttpResponse, ?> createResponseParser(
Injector injector, Method method, HttpRequest request) { ParseSax.Factory parserFactory, Injector injector, Method method,
HttpRequest request) {
Function<HttpResponse, ?> transformer; Function<HttpResponse, ?> transformer;
Class<? extends HandlerWithResult<?>> handler = getSaxResponseParserClassOrNull(method); Class<? extends HandlerWithResult<?>> handler = getSaxResponseParserClassOrNull(method);
if (handler != null) { if (handler != null) {
@ -246,7 +244,8 @@ public class RestAnnotationProcessor<T> {
transformer = injector.getInstance(getParserOrThrowException(method)); transformer = injector.getInstance(getParserOrThrowException(method));
} }
if (transformer instanceof InvocationContext) { if (transformer instanceof InvocationContext) {
((InvocationContext) transformer).setContext((GeneratedHttpRequest<?>) request); ((InvocationContext) transformer)
.setContext((GeneratedHttpRequest<?>) request);
} }
return transformer; return transformer;
} }
@ -254,7 +253,8 @@ public class RestAnnotationProcessor<T> {
@VisibleForTesting @VisibleForTesting
Function<Exception, ?> createExceptionParserOrThrowResourceNotFoundOn404IfNoAnnotation( Function<Exception, ?> createExceptionParserOrThrowResourceNotFoundOn404IfNoAnnotation(
Method method) { Method method) {
return createExceptionParserOrThrowResourceNotFoundOn404IfNoAnnotation(injector, method); return createExceptionParserOrThrowResourceNotFoundOn404IfNoAnnotation(
injector, method);
} }
@VisibleForTesting @VisibleForTesting
@ -269,8 +269,9 @@ public class RestAnnotationProcessor<T> {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Inject @Inject
public RestAnnotationProcessor(Injector injector, ParseSax.Factory parserFactory, public RestAnnotationProcessor(Injector injector,
HttpUtils utils, TypeLiteral<T> typeLiteral) { ParseSax.Factory parserFactory, HttpUtils utils,
TypeLiteral<T> typeLiteral) {
this.declaring = (Class<T>) typeLiteral.getRawType(); this.declaring = (Class<T>) typeLiteral.getRawType();
this.injector = injector; this.injector = injector;
this.parserFactory = parserFactory; this.parserFactory = parserFactory;
@ -294,17 +295,27 @@ public class RestAnnotationProcessor<T> {
for (Method method : methods) { for (Method method : methods) {
if (isHttpMethod(method)) { if (isHttpMethod(method)) {
for (int index = 0; index < method.getParameterTypes().length; index++) { for (int index = 0; index < method.getParameterTypes().length; index++) {
methodToIndexOfParamToDecoratorParamAnnotation.get(method).get(index); methodToIndexOfParamToDecoratorParamAnnotation.get(method).get(
methodToIndexOfParamToHeaderParamAnnotations.get(method).get(index); index);
methodToIndexOfParamToMatrixParamAnnotations.get(method).get(index); methodToIndexOfParamToHeaderParamAnnotations.get(method).get(
methodToIndexOfParamToFormParamAnnotations.get(method).get(index); index);
methodToIndexOfParamToQueryParamAnnotations.get(method).get(index); methodToIndexOfParamToMatrixParamAnnotations.get(method).get(
index);
methodToIndexOfParamToFormParamAnnotations.get(method)
.get(index);
methodToIndexOfParamToQueryParamAnnotations.get(method).get(
index);
methodToIndexOfParamToEndpointAnnotations.get(method).get(index); methodToIndexOfParamToEndpointAnnotations.get(method).get(index);
methodToIndexOfParamToEndpointParamAnnotations.get(method).get(index); methodToIndexOfParamToEndpointParamAnnotations.get(method).get(
methodToIndexOfParamToPathParamAnnotations.get(method).get(index); index);
methodToIndexOfParamToPostParamAnnotations.get(method).get(index); methodToIndexOfParamToPathParamAnnotations.get(method)
methodToIndexOfParamToParamParserAnnotations.get(method).get(index); .get(index);
methodToIndexOfParamToPartParamAnnotations.get(method).get(index); methodToIndexOfParamToPostParamAnnotations.get(method)
.get(index);
methodToIndexOfParamToParamParserAnnotations.get(method).get(
index);
methodToIndexOfParamToPartParamAnnotations.get(method)
.get(index);
methodToIndexesOfOptions.get(method); methodToIndexesOfOptions.get(method);
} }
delegationMap.put(new MethodKey(method), method); delegationMap.put(new MethodKey(method), method);
@ -315,7 +326,9 @@ public class RestAnnotationProcessor<T> {
} else if (method.isAnnotationPresent(Delegate.class)) { } else if (method.isAnnotationPresent(Delegate.class)) {
logger.debug("skipping delegate method %s", method); logger.debug("skipping delegate method %s", method);
} else if (!method.getName().startsWith("new")) { } else if (!method.getName().startsWith("new")) {
logger.trace("Method is not annotated as either http or constant: %s", method); logger.trace(
"Method is not annotated as either http or constant: %s",
method);
} }
} }
} }
@ -362,19 +375,26 @@ public class RestAnnotationProcessor<T> {
final Injector injector; final Injector injector;
ClassMethodArgs caller;
@Inject(optional = true)
public void setCaller(@Caller ClassMethodArgs caller) {
seedCache(caller.getMethod().getDeclaringClass());
this.caller = caller;
}
@Inject(optional = true)
@Caller
@Nullable
URI callerEndpoint;
public GeneratedHttpRequest<T> createRequest(Method method, Object... args) { public GeneratedHttpRequest<T> createRequest(Method method, Object... args) {
inputParamValidator.validateMethodParametersOrThrow(method, args); inputParamValidator.validateMethodParametersOrThrow(method, args);
URI endpoint; URI endpoint = callerEndpoint;
try { try {
if (caller != null) { if (endpoint == null)
try { endpoint = getEndpointFor(method, args, injector);
endpoint = getEndpointFor(caller.getMethod(), caller.getArgs());
} catch (IllegalStateException e) {
endpoint = getEndpointFor(method, args);
}
} else {
endpoint = getEndpointFor(method, args);
}
} catch (IllegalStateException e) { } catch (IllegalStateException e) {
endpoint = injector.getInstance(Key.get(URI.class, endpoint = injector.getInstance(Key.get(URI.class,
org.jclouds.rest.annotations.Provider.class)); org.jclouds.rest.annotations.Provider.class));
@ -386,34 +406,44 @@ public class RestAnnotationProcessor<T> {
Multimap<String, String> tokenValues = LinkedHashMultimap.create(); Multimap<String, String> tokenValues = LinkedHashMultimap.create();
if (caller != null) { if (caller != null) {
builder.path(getPath(caller.getMethod().getDeclaringClass(), caller.getMethod(), caller builder.path(getPath(caller.getMethod().getDeclaringClass(), caller
.getArgs())); .getMethod(), caller.getArgs()));
} }
tokenValues.putAll(addPathAndGetTokens(declaring, method, args, builder)); tokenValues.putAll(addPathAndGetTokens(declaring, method, args, builder));
Multimap<String, String> formParams = addFormParams(tokenValues.entries(), method, args); Multimap<String, String> formParams = addFormParams(
Multimap<String, String> queryParams = addQueryParams(tokenValues.entries(), method, args); tokenValues.entries(), method, args);
Multimap<String, String> queryParams = addQueryParams(tokenValues
.entries(), method, args);
addMatrixParams(builder, tokenValues.entries(), method, args); addMatrixParams(builder, tokenValues.entries(), method, args);
Multimap<String, String> headers = buildHeaders(tokenValues.entries(), method, args); Multimap<String, String> headers = buildHeaders(tokenValues.entries(),
method, args);
Payload payload = null; Payload payload = null;
HttpRequestOptions options = findOptionsIn(method, args); HttpRequestOptions options = findOptionsIn(method, args);
if (options != null) { if (options != null) {
injector.injectMembers(options);// TODO test case injector.injectMembers(options);// TODO test case
for (Entry<String, String> header : options.buildRequestHeaders().entries()) { for (Entry<String, String> header : options.buildRequestHeaders()
headers.put(header.getKey(), replaceTokens(header.getValue(), tokenValues.entries())); .entries()) {
headers.put(header.getKey(), replaceTokens(header.getValue(),
tokenValues.entries()));
} }
for (Entry<String, String> matrix : options.buildMatrixParameters().entries()) { for (Entry<String, String> matrix : options.buildMatrixParameters()
builder.matrixParam(matrix.getKey(), replaceTokens(matrix.getValue(), tokenValues .entries()) {
.entries())); builder.matrixParam(matrix.getKey(), replaceTokens(matrix
.getValue(), tokenValues.entries()));
} }
for (Entry<String, String> query : options.buildQueryParameters().entries()) { for (Entry<String, String> query : options.buildQueryParameters()
queryParams.put(query.getKey(), replaceTokens(query.getValue(), tokenValues.entries())); .entries()) {
queryParams.put(query.getKey(), replaceTokens(query.getValue(),
tokenValues.entries()));
} }
for (Entry<String, String> form : options.buildFormParameters().entries()) { for (Entry<String, String> form : options.buildFormParameters()
formParams.put(form.getKey(), replaceTokens(form.getValue(), tokenValues.entries())); .entries()) {
formParams.put(form.getKey(), replaceTokens(form.getValue(),
tokenValues.entries()));
} }
String pathSuffix = options.buildPathSuffix(); String pathSuffix = options.buildPathSuffix();
@ -422,7 +452,7 @@ public class RestAnnotationProcessor<T> {
} }
String stringPayload = options.buildStringPayload(); String stringPayload = options.buildStringPayload();
if (stringPayload != null) if (stringPayload != null)
payload = newStringPayload(stringPayload); payload = Payloads.newStringPayload(stringPayload);
} }
if (queryParams.size() > 0) { if (queryParams.size() > 0) {
@ -437,22 +467,24 @@ public class RestAnnotationProcessor<T> {
throw new IllegalStateException(e); throw new IllegalStateException(e);
} }
GeneratedHttpRequest<T> request = new GeneratedHttpRequest<T>(httpMethod, endpoint, skips, GeneratedHttpRequest<T> request = new GeneratedHttpRequest<T>(httpMethod,
declaring, method, args); endpoint, skips, declaring, method, args);
addHostHeaderIfAnnotatedWithVirtualHost(headers, request.getEndpoint().getHost(), method); addHostHeaderIfAnnotatedWithVirtualHost(headers, request.getEndpoint()
.getHost(), method);
addFiltersIfAnnotated(method, request); addFiltersIfAnnotated(method, request);
if (payload == null) if (payload == null)
payload = findPayloadInArgs(args); payload = findPayloadInArgs(args);
List<? extends Part> parts = getParts(method, args, concat(tokenValues.entries(), formParams List<? extends Part> parts = getParts(method, args, Iterables.concat(
.entries())); tokenValues.entries(), formParams.entries()));
if (parts.size() > 0) { if (parts.size() > 0) {
if (formParams.size() > 0) { if (formParams.size() > 0) {
parts = newLinkedList(concat(transform(formParams.entries(), ENTRY_TO_PART), parts)); parts = newLinkedList(Iterables.concat(Iterables.transform(
formParams.entries(), ENTRY_TO_PART), parts));
} }
payload = new MultipartForm(BOUNDARY, parts); payload = new MultipartForm(BOUNDARY, parts);
} else if (formParams.size() > 0) { } else if (formParams.size() > 0) {
payload = newUrlEncodedFormPayload(formParams, skips); payload = Payloads.newUrlEncodedFormPayload(formParams, skips);
} }
if (payload != null) { if (payload != null) {
request.setPayload(payload); request.setPayload(payload);
@ -469,11 +501,12 @@ public class RestAnnotationProcessor<T> {
builder.path(clazz); builder.path(clazz);
builder.path(method); builder.path(method);
return builder.buildFromEncodedMap( return builder.buildFromEncodedMap(
convertUnsafe(encodeValues(getPathParamKeyValues(method, args), skips))).getPath(); convertUnsafe(encodeValues(getPathParamKeyValues(method, args),
skips))).getPath();
} }
private Multimap<String, String> addPathAndGetTokens(Class<?> clazz, Method method, private Multimap<String, String> addPathAndGetTokens(Class<?> clazz,
Object[] args, UriBuilder builder) { Method method, Object[] args, UriBuilder builder) {
if (clazz.isAnnotationPresent(Path.class)) if (clazz.isAnnotationPresent(Path.class))
builder.path(clazz); builder.path(clazz);
builder.path(method); builder.path(method);
@ -485,15 +518,18 @@ public class RestAnnotationProcessor<T> {
return replaceQuery(uriBuilderProvider, in, newQuery, sorter, skips); return replaceQuery(uriBuilderProvider, in, newQuery, sorter, skips);
} }
public static URI replaceQuery(Provider<UriBuilder> uriBuilderProvider, URI in, String newQuery, public static URI replaceQuery(Provider<UriBuilder> uriBuilderProvider,
URI in, String newQuery,
@Nullable Comparator<Entry<String, String>> sorter, char... skips) { @Nullable Comparator<Entry<String, String>> sorter, char... skips) {
UriBuilder builder = uriBuilderProvider.get().uri(in); UriBuilder builder = uriBuilderProvider.get().uri(in);
builder.replaceQuery(makeQueryLine(parseQueryToMap(newQuery), sorter, skips)); builder.replaceQuery(makeQueryLine(parseQueryToMap(newQuery), sorter,
skips));
return builder.build(); return builder.build();
} }
private void addMatrixParams(UriBuilder builder, Collection<Entry<String, String>> tokenValues, private void addMatrixParams(UriBuilder builder,
Method method, Object... args) { Collection<Entry<String, String>> tokenValues, Method method,
Object... args) {
if (declaring.isAnnotationPresent(MatrixParams.class)) { if (declaring.isAnnotationPresent(MatrixParams.class)) {
MatrixParams matrix = declaring.getAnnotation(MatrixParams.class); MatrixParams matrix = declaring.getAnnotation(MatrixParams.class);
addMatrix(builder, matrix, tokenValues); addMatrix(builder, matrix, tokenValues);
@ -504,13 +540,16 @@ public class RestAnnotationProcessor<T> {
addMatrix(builder, matrix, tokenValues); addMatrix(builder, matrix, tokenValues);
} }
for (Entry<String, String> matrix : getMatrixParamKeyValues(method, args).entries()) { for (Entry<String, String> matrix : getMatrixParamKeyValues(method, args)
builder.matrixParam(matrix.getKey(), replaceTokens(matrix.getValue(), tokenValues)); .entries()) {
builder.matrixParam(matrix.getKey(), replaceTokens(matrix.getValue(),
tokenValues));
} }
} }
private Multimap<String, String> addFormParams(Collection<Entry<String, String>> tokenValues, private Multimap<String, String> addFormParams(
Method method, Object... args) { Collection<Entry<String, String>> tokenValues, Method method,
Object... args) {
Multimap<String, String> formMap = LinkedListMultimap.create(); Multimap<String, String> formMap = LinkedListMultimap.create();
if (declaring.isAnnotationPresent(FormParams.class)) { if (declaring.isAnnotationPresent(FormParams.class)) {
FormParams form = declaring.getAnnotation(FormParams.class); FormParams form = declaring.getAnnotation(FormParams.class);
@ -522,14 +561,17 @@ public class RestAnnotationProcessor<T> {
addForm(formMap, form, tokenValues); addForm(formMap, form, tokenValues);
} }
for (Entry<String, String> form : getFormParamKeyValues(method, args).entries()) { for (Entry<String, String> form : getFormParamKeyValues(method, args)
formMap.put(form.getKey(), replaceTokens(form.getValue(), tokenValues)); .entries()) {
formMap
.put(form.getKey(), replaceTokens(form.getValue(), tokenValues));
} }
return formMap; return formMap;
} }
private Multimap<String, String> addQueryParams(Collection<Entry<String, String>> tokenValues, private Multimap<String, String> addQueryParams(
Method method, Object... args) { Collection<Entry<String, String>> tokenValues, Method method,
Object... args) {
Multimap<String, String> queryMap = LinkedListMultimap.create(); Multimap<String, String> queryMap = LinkedListMultimap.create();
if (declaring.isAnnotationPresent(QueryParams.class)) { if (declaring.isAnnotationPresent(QueryParams.class)) {
QueryParams query = declaring.getAnnotation(QueryParams.class); QueryParams query = declaring.getAnnotation(QueryParams.class);
@ -541,8 +583,10 @@ public class RestAnnotationProcessor<T> {
addQuery(queryMap, query, tokenValues); addQuery(queryMap, query, tokenValues);
} }
for (Entry<String, String> query : getQueryParamKeyValues(method, args).entries()) { for (Entry<String, String> query : getQueryParamKeyValues(method, args)
queryMap.put(query.getKey(), replaceTokens(query.getValue(), tokenValues)); .entries()) {
queryMap.put(query.getKey(), replaceTokens(query.getValue(),
tokenValues));
} }
return queryMap; return queryMap;
} }
@ -554,19 +598,21 @@ public class RestAnnotationProcessor<T> {
formParams.removeAll(form.keys()[i]); formParams.removeAll(form.keys()[i]);
formParams.put(form.keys()[i], null); formParams.put(form.keys()[i], null);
} else { } else {
formParams.put(form.keys()[i], replaceTokens(form.values()[i], tokenValues)); formParams.put(form.keys()[i], replaceTokens(form.values()[i],
tokenValues));
} }
} }
} }
private void addQuery(Multimap<String, String> queryParams, QueryParams query, private void addQuery(Multimap<String, String> queryParams,
Collection<Entry<String, String>> tokenValues) { QueryParams query, Collection<Entry<String, String>> tokenValues) {
for (int i = 0; i < query.keys().length; i++) { for (int i = 0; i < query.keys().length; i++) {
if (query.values()[i].equals(QueryParams.NULL)) { if (query.values()[i].equals(QueryParams.NULL)) {
queryParams.removeAll(query.keys()[i]); queryParams.removeAll(query.keys()[i]);
queryParams.put(query.keys()[i], null); queryParams.put(query.keys()[i], null);
} else { } else {
queryParams.put(query.keys()[i], replaceTokens(query.values()[i], tokenValues)); queryParams.put(query.keys()[i], replaceTokens(query.values()[i],
tokenValues));
} }
} }
} }
@ -577,56 +623,82 @@ public class RestAnnotationProcessor<T> {
if (matrix.values()[i].equals(MatrixParams.NULL)) { if (matrix.values()[i].equals(MatrixParams.NULL)) {
builder.replaceMatrix(matrix.keys()[i]); builder.replaceMatrix(matrix.keys()[i]);
} else { } else {
builder.matrixParam(matrix.keys()[i], replaceTokens(matrix.values()[i], tokenValues)); builder.matrixParam(matrix.keys()[i], replaceTokens(
matrix.values()[i], tokenValues));
} }
} }
} }
private void addFiltersIfAnnotated(Method method, HttpRequest request) { private void addFiltersIfAnnotated(Method method, HttpRequest request) {
if (declaring.isAnnotationPresent(RequestFilters.class)) { if (declaring.isAnnotationPresent(RequestFilters.class)) {
for (Class<? extends HttpRequestFilter> clazz : declaring.getAnnotation( for (Class<? extends HttpRequestFilter> clazz : declaring
RequestFilters.class).value()) { .getAnnotation(RequestFilters.class).value()) {
HttpRequestFilter instance = injector.getInstance(clazz); HttpRequestFilter instance = injector.getInstance(clazz);
request.getFilters().add(instance); request.getFilters().add(instance);
logger.trace("%s - adding filter %s from annotation on %s", request, instance, logger.trace("%s - adding filter %s from annotation on %s",
declaring.getName()); request, instance, declaring.getName());
} }
} }
if (method.isAnnotationPresent(RequestFilters.class)) { if (method.isAnnotationPresent(RequestFilters.class)) {
if (method.isAnnotationPresent(OverrideRequestFilters.class)) if (method.isAnnotationPresent(OverrideRequestFilters.class))
request.getFilters().clear(); request.getFilters().clear();
for (Class<? extends HttpRequestFilter> clazz : method.getAnnotation(RequestFilters.class) for (Class<? extends HttpRequestFilter> clazz : method.getAnnotation(
.value()) { RequestFilters.class).value()) {
HttpRequestFilter instance = injector.getInstance(clazz); HttpRequestFilter instance = injector.getInstance(clazz);
request.getFilters().add(instance); request.getFilters().add(instance);
logger.trace("%s - adding filter %s from annotation on %s", request, instance, method logger.trace("%s - adding filter %s from annotation on %s",
.getName()); request, instance, method.getName());
} }
} }
} }
@VisibleForTesting @VisibleForTesting
URI getEndpointInParametersOrNull(Method method, Object... args) { public static URI getEndpointInParametersOrNull(Method method,
Map<Integer, Set<Annotation>> map = indexWithOnlyOneAnnotation(method, "@EndpointParam", Object[] args, Injector injector) {
methodToIndexOfParamToEndpointParamAnnotations); Map<Integer, Set<Annotation>> map = RestAnnotationProcessor
.indexWithOnlyOneAnnotation(
method,
"@EndpointParam",
RestAnnotationProcessor.methodToIndexOfParamToEndpointParamAnnotations);
if (map.size() == 1 && args.length > 0) { if (map.size() == 1 && args.length > 0) {
EndpointParam annotation = (EndpointParam) map.values().iterator().next().iterator() EndpointParam annotation = (EndpointParam) map.values().iterator()
.next(); .next().iterator().next();
int index = map.keySet().iterator().next(); int index = map.keySet().iterator().next();
Function<Object, URI> parser = injector.getInstance(annotation.parser()); Function<Object, URI> parser = injector.getInstance(annotation
.parser());
try { try {
URI returnVal = parser.apply(args[index]); URI returnVal = parser.apply(args[index]);
checkArgument(returnVal != null, String.format( checkArgument(returnVal != null, String.format(
"endpoint for [%s] not configured for %s", args[index], method)); "endpoint for [%s] not configured for %s", args[index],
method));
return returnVal; return returnVal;
} catch (NullPointerException e) { } catch (NullPointerException e) {
logger.error("argument at index %d on method %s", index, method); throw new IllegalArgumentException(String.format(
throw e; "argument at index %d on method %s", index, method), e);
} }
} }
return null; return null;
} }
public static URI getEndpointFor(Method method, Object[] args, Injector injector) {
URI endpoint = getEndpointInParametersOrNull(method, args, injector);
if (endpoint == null) {
Endpoint annotation;
if (method.isAnnotationPresent(Endpoint.class)) {
annotation = method.getAnnotation(Endpoint.class);
} else if (method.getDeclaringClass().isAnnotationPresent(
Endpoint.class)) {
annotation = method.getDeclaringClass().getAnnotation(
Endpoint.class);
} else {
throw new IllegalStateException(
"no annotations on class or method: " + method);
}
return injector.getInstance(Key.get(URI.class, annotation.value()));
}
return endpoint;
}
public static final TypeLiteral<ListenableFuture<Boolean>> futureBooleanLiteral = new TypeLiteral<ListenableFuture<Boolean>>() { public static final TypeLiteral<ListenableFuture<Boolean>> futureBooleanLiteral = new TypeLiteral<ListenableFuture<Boolean>>() {
}; };
@ -640,43 +712,52 @@ public class RestAnnotationProcessor<T> {
public static final TypeLiteral<ListenableFuture<InputStream>> futureInputStreamLiteral = new TypeLiteral<ListenableFuture<InputStream>>() { public static final TypeLiteral<ListenableFuture<InputStream>> futureInputStreamLiteral = new TypeLiteral<ListenableFuture<InputStream>>() {
}; };
public static Class<? extends Function<HttpResponse, ?>> getParserOrThrowException(Method method) { public static Class<? extends Function<HttpResponse, ?>> getParserOrThrowException(
Method method) {
ResponseParser annotation = method.getAnnotation(ResponseParser.class); ResponseParser annotation = method.getAnnotation(ResponseParser.class);
if (annotation == null) { if (annotation == null) {
if (method.getReturnType().equals(boolean.class) if (method.getReturnType().equals(boolean.class)
|| method.getReturnType().equals(Boolean.class) || method.getReturnType().equals(Boolean.class)
|| TypeLiteral.get(method.getGenericReturnType()).equals(futureBooleanLiteral)) { || TypeLiteral.get(method.getGenericReturnType()).equals(
futureBooleanLiteral)) {
return ReturnTrueIf2xx.class; return ReturnTrueIf2xx.class;
} else if (method.getReturnType().equals(String.class) } else if (method.getReturnType().equals(String.class)
|| TypeLiteral.get(method.getGenericReturnType()).equals(futureStringLiteral)) { || TypeLiteral.get(method.getGenericReturnType()).equals(
futureStringLiteral)) {
return ReturnStringIf2xx.class; return ReturnStringIf2xx.class;
} else if (method.getReturnType().equals(void.class) } else if (method.getReturnType().equals(void.class)
|| TypeLiteral.get(method.getGenericReturnType()).equals(futureVoidLiteral)) { || TypeLiteral.get(method.getGenericReturnType()).equals(
futureVoidLiteral)) {
return ReleasePayloadAndReturn.class; return ReleasePayloadAndReturn.class;
} else if (method.getReturnType().equals(URI.class) } else if (method.getReturnType().equals(URI.class)
|| TypeLiteral.get(method.getGenericReturnType()).equals(futureURILiteral)) { || TypeLiteral.get(method.getGenericReturnType()).equals(
futureURILiteral)) {
return ParseURIFromListOrLocationHeaderIf20x.class; return ParseURIFromListOrLocationHeaderIf20x.class;
} else if (method.getReturnType().equals(InputStream.class) } else if (method.getReturnType().equals(InputStream.class)
|| TypeLiteral.get(method.getGenericReturnType()) || TypeLiteral.get(method.getGenericReturnType()).equals(
.equals(futureInputStreamLiteral)) { futureInputStreamLiteral)) {
return ReturnInputStream.class; return ReturnInputStream.class;
} else { } else {
throw new IllegalStateException( throw new IllegalStateException(
"You must specify a ResponseTransformer annotation on: " + method.toString()); "You must specify a ResponseTransformer annotation on: "
+ method.toString());
} }
} }
return annotation.value(); return annotation.value();
} }
public static Class<? extends HandlerWithResult<?>> getSaxResponseParserClassOrNull(Method method) { public static Class<? extends HandlerWithResult<?>> getSaxResponseParserClassOrNull(
XMLResponseParser annotation = method.getAnnotation(XMLResponseParser.class); Method method) {
XMLResponseParser annotation = method
.getAnnotation(XMLResponseParser.class);
if (annotation != null) { if (annotation != null) {
return annotation.value(); return annotation.value();
} }
return null; return null;
} }
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) {
if (arg instanceof Object[]) { if (arg instanceof Object[]) {
@ -712,16 +793,19 @@ public class RestAnnotationProcessor<T> {
private Multimap<String, String> constants = LinkedHashMultimap.create(); private Multimap<String, String> constants = LinkedHashMultimap.create();
public boolean isHttpMethod(Method method) { public boolean isHttpMethod(Method method) {
return method.isAnnotationPresent(Path.class) || IsHttpMethod.getHttpMethods(method) != null; return method.isAnnotationPresent(Path.class)
|| IsHttpMethod.getHttpMethods(method) != null;
} }
public boolean isConstantDeclaration(Method method) { public boolean isConstantDeclaration(Method method) {
return method.isAnnotationPresent(PathParam.class) && method.isAnnotationPresent(Named.class); return method.isAnnotationPresent(PathParam.class)
&& method.isAnnotationPresent(Named.class);
} }
public void bindConstant(Method method) { public void bindConstant(Method method) {
String key = method.getAnnotation(PathParam.class).value(); String key = method.getAnnotation(PathParam.class).value();
String value = injector.getInstance(Key.get(String.class, method.getAnnotation(Named.class))); String value = injector.getInstance(Key.get(String.class, method
.getAnnotation(Named.class)));
constants.put(key, value); constants.put(key, value);
} }
@ -735,18 +819,20 @@ public class RestAnnotationProcessor<T> {
return requests.iterator().next(); return requests.iterator().next();
} }
public void addHostHeaderIfAnnotatedWithVirtualHost(Multimap<String, String> headers, public void addHostHeaderIfAnnotatedWithVirtualHost(
String host, Method method) { Multimap<String, String> headers, String host, Method method) {
if (declaring.isAnnotationPresent(VirtualHost.class) if (declaring.isAnnotationPresent(VirtualHost.class)
|| method.isAnnotationPresent(VirtualHost.class)) { || method.isAnnotationPresent(VirtualHost.class)) {
headers.put(HOST, host); headers.put(HOST, host);
} }
} }
public void decorateRequest(GeneratedHttpRequest<T> request, Multimap<String, String> headers) { public void decorateRequest(GeneratedHttpRequest<T> request,
org.jclouds.rest.MapBinder mapBinder = getMapPayloadBinderOrNull(request.getJavaMethod(), Multimap<String, String> headers) {
org.jclouds.rest.MapBinder mapBinder = getMapPayloadBinderOrNull(request
.getJavaMethod(), request.getArgs());
Map<String, String> mapParams = buildPostParams(request.getJavaMethod(),
request.getArgs()); request.getArgs());
Map<String, String> mapParams = buildPostParams(request.getJavaMethod(), request.getArgs());
// MapPayloadBinder is only useful if there are parameters. We guard here // MapPayloadBinder is only useful if there are parameters. We guard here
// in case the // in case the
// MapPayloadBinder is also an PayloadBinder. If so, it can be used with // MapPayloadBinder is also an PayloadBinder. If so, it can be used with
@ -756,31 +842,37 @@ public class RestAnnotationProcessor<T> {
mapBinder.bindToRequest(request, mapParams); mapBinder.bindToRequest(request, mapParams);
} else { } else {
OUTER: for (Entry<Integer, Set<Annotation>> entry : filterValues( OUTER: for (Entry<Integer, Set<Annotation>> entry : filterValues(
methodToIndexOfParamToDecoratorParamAnnotation.get(request.getJavaMethod()), methodToIndexOfParamToDecoratorParamAnnotation.get(request
new Predicate<Set<Annotation>>() { .getJavaMethod()), new Predicate<Set<Annotation>>() {
public boolean apply(Set<Annotation> input) { public boolean apply(Set<Annotation> input) {
return input.size() >= 1; return input.size() >= 1;
} }
}).entrySet()) { }).entrySet()) {
boolean shouldBreak = false; boolean shouldBreak = false;
BinderParam payloadAnnotation = (BinderParam) entry.getValue().iterator().next(); BinderParam payloadAnnotation = (BinderParam) entry.getValue()
.iterator().next();
Binder binder = injector.getInstance(payloadAnnotation.value()); Binder binder = injector.getInstance(payloadAnnotation.value());
if (request.getArgs().length >= entry.getKey() + 1 if (request.getArgs().length >= entry.getKey() + 1
&& request.getArgs()[entry.getKey()] != null) { && request.getArgs()[entry.getKey()] != null) {
Object input; Object input;
Class<?> parameterType = request.getJavaMethod().getParameterTypes()[entry.getKey()]; Class<?> parameterType = request.getJavaMethod()
Class<? extends Object> argType = request.getArgs()[entry.getKey()].getClass(); .getParameterTypes()[entry.getKey()];
Class<? extends Object> argType = request.getArgs()[entry
.getKey()].getClass();
if (!argType.isArray() && request.getJavaMethod().isVarArgs() if (!argType.isArray() && request.getJavaMethod().isVarArgs()
&& parameterType.isArray()) { && parameterType.isArray()) {
int arrayLength = request.getArgs().length int arrayLength = request.getArgs().length
- request.getJavaMethod().getParameterTypes().length + 1; - request.getJavaMethod().getParameterTypes().length
+ 1;
if (arrayLength == 0) if (arrayLength == 0)
break OUTER; break OUTER;
input = (Object[]) Array.newInstance( input = (Object[]) Array.newInstance(request.getArgs()[entry
request.getArgs()[entry.getKey()].getClass(), arrayLength); .getKey()].getClass(), arrayLength);
System.arraycopy(request.getArgs(), entry.getKey(), input, 0, arrayLength); System.arraycopy(request.getArgs(), entry.getKey(), input, 0,
arrayLength);
shouldBreak = true; shouldBreak = true;
} else if (argType.isArray() && request.getJavaMethod().isVarArgs() } else if (argType.isArray()
&& request.getJavaMethod().isVarArgs()
&& parameterType.isArray()) { && parameterType.isArray()) {
input = request.getArgs()[entry.getKey()]; input = request.getArgs()[entry.getKey()];
} else { } else {
@ -801,19 +893,23 @@ public class RestAnnotationProcessor<T> {
utils.setPayloadPropertiesFromHeaders(headers, request); utils.setPayloadPropertiesFromHeaders(headers, request);
} }
protected Map<Integer, Set<Annotation>> indexWithOnlyOneAnnotation(Method method, public static Map<Integer, Set<Annotation>> indexWithOnlyOneAnnotation(
String description, Map<Method, Map<Integer, Set<Annotation>>> toRefine) { Method method, String description,
Map<Integer, Set<Annotation>> indexToPayloadAnnotation = filterValues(toRefine.get(method), Map<Method, Map<Integer, Set<Annotation>>> toRefine) {
new Predicate<Set<Annotation>>() { Map<Integer, Set<Annotation>> indexToPayloadAnnotation = filterValues(
toRefine.get(method), new Predicate<Set<Annotation>>() {
public boolean apply(Set<Annotation> input) { public boolean apply(Set<Annotation> input) {
return input.size() == 1; return input.size() == 1;
} }
}); });
if (indexToPayloadAnnotation.size() > 1) { if (indexToPayloadAnnotation.size() > 1) {
throw new IllegalStateException(String.format( throw new IllegalStateException(
"You must not specify more than one %s annotation on: %s; found %s", description, String
method.toString(), indexToPayloadAnnotation)); .format(
"You must not specify more than one %s annotation on: %s; found %s",
description, method.toString(),
indexToPayloadAnnotation));
} }
return indexToPayloadAnnotation; return indexToPayloadAnnotation;
} }
@ -845,13 +941,15 @@ public class RestAnnotationProcessor<T> {
return null; return null;
} }
public Multimap<String, String> buildHeaders(Collection<Entry<String, String>> tokenValues, public Multimap<String, String> buildHeaders(
Method method, final Object... args) { Collection<Entry<String, String>> tokenValues, Method method,
final Object... args) {
Multimap<String, String> headers = LinkedHashMultimap.create(); Multimap<String, String> headers = LinkedHashMultimap.create();
addHeaderIfAnnotationPresentOnMethod(headers, method, tokenValues); addHeaderIfAnnotationPresentOnMethod(headers, method, tokenValues);
Map<Integer, Set<Annotation>> indexToHeaderParam = methodToIndexOfParamToHeaderParamAnnotations Map<Integer, Set<Annotation>> indexToHeaderParam = methodToIndexOfParamToHeaderParamAnnotations
.get(method); .get(method);
for (Entry<Integer, Set<Annotation>> entry : indexToHeaderParam.entrySet()) { for (Entry<Integer, Set<Annotation>> entry : indexToHeaderParam
.entrySet()) {
for (Annotation key : entry.getValue()) { for (Annotation key : entry.getValue()) {
String value = args[entry.getKey()].toString(); String value = args[entry.getKey()].toString();
value = replaceTokens(value, tokenValues); value = replaceTokens(value, tokenValues);
@ -863,7 +961,8 @@ public class RestAnnotationProcessor<T> {
return headers; return headers;
} }
void addConsumesIfPresentOnTypeOrMethod(Multimap<String, String> headers, Method method) { void addConsumesIfPresentOnTypeOrMethod(Multimap<String, String> headers,
Method method) {
if (declaring.isAnnotationPresent(Consumes.class)) { if (declaring.isAnnotationPresent(Consumes.class)) {
Consumes header = declaring.getAnnotation(Consumes.class); Consumes header = declaring.getAnnotation(Consumes.class);
headers.replaceValues(ACCEPT, asList(header.value())); headers.replaceValues(ACCEPT, asList(header.value()));
@ -874,7 +973,8 @@ public class RestAnnotationProcessor<T> {
} }
} }
void addProducesIfPresentOnTypeOrMethod(Multimap<String, String> headers, Method method) { void addProducesIfPresentOnTypeOrMethod(Multimap<String, String> headers,
Method method) {
if (declaring.isAnnotationPresent(Produces.class)) { if (declaring.isAnnotationPresent(Produces.class)) {
Produces header = declaring.getAnnotation(Produces.class); Produces header = declaring.getAnnotation(Produces.class);
headers.replaceValues(CONTENT_TYPE, asList(header.value())); headers.replaceValues(CONTENT_TYPE, asList(header.value()));
@ -885,8 +985,9 @@ public class RestAnnotationProcessor<T> {
} }
} }
public void addHeaderIfAnnotationPresentOnMethod(Multimap<String, String> headers, public void addHeaderIfAnnotationPresentOnMethod(
Method method, Collection<Entry<String, String>> tokenValues) { Multimap<String, String> headers, Method method,
Collection<Entry<String, String>> tokenValues) {
if (declaring.isAnnotationPresent(Headers.class)) { if (declaring.isAnnotationPresent(Headers.class)) {
Headers header = declaring.getAnnotation(Headers.class); Headers header = declaring.getAnnotation(Headers.class);
addHeader(headers, header, tokenValues); addHeader(headers, header, tokenValues);
@ -928,7 +1029,8 @@ public class RestAnnotationProcessor<T> {
options.contentType(param.contentType()); options.contentType(param.contentType());
if (!PartParam.NO_FILENAME.equals(param.filename())) if (!PartParam.NO_FILENAME.equals(param.filename()))
options.filename(replaceTokens(param.filename(), iterable)); options.filename(replaceTokens(param.filename(), iterable));
Part part = Part.create(param.name(), newPayload(args[entry.getKey()]), options); Part part = Part.create(param.name(), newPayload(args[entry
.getKey()]), options);
parts.add(part); parts.add(part);
} }
} }
@ -946,7 +1048,8 @@ public class RestAnnotationProcessor<T> {
return null; return null;
} }
private Multimap<String, String> getPathParamKeyValues(Method method, Object... args) { private Multimap<String, String> getPathParamKeyValues(Method method,
Object... args) {
Multimap<String, String> pathParamValues = LinkedHashMultimap.create(); Multimap<String, String> pathParamValues = LinkedHashMultimap.create();
pathParamValues.putAll(constants); pathParamValues.putAll(constants);
Map<Integer, Set<Annotation>> indexToPathParam = methodToIndexOfParamToPathParamAnnotations Map<Integer, Set<Annotation>> indexToPathParam = methodToIndexOfParamToPathParamAnnotations
@ -956,12 +1059,15 @@ public class RestAnnotationProcessor<T> {
.get(method); .get(method);
for (Entry<Integer, Set<Annotation>> entry : indexToPathParam.entrySet()) { for (Entry<Integer, Set<Annotation>> entry : indexToPathParam.entrySet()) {
for (Annotation key : entry.getValue()) { for (Annotation key : entry.getValue()) {
Set<Annotation> extractors = indexToParamExtractor.get(entry.getKey()); Set<Annotation> extractors = indexToParamExtractor.get(entry
.getKey());
String paramKey = ((PathParam) key).value(); String paramKey = ((PathParam) key).value();
String paramValue; String paramValue;
if (extractors != null && extractors.size() > 0) { if (extractors != null && extractors.size() > 0) {
ParamParser extractor = (ParamParser) extractors.iterator().next(); ParamParser extractor = (ParamParser) extractors.iterator()
paramValue = injector.getInstance(extractor.value()).apply(args[entry.getKey()]); .next();
paramValue = injector.getInstance(extractor.value()).apply(
args[entry.getKey()]);
} else { } else {
paramValue = args[entry.getKey()].toString(); paramValue = args[entry.getKey()].toString();
} }
@ -972,15 +1078,16 @@ public class RestAnnotationProcessor<T> {
if (method.isAnnotationPresent(PathParam.class) if (method.isAnnotationPresent(PathParam.class)
&& method.isAnnotationPresent(ParamParser.class)) { && method.isAnnotationPresent(ParamParser.class)) {
String paramKey = method.getAnnotation(PathParam.class).value(); String paramKey = method.getAnnotation(PathParam.class).value();
String paramValue = injector.getInstance(method.getAnnotation(ParamParser.class).value()) String paramValue = injector.getInstance(
.apply(args); method.getAnnotation(ParamParser.class).value()).apply(args);
pathParamValues.put(paramKey, paramValue); pathParamValues.put(paramKey, paramValue);
} }
return pathParamValues; return pathParamValues;
} }
private Multimap<String, String> encodeValues(Multimap<String, String> unencoded, char... skips) { private Multimap<String, String> encodeValues(
Multimap<String, String> unencoded, char... skips) {
Multimap<String, String> encoded = LinkedHashMultimap.create(); Multimap<String, String> encoded = LinkedHashMultimap.create();
for (Entry<String, String> entry : unencoded.entries()) { for (Entry<String, String> entry : unencoded.entries()) {
encoded.put(entry.getKey(), urlEncode(entry.getValue(), skips)); encoded.put(entry.getKey(), urlEncode(entry.getValue(), skips));
@ -988,7 +1095,8 @@ public class RestAnnotationProcessor<T> {
return encoded; return encoded;
} }
private Multimap<String, String> getMatrixParamKeyValues(Method method, Object... args) { private Multimap<String, String> getMatrixParamKeyValues(Method method,
Object... args) {
Multimap<String, String> matrixParamValues = LinkedHashMultimap.create(); Multimap<String, String> matrixParamValues = LinkedHashMultimap.create();
matrixParamValues.putAll(constants); matrixParamValues.putAll(constants);
Map<Integer, Set<Annotation>> indexToMatrixParam = methodToIndexOfParamToMatrixParamAnnotations Map<Integer, Set<Annotation>> indexToMatrixParam = methodToIndexOfParamToMatrixParamAnnotations
@ -996,14 +1104,18 @@ public class RestAnnotationProcessor<T> {
Map<Integer, Set<Annotation>> indexToParamExtractor = methodToIndexOfParamToParamParserAnnotations Map<Integer, Set<Annotation>> indexToParamExtractor = methodToIndexOfParamToParamParserAnnotations
.get(method); .get(method);
for (Entry<Integer, Set<Annotation>> entry : indexToMatrixParam.entrySet()) { for (Entry<Integer, Set<Annotation>> entry : indexToMatrixParam
.entrySet()) {
for (Annotation key : entry.getValue()) { for (Annotation key : entry.getValue()) {
Set<Annotation> extractors = indexToParamExtractor.get(entry.getKey()); Set<Annotation> extractors = indexToParamExtractor.get(entry
.getKey());
String paramKey = ((MatrixParam) key).value(); String paramKey = ((MatrixParam) key).value();
String paramValue; String paramValue;
if (extractors != null && extractors.size() > 0) { if (extractors != null && extractors.size() > 0) {
ParamParser extractor = (ParamParser) extractors.iterator().next(); ParamParser extractor = (ParamParser) extractors.iterator()
paramValue = injector.getInstance(extractor.value()).apply(args[entry.getKey()]); .next();
paramValue = injector.getInstance(extractor.value()).apply(
args[entry.getKey()]);
} else { } else {
paramValue = args[entry.getKey()].toString(); paramValue = args[entry.getKey()].toString();
} }
@ -1014,15 +1126,16 @@ public class RestAnnotationProcessor<T> {
if (method.isAnnotationPresent(MatrixParam.class) if (method.isAnnotationPresent(MatrixParam.class)
&& method.isAnnotationPresent(ParamParser.class)) { && method.isAnnotationPresent(ParamParser.class)) {
String paramKey = method.getAnnotation(MatrixParam.class).value(); String paramKey = method.getAnnotation(MatrixParam.class).value();
String paramValue = injector.getInstance(method.getAnnotation(ParamParser.class).value()) String paramValue = injector.getInstance(
.apply(args); method.getAnnotation(ParamParser.class).value()).apply(args);
matrixParamValues.put(paramKey, paramValue); matrixParamValues.put(paramKey, paramValue);
} }
return matrixParamValues; return matrixParamValues;
} }
private Multimap<String, String> getFormParamKeyValues(Method method, Object... args) { private Multimap<String, String> getFormParamKeyValues(Method method,
Object... args) {
Multimap<String, String> formParamValues = LinkedHashMultimap.create(); Multimap<String, String> formParamValues = LinkedHashMultimap.create();
formParamValues.putAll(constants); formParamValues.putAll(constants);
Map<Integer, Set<Annotation>> indexToFormParam = methodToIndexOfParamToFormParamAnnotations Map<Integer, Set<Annotation>> indexToFormParam = methodToIndexOfParamToFormParamAnnotations
@ -1032,12 +1145,15 @@ public class RestAnnotationProcessor<T> {
.get(method); .get(method);
for (Entry<Integer, Set<Annotation>> entry : indexToFormParam.entrySet()) { for (Entry<Integer, Set<Annotation>> entry : indexToFormParam.entrySet()) {
for (Annotation key : entry.getValue()) { for (Annotation key : entry.getValue()) {
Set<Annotation> extractors = indexToParamExtractor.get(entry.getKey()); Set<Annotation> extractors = indexToParamExtractor.get(entry
.getKey());
String paramKey = ((FormParam) key).value(); String paramKey = ((FormParam) key).value();
String paramValue; String paramValue;
if (extractors != null && extractors.size() > 0) { if (extractors != null && extractors.size() > 0) {
ParamParser extractor = (ParamParser) extractors.iterator().next(); ParamParser extractor = (ParamParser) extractors.iterator()
paramValue = injector.getInstance(extractor.value()).apply(args[entry.getKey()]); .next();
paramValue = injector.getInstance(extractor.value()).apply(
args[entry.getKey()]);
} else { } else {
paramValue = args[entry.getKey()].toString(); paramValue = args[entry.getKey()].toString();
} }
@ -1048,15 +1164,16 @@ public class RestAnnotationProcessor<T> {
if (method.isAnnotationPresent(FormParam.class) if (method.isAnnotationPresent(FormParam.class)
&& method.isAnnotationPresent(ParamParser.class)) { && method.isAnnotationPresent(ParamParser.class)) {
String paramKey = method.getAnnotation(FormParam.class).value(); String paramKey = method.getAnnotation(FormParam.class).value();
String paramValue = injector.getInstance(method.getAnnotation(ParamParser.class).value()) String paramValue = injector.getInstance(
.apply(args); method.getAnnotation(ParamParser.class).value()).apply(args);
formParamValues.put(paramKey, paramValue); formParamValues.put(paramKey, paramValue);
} }
return formParamValues; return formParamValues;
} }
private Multimap<String, String> getQueryParamKeyValues(Method method, Object... args) { private Multimap<String, String> getQueryParamKeyValues(Method method,
Object... args) {
Multimap<String, String> queryParamValues = LinkedHashMultimap.create(); Multimap<String, String> queryParamValues = LinkedHashMultimap.create();
queryParamValues.putAll(constants); queryParamValues.putAll(constants);
Map<Integer, Set<Annotation>> indexToQueryParam = methodToIndexOfParamToQueryParamAnnotations Map<Integer, Set<Annotation>> indexToQueryParam = methodToIndexOfParamToQueryParamAnnotations
@ -1066,12 +1183,15 @@ public class RestAnnotationProcessor<T> {
.get(method); .get(method);
for (Entry<Integer, Set<Annotation>> entry : indexToQueryParam.entrySet()) { for (Entry<Integer, Set<Annotation>> entry : indexToQueryParam.entrySet()) {
for (Annotation key : entry.getValue()) { for (Annotation key : entry.getValue()) {
Set<Annotation> extractors = indexToParamExtractor.get(entry.getKey()); Set<Annotation> extractors = indexToParamExtractor.get(entry
.getKey());
String paramKey = ((QueryParam) key).value(); String paramKey = ((QueryParam) key).value();
String paramValue; String paramValue;
if (extractors != null && extractors.size() > 0) { if (extractors != null && extractors.size() > 0) {
ParamParser extractor = (ParamParser) extractors.iterator().next(); ParamParser extractor = (ParamParser) extractors.iterator()
paramValue = injector.getInstance(extractor.value()).apply(args[entry.getKey()]); .next();
paramValue = injector.getInstance(extractor.value()).apply(
args[entry.getKey()]);
} else { } else {
paramValue = args[entry.getKey()].toString(); paramValue = args[entry.getKey()].toString();
} }
@ -1082,8 +1202,8 @@ public class RestAnnotationProcessor<T> {
if (method.isAnnotationPresent(QueryParam.class) if (method.isAnnotationPresent(QueryParam.class)
&& method.isAnnotationPresent(ParamParser.class)) { && method.isAnnotationPresent(ParamParser.class)) {
String paramKey = method.getAnnotation(QueryParam.class).value(); String paramKey = method.getAnnotation(QueryParam.class).value();
String paramValue = injector.getInstance(method.getAnnotation(ParamParser.class).value()) String paramValue = injector.getInstance(
.apply(args); method.getAnnotation(ParamParser.class).value()).apply(args);
queryParamValues.put(paramKey, paramValue); queryParamValues.put(paramKey, paramValue);
} }
@ -1098,12 +1218,15 @@ public class RestAnnotationProcessor<T> {
.get(method); .get(method);
for (Entry<Integer, Set<Annotation>> entry : indexToPathParam.entrySet()) { for (Entry<Integer, Set<Annotation>> entry : indexToPathParam.entrySet()) {
for (Annotation key : entry.getValue()) { for (Annotation key : entry.getValue()) {
Set<Annotation> extractors = indexToParamExtractor.get(entry.getKey()); Set<Annotation> extractors = indexToParamExtractor.get(entry
.getKey());
String paramKey = ((MapPayloadParam) key).value(); String paramKey = ((MapPayloadParam) key).value();
String paramValue; String paramValue;
if (extractors != null && extractors.size() > 0) { if (extractors != null && extractors.size() > 0) {
ParamParser extractor = (ParamParser) extractors.iterator().next(); ParamParser extractor = (ParamParser) extractors.iterator()
paramValue = injector.getInstance(extractor.value()).apply(args[entry.getKey()]); .next();
paramValue = injector.getInstance(extractor.value()).apply(
args[entry.getKey()]);
} else { } else {
paramValue = args[entry.getKey()].toString(); paramValue = args[entry.getKey()].toString();
@ -1115,19 +1238,4 @@ public class RestAnnotationProcessor<T> {
return postParams; return postParams;
} }
public URI getEndpointFor(Method method, Object... args) {
URI endpoint = getEndpointInParametersOrNull(method, args);
if (endpoint == null) {
Endpoint annotation;
if (method.isAnnotationPresent(Endpoint.class)) {
annotation = method.getAnnotation(Endpoint.class);
} else if (method.getDeclaringClass().isAnnotationPresent(Endpoint.class)) {
annotation = method.getDeclaringClass().getAnnotation(Endpoint.class);
} else {
throw new IllegalStateException("no annotations on class or method: " + method);
}
return injector.getInstance(Key.get(URI.class, annotation.value()));
}
return endpoint;
}
} }