View Javadoc

1   /**
2    * Licensed to jclouds, Inc. (jclouds) under one or more
3    * contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  jclouds licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.jclouds.rest.internal;
20  
21  import static com.google.common.base.Preconditions.checkArgument;
22  import static com.google.common.collect.Collections2.filter;
23  import static com.google.common.collect.Iterables.concat;
24  import static com.google.common.collect.Iterables.get;
25  import static com.google.common.collect.Iterables.transform;
26  import static com.google.common.collect.Lists.newLinkedList;
27  import static com.google.common.collect.Maps.filterValues;
28  import static com.google.common.collect.Maps.newHashMap;
29  import static com.google.common.collect.Sets.newTreeSet;
30  import static java.util.Arrays.asList;
31  import static javax.ws.rs.core.HttpHeaders.ACCEPT;
32  import static javax.ws.rs.core.HttpHeaders.CONTENT_TYPE;
33  import static javax.ws.rs.core.HttpHeaders.HOST;
34  import static org.jclouds.io.Payloads.newPayload;
35  
36  import java.io.InputStream;
37  import java.lang.annotation.Annotation;
38  import java.lang.reflect.Array;
39  import java.lang.reflect.Method;
40  import java.lang.reflect.ParameterizedType;
41  import java.lang.reflect.Type;
42  import java.lang.reflect.WildcardType;
43  import java.net.URI;
44  import java.util.Collection;
45  import java.util.Collections;
46  import java.util.Comparator;
47  import java.util.List;
48  import java.util.Map;
49  import java.util.Set;
50  import java.util.SortedSet;
51  import java.util.Map.Entry;
52  import java.util.concurrent.ExecutionException;
53  
54  import javax.annotation.Resource;
55  import javax.inject.Named;
56  import javax.inject.Provider;
57  import javax.ws.rs.Consumes;
58  import javax.ws.rs.FormParam;
59  import javax.ws.rs.HeaderParam;
60  import javax.ws.rs.HttpMethod;
61  import javax.ws.rs.MatrixParam;
62  import javax.ws.rs.Path;
63  import javax.ws.rs.PathParam;
64  import javax.ws.rs.Produces;
65  import javax.ws.rs.QueryParam;
66  import javax.ws.rs.core.MediaType;
67  import javax.ws.rs.core.UriBuilder;
68  import javax.ws.rs.core.UriBuilderException;
69  
70  import org.jclouds.Constants;
71  import org.jclouds.functions.IdentityFunction;
72  import org.jclouds.functions.OnlyElementOrNull;
73  import org.jclouds.http.HttpRequest;
74  import org.jclouds.http.HttpRequestFilter;
75  import org.jclouds.http.HttpResponse;
76  import org.jclouds.http.HttpUtils;
77  import org.jclouds.http.functions.ParseFirstJsonValueNamed;
78  import org.jclouds.http.functions.ParseJson;
79  import org.jclouds.http.functions.ParseSax;
80  import org.jclouds.http.functions.ParseURIFromListOrLocationHeaderIf20x;
81  import org.jclouds.http.functions.ReleasePayloadAndReturn;
82  import org.jclouds.http.functions.ReturnInputStream;
83  import org.jclouds.http.functions.ReturnStringIf2xx;
84  import org.jclouds.http.functions.ReturnTrueIf2xx;
85  import org.jclouds.http.functions.UnwrapOnlyJsonValue;
86  import org.jclouds.http.functions.UnwrapOnlyJsonValueInSet;
87  import org.jclouds.http.functions.UnwrapOnlyNestedJsonValue;
88  import org.jclouds.http.functions.UnwrapOnlyNestedJsonValueInSet;
89  import org.jclouds.http.functions.ParseSax.HandlerWithResult;
90  import org.jclouds.http.options.HttpRequestOptions;
91  import org.jclouds.http.utils.ModifyRequest;
92  import org.jclouds.internal.ClassMethodArgs;
93  import org.jclouds.io.ContentMetadata;
94  import org.jclouds.io.Payload;
95  import org.jclouds.io.PayloadEnclosing;
96  import org.jclouds.io.Payloads;
97  import org.jclouds.io.payloads.MultipartForm;
98  import org.jclouds.io.payloads.Part;
99  import org.jclouds.io.payloads.Part.PartOptions;
100 import org.jclouds.javax.annotation.Nullable;
101 import org.jclouds.json.internal.GsonWrapper;
102 import org.jclouds.logging.Logger;
103 import org.jclouds.rest.Binder;
104 import org.jclouds.rest.InputParamValidator;
105 import org.jclouds.rest.InvocationContext;
106 import org.jclouds.rest.annotations.BinderParam;
107 import org.jclouds.rest.annotations.Endpoint;
108 import org.jclouds.rest.annotations.EndpointParam;
109 import org.jclouds.rest.annotations.ExceptionParser;
110 import org.jclouds.rest.annotations.FormParams;
111 import org.jclouds.rest.annotations.Headers;
112 import org.jclouds.rest.annotations.MapBinder;
113 import org.jclouds.rest.annotations.MatrixParams;
114 import org.jclouds.rest.annotations.OnlyElement;
115 import org.jclouds.rest.annotations.OverrideRequestFilters;
116 import org.jclouds.rest.annotations.ParamParser;
117 import org.jclouds.rest.annotations.PartParam;
118 import org.jclouds.rest.annotations.PayloadParam;
119 import org.jclouds.rest.annotations.PayloadParams;
120 import org.jclouds.rest.annotations.QueryParams;
121 import org.jclouds.rest.annotations.RequestFilters;
122 import org.jclouds.rest.annotations.ResponseParser;
123 import org.jclouds.rest.annotations.SelectJson;
124 import org.jclouds.rest.annotations.SkipEncoding;
125 import org.jclouds.rest.annotations.Unwrap;
126 import org.jclouds.rest.annotations.VirtualHost;
127 import org.jclouds.rest.annotations.WrapWith;
128 import org.jclouds.rest.annotations.XMLResponseParser;
129 import org.jclouds.rest.binders.BindMapToStringPayload;
130 import org.jclouds.rest.binders.BindToJsonPayloadWrappedWith;
131 import org.jclouds.rest.functions.MapHttp4xxCodesToExceptions;
132 import org.jclouds.util.Maps2;
133 import org.jclouds.util.Strings2;
134 
135 import com.google.common.annotations.VisibleForTesting;
136 import com.google.common.base.Function;
137 import com.google.common.base.Functions;
138 import com.google.common.base.Preconditions;
139 import com.google.common.base.Predicate;
140 import com.google.common.base.Predicates;
141 import com.google.common.base.Throwables;
142 import com.google.common.cache.Cache;
143 import com.google.common.cache.CacheBuilder;
144 import com.google.common.cache.CacheLoader;
145 import com.google.common.collect.ImmutableList;
146 import com.google.common.collect.ImmutableMultimap;
147 import com.google.common.collect.ImmutableSet;
148 import com.google.common.collect.Iterables;
149 import com.google.common.collect.LinkedHashMultimap;
150 import com.google.common.collect.LinkedListMultimap;
151 import com.google.common.collect.Lists;
152 import com.google.common.collect.Multimap;
153 import com.google.common.collect.ImmutableSet.Builder;
154 import com.google.common.util.concurrent.ListenableFuture;
155 import com.google.inject.Inject;
156 import com.google.inject.Injector;
157 import com.google.inject.Key;
158 import com.google.inject.TypeLiteral;
159 import com.google.inject.util.Types;
160 
161 /**
162  * Creates http methods based on annotations on a class or interface.
163  * 
164  * @author Adrian Cole
165  */
166 public class RestAnnotationProcessor<T> {
167 
168    @Resource
169    protected Logger logger = Logger.NULL;
170 
171    private final Class<T> declaring;
172 
173    // TODO replace with Table object
174    static final Cache<Method, Cache<Integer, Set<Annotation>>> methodToIndexOfParamToBinderParamAnnotation = createMethodToIndexOfParamToAnnotation(BinderParam.class);
175    static final Cache<Method, Cache<Integer, Set<Annotation>>> methodToIndexOfParamToWrapWithAnnotation = createMethodToIndexOfParamToAnnotation(WrapWith.class);
176    static final Cache<Method, Cache<Integer, Set<Annotation>>> methodToIndexOfParamToHeaderParamAnnotations = createMethodToIndexOfParamToAnnotation(HeaderParam.class);
177    static final Cache<Method, Cache<Integer, Set<Annotation>>> methodToIndexOfParamToEndpointAnnotations = createMethodToIndexOfParamToAnnotation(Endpoint.class);
178    static final Cache<Method, Cache<Integer, Set<Annotation>>> methodToIndexOfParamToEndpointParamAnnotations = createMethodToIndexOfParamToAnnotation(EndpointParam.class);
179    static final Cache<Method, Cache<Integer, Set<Annotation>>> methodToIndexOfParamToMatrixParamAnnotations = createMethodToIndexOfParamToAnnotation(MatrixParam.class);
180    static final Cache<Method, Cache<Integer, Set<Annotation>>> methodToIndexOfParamToFormParamAnnotations = createMethodToIndexOfParamToAnnotation(FormParam.class);
181    static final Cache<Method, Cache<Integer, Set<Annotation>>> methodToIndexOfParamToQueryParamAnnotations = createMethodToIndexOfParamToAnnotation(QueryParam.class);
182    static final Cache<Method, Cache<Integer, Set<Annotation>>> methodToIndexOfParamToPathParamAnnotations = createMethodToIndexOfParamToAnnotation(PathParam.class);
183    static final Cache<Method, Cache<Integer, Set<Annotation>>> methodToIndexOfParamToPostParamAnnotations = createMethodToIndexOfParamToAnnotation(PayloadParam.class);
184    static final Cache<Method, Cache<Integer, Set<Annotation>>> methodToIndexOfParamToPartParamAnnotations = createMethodToIndexOfParamToAnnotation(PartParam.class);
185    static final Cache<Method, Cache<Integer, Set<Annotation>>> methodToIndexOfParamToParamParserAnnotations = createMethodToIndexOfParamToAnnotation(ParamParser.class);
186    static final Map<MethodKey, Method> delegationMap = newHashMap();
187 
188    static Cache<Method, Cache<Integer, Set<Annotation>>> createMethodToIndexOfParamToAnnotation(
189             final Class<? extends Annotation> annotation) {
190       return CacheBuilder.newBuilder().build(new CacheLoader<Method, Cache<Integer, Set<Annotation>>>() {
191          public Cache<Integer, Set<Annotation>> load(Method method) {
192             return CacheBuilder.newBuilder().build(CacheLoader.from(new GetAnnotationsForMethodParameterIndex(method, annotation)));
193          }
194       });
195    }
196 
197    static class GetAnnotationsForMethodParameterIndex implements Function<Integer, Set<Annotation>> {
198       private final Method method;
199       private final Class<?> clazz;
200 
201       protected GetAnnotationsForMethodParameterIndex(Method method, Class<?> clazz) {
202          this.method = method;
203          this.clazz = clazz;
204       }
205 
206       public Set<Annotation> apply(final Integer index) {
207          return ImmutableSet.<Annotation> copyOf(filter(ImmutableList.copyOf(method.getParameterAnnotations()[index]),
208                new Predicate<Annotation>() {
209                   public boolean apply(Annotation input) {
210                      return input.annotationType().equals(clazz);
211                   }
212                }));
213       }
214 
215    }
216 
217    private static final Class<? extends HttpRequestOptions[]> optionsVarArgsClass = new HttpRequestOptions[] {}
218          .getClass();
219 
220    private static final Function<? super Entry<String, String>, ? extends Part> ENTRY_TO_PART = new Function<Entry<String, String>, Part>() {
221 
222       @Override
223       public Part apply(Entry<String, String> from) {
224          return Part.create(from.getKey(), from.getValue());
225       }
226 
227    };
228 
229    static final Cache<Method, Set<Integer>> methodToIndexesOfOptions = CacheBuilder.newBuilder().build(
230          new CacheLoader<Method, Set<Integer>>() {
231             @Override
232             public Set<Integer> load(Method method) {
233                Builder<Integer> toReturn = ImmutableSet.<Integer> builder();
234                for (int index = 0; index < method.getParameterTypes().length; index++) {
235                   Class<?> type = method.getParameterTypes()[index];
236                   if (HttpRequestOptions.class.isAssignableFrom(type) || optionsVarArgsClass.isAssignableFrom(type))
237                      toReturn.add(index);
238                }
239                return toReturn.build();
240             }
241          });
242 
243    private final ParseSax.Factory parserFactory;
244    private final HttpUtils utils;
245    private final Provider<UriBuilder> uriBuilderProvider;
246    private final Cache<Class<?>, Boolean> seedAnnotationCache;
247    private final String apiVersion;
248    private char[] skips;
249 
250    @Inject
251    private InputParamValidator inputParamValidator;
252 
253    @VisibleForTesting
254    Function<HttpResponse, ?> createResponseParser(Method method, HttpRequest request) {
255       return createResponseParser(parserFactory, injector, method, request);
256    }
257 
258    @VisibleForTesting
259    public static Function<HttpResponse, ?> createResponseParser(ParseSax.Factory parserFactory, Injector injector,
260          Method method, HttpRequest request) {
261       Function<HttpResponse, ?> transformer;
262       Class<? extends HandlerWithResult<?>> handler = getSaxResponseParserClassOrNull(method);
263       if (handler != null) {
264          transformer = parserFactory.create(injector.getInstance(handler));
265       } else {
266          transformer = getTransformerForMethod(method, injector);
267       }
268       if (transformer instanceof InvocationContext<?>) {
269          ((InvocationContext<?>) transformer).setContext(request);
270       }
271       return transformer;
272    }
273 
274    @SuppressWarnings({ "rawtypes", "unchecked" })
275    public static Function<HttpResponse, ?> getTransformerForMethod(Method method, Injector injector) {
276       Function<HttpResponse, ?> transformer;
277       if (method.isAnnotationPresent(SelectJson.class)) {
278          Type returnVal = getReturnTypeForMethod(method);
279          if (method.isAnnotationPresent(OnlyElement.class))
280             returnVal = Types.newParameterizedType(Set.class, returnVal);
281          transformer = new ParseFirstJsonValueNamed(injector.getInstance(GsonWrapper.class),
282                TypeLiteral.get(returnVal), method.getAnnotation(SelectJson.class).value());
283          if (method.isAnnotationPresent(OnlyElement.class))
284             transformer = Functions.compose(new OnlyElementOrNull(), transformer);
285       } else {
286          transformer = injector.getInstance(getParserOrThrowException(method));
287       }
288       return transformer;
289    }
290 
291    @VisibleForTesting
292    Function<Exception, ?> createExceptionParserOrThrowResourceNotFoundOn404IfNoAnnotation(Method method) {
293       return createExceptionParserOrThrowResourceNotFoundOn404IfNoAnnotation(injector, method);
294    }
295 
296    @VisibleForTesting
297    public static Function<Exception, ?> createExceptionParserOrThrowResourceNotFoundOn404IfNoAnnotation(
298          Injector injector, Method method) {
299       ExceptionParser annotation = method.getAnnotation(ExceptionParser.class);
300       if (annotation != null) {
301          return injector.getInstance(annotation.value());
302       }
303       return injector.getInstance(MapHttp4xxCodesToExceptions.class);
304    }
305 
306    @SuppressWarnings("unchecked")
307    @Inject
308    public RestAnnotationProcessor(Injector injector, Cache<Class<?>, Boolean> seedAnnotationCache,
309          @Named(Constants.PROPERTY_API_VERSION) String apiVersion, ParseSax.Factory parserFactory, HttpUtils utils,
310          TypeLiteral<T> typeLiteral) throws ExecutionException {
311       this.declaring = (Class<T>) typeLiteral.getRawType();
312       this.injector = injector;
313       this.parserFactory = parserFactory;
314       this.utils = utils;
315       this.uriBuilderProvider = injector.getProvider(UriBuilder.class);
316       this.seedAnnotationCache = seedAnnotationCache;
317       seedAnnotationCache.get(declaring);
318       if (declaring.isAnnotationPresent(SkipEncoding.class)) {
319          skips = declaring.getAnnotation(SkipEncoding.class).value();
320       } else {
321          skips = new char[] {};
322       }
323       this.apiVersion = apiVersion;
324    }
325 
326    public Method getDelegateOrNull(Method in) {
327       return delegationMap.get(new MethodKey(in));
328    }
329 
330    public static class MethodKey {
331 
332       @Override
333       public int hashCode() {
334          final int prime = 31;
335          int result = 1;
336          result = prime * result + ((declaringPackage == null) ? 0 : declaringPackage.hashCode());
337          result = prime * result + ((name == null) ? 0 : name.hashCode());
338          result = prime * result + parametersTypeHashCode;
339          return result;
340       }
341 
342       @Override
343       public boolean equals(Object obj) {
344          if (this == obj)
345             return true;
346          if (obj == null)
347             return false;
348          if (getClass() != obj.getClass())
349             return false;
350          MethodKey other = (MethodKey) obj;
351          if (declaringPackage == null) {
352             if (other.declaringPackage != null)
353                return false;
354          } else if (!declaringPackage.equals(other.declaringPackage))
355             return false;
356          if (name == null) {
357             if (other.name != null)
358                return false;
359          } else if (!name.equals(other.name))
360             return false;
361          if (parametersTypeHashCode != other.parametersTypeHashCode)
362             return false;
363          return true;
364       }
365 
366       private final String name;
367       private final int parametersTypeHashCode;
368       private final Package declaringPackage;
369 
370       public MethodKey(Method method) {
371          this.name = method.getName();
372          this.declaringPackage = method.getDeclaringClass().getPackage();
373          int parametersTypeHashCode = 0;
374          for (Class<?> param : method.getParameterTypes())
375             parametersTypeHashCode += param.hashCode();
376          this.parametersTypeHashCode = parametersTypeHashCode;
377       }
378 
379    }
380 
381    final Injector injector;
382 
383    private ClassMethodArgs caller;
384    private URI callerEndpoint;
385 
386    public void setCaller(ClassMethodArgs caller) {
387       try {
388          seedAnnotationCache.get(caller.getMethod().getDeclaringClass());
389       } catch (ExecutionException e) {
390          Throwables.propagate(e);
391       }
392       this.caller = caller;
393       try {
394          callerEndpoint = getEndpointFor(caller.getMethod(), caller.getArgs(), injector);
395       } catch (IllegalStateException e) {
396       } catch (ExecutionException e) {
397          Throwables.propagate(e);
398       }
399    }
400 
401    public GeneratedHttpRequest<T> createRequest(Method method, Object... args) {
402       try {
403          inputParamValidator.validateMethodParametersOrThrow(method, args);
404          ClassMethodArgs cma = logger.isTraceEnabled() ? new ClassMethodArgs(method.getDeclaringClass(), method, args)
405                : null;
406 
407          URI endpoint = callerEndpoint;
408          try {
409             if (endpoint == null) {
410                endpoint = getEndpointFor(method, args, injector);
411                logger.trace("using endpoint %s for %s", endpoint, cma);
412             } else {
413                logger.trace("using endpoint %s from caller %s for %s", caller, endpoint, cma);
414             }
415          } catch (IllegalStateException e) {
416             logger.trace("looking up default endpoint for %s", cma);
417             endpoint = injector.getInstance(Key.get(URI.class, org.jclouds.location.Provider.class));
418             logger.trace("using default endpoint %s for %s", endpoint, cma);
419          }
420          GeneratedHttpRequest.Builder<T> requestBuilder;
421          HttpRequest r = RestAnnotationProcessor.findHttpRequestInArgs(args);
422          if (r != null) {
423             requestBuilder = GeneratedHttpRequest.Builder.<T> from(r);
424             endpoint = r.getEndpoint();
425          } else {
426             requestBuilder = GeneratedHttpRequest.<T> builder();
427             requestBuilder.method(getHttpMethodOrConstantOrThrowException(method));
428          }
429 
430          requestBuilder.declaring(declaring).javaMethod(method).args(args).skips(skips);
431          requestBuilder.filters(getFiltersIfAnnotated(method));
432 
433          UriBuilder builder = uriBuilderProvider.get().uri(endpoint);
434 
435          Multimap<String, String> tokenValues = LinkedHashMultimap.create();
436 
437          tokenValues.put(Constants.PROPERTY_API_VERSION, apiVersion);
438 
439          tokenValues.putAll(addPathAndGetTokens(declaring, method, args, builder));
440 
441          Multimap<String, String> formParams = addFormParams(tokenValues.entries(), method, args);
442          Multimap<String, String> queryParams = addQueryParams(tokenValues.entries(), method, args);
443          Multimap<String, String> matrixParams = addMatrixParams(tokenValues.entries(), method, args);
444          Multimap<String, String> headers = buildHeaders(tokenValues.entries(), method, args);
445          if (r != null)
446             headers.putAll(r.getHeaders());
447 
448          if (shouldAddHostHeader(method)) {
449             StringBuilder hostHeader = new StringBuilder(endpoint.getHost());
450             if (endpoint.getPort() != -1)
451                hostHeader.append(":").append(endpoint.getPort());
452             headers.put(HOST, hostHeader.toString());
453          }
454 
455          Payload payload = null;
456          HttpRequestOptions options = findOptionsIn(method, args);
457          if (options != null) {
458             injector.injectMembers(options);// TODO test case
459             for (Entry<String, String> header : options.buildRequestHeaders().entries()) {
460                headers.put(header.getKey(), Strings2.replaceTokens(header.getValue(), tokenValues.entries()));
461             }
462             for (Entry<String, String> matrix : options.buildMatrixParameters().entries()) {
463                matrixParams.put(matrix.getKey(), Strings2.replaceTokens(matrix.getValue(), tokenValues.entries()));
464             }
465             for (Entry<String, String> query : options.buildQueryParameters().entries()) {
466                queryParams.put(query.getKey(), Strings2.replaceTokens(query.getValue(), tokenValues.entries()));
467             }
468             for (Entry<String, String> form : options.buildFormParameters().entries()) {
469                formParams.put(form.getKey(), Strings2.replaceTokens(form.getValue(), tokenValues.entries()));
470             }
471 
472             String pathSuffix = options.buildPathSuffix();
473             if (pathSuffix != null) {
474                builder.path(pathSuffix);
475             }
476             String stringPayload = options.buildStringPayload();
477             if (stringPayload != null)
478                payload = Payloads.newStringPayload(stringPayload);
479          }
480 
481          if (matrixParams.size() > 0) {
482             for (String key : matrixParams.keySet())
483                builder.matrixParam(key, Lists.newArrayList(matrixParams.get(key)).toArray());
484          }
485 
486          if (queryParams.size() > 0) {
487             builder.replaceQuery(ModifyRequest.makeQueryLine(queryParams, null, skips));
488          }
489 
490          requestBuilder.headers(filterOutContentHeaders(headers));
491 
492          try {
493             requestBuilder.endpoint(builder.buildFromEncodedMap(Maps2.convertUnsafe(tokenValues)));
494          } catch (IllegalArgumentException e) {
495             throw new IllegalStateException(e);
496          } catch (UriBuilderException e) {
497             throw new IllegalStateException(e);
498          }
499 
500          if (payload == null)
501             payload = findPayloadInArgs(args);
502          List<? extends Part> parts = getParts(method, args, concat(tokenValues.entries(), formParams.entries()));
503          if (parts.size() > 0) {
504             if (formParams.size() > 0) {
505                parts = newLinkedList(concat(transform(formParams.entries(), ENTRY_TO_PART), parts));
506             }
507             payload = new MultipartForm(BOUNDARY, parts);
508          } else if (formParams.size() > 0) {
509             payload = Payloads.newUrlEncodedFormPayload(formParams, skips);
510          } else if (headers.containsKey(CONTENT_TYPE)) {
511             if (payload == null)
512                payload = Payloads.newByteArrayPayload(new byte[] {});
513             payload.getContentMetadata().setContentType(Iterables.get(headers.get(CONTENT_TYPE), 0));
514          }
515          if (payload != null) {
516             requestBuilder.payload(payload);
517          }
518          GeneratedHttpRequest<T> request = requestBuilder.build();
519 
520          org.jclouds.rest.MapBinder mapBinder = getMapPayloadBinderOrNull(method, args);
521          if (mapBinder != null) {
522             Map<String, String> mapParams = buildPostParams(method, args);
523             if (method.isAnnotationPresent(PayloadParams.class)) {
524                PayloadParams params = method.getAnnotation(PayloadParams.class);
525                addMapPayload(mapParams, params, headers.entries());
526             }
527             request = mapBinder.bindToRequest(request, mapParams);
528          } else {
529             request = decorateRequest(request);
530          }
531 
532          if (request.getPayload() != null)
533             request.getPayload().getContentMetadata().setPropertiesFromHttpHeaders(headers);
534          utils.checkRequestHasRequiredProperties(request);
535          return request;
536       } catch (ExecutionException e) {
537          Throwables.propagate(e);
538          return null;
539       }
540    }
541 
542    public static Multimap<String, String> filterOutContentHeaders(Multimap<String, String> headers) {
543       // TODO make a filter like {@link Maps.filterKeys} instead of this
544       ImmutableMultimap.Builder<String, String> headersBuilder = ImmutableMultimap.builder();
545       // http message usually comes in as a null key header, let's filter it
546       // out.
547       for (String header : Iterables.filter(headers.keySet(), Predicates.notNull())) {
548          if (!ContentMetadata.HTTP_HEADERS.contains(header)) {
549             headersBuilder.putAll(header, headers.get(header));
550          }
551       }
552       return headersBuilder.build();
553    }
554 
555    public static final String BOUNDARY = "--JCLOUDS--";
556 
557    private Multimap<String, String> addPathAndGetTokens(Class<?> clazz, Method method, Object[] args, UriBuilder builder) throws ExecutionException {
558       if (clazz.isAnnotationPresent(Path.class))
559          builder.path(clazz);
560       if (method.isAnnotationPresent(Path.class))
561          builder.path(method);
562       return encodeValues(getPathParamKeyValues(method, args), skips);
563    }
564 
565    public URI replaceQuery(URI in, String newQuery, @Nullable Comparator<Entry<String, String>> sorter) {
566       return replaceQuery(uriBuilderProvider, in, newQuery, sorter, skips);
567    }
568 
569    public static URI replaceQuery(Provider<UriBuilder> uriBuilderProvider, URI in, String newQuery,
570          @Nullable Comparator<Entry<String, String>> sorter, char... skips) {
571       UriBuilder builder = uriBuilderProvider.get().uri(in);
572       builder.replaceQuery(ModifyRequest.makeQueryLine(ModifyRequest.parseQueryToMap(newQuery), sorter, skips));
573       return builder.build();
574    }
575 
576    private Multimap<String, String> addMatrixParams(Collection<Entry<String, String>> tokenValues, Method method,
577          Object... args) throws ExecutionException {
578       Multimap<String, String> matrixMap = LinkedListMultimap.create();
579       if (declaring.isAnnotationPresent(MatrixParams.class)) {
580          MatrixParams matrix = declaring.getAnnotation(MatrixParams.class);
581          addMatrix(matrixMap, matrix, tokenValues);
582       }
583 
584       if (method.isAnnotationPresent(MatrixParams.class)) {
585          MatrixParams matrix = method.getAnnotation(MatrixParams.class);
586          addMatrix(matrixMap, matrix, tokenValues);
587       }
588 
589       for (Entry<String, String> matrix : getMatrixParamKeyValues(method, args).entries()) {
590          matrixMap.put(matrix.getKey(), Strings2.replaceTokens(matrix.getValue(), tokenValues));
591       }
592       return matrixMap;
593    }
594 
595    private Multimap<String, String> addFormParams(Collection<Entry<String, String>> tokenValues, Method method,
596          Object... args) throws ExecutionException {
597       Multimap<String, String> formMap = LinkedListMultimap.create();
598       if (declaring.isAnnotationPresent(FormParams.class)) {
599          FormParams form = declaring.getAnnotation(FormParams.class);
600          addForm(formMap, form, tokenValues);
601       }
602 
603       if (method.isAnnotationPresent(FormParams.class)) {
604          FormParams form = method.getAnnotation(FormParams.class);
605          addForm(formMap, form, tokenValues);
606       }
607 
608       for (Entry<String, String> form : getFormParamKeyValues(method, args).entries()) {
609          formMap.put(form.getKey(), Strings2.replaceTokens(form.getValue(), tokenValues));
610       }
611       return formMap;
612    }
613 
614    private Multimap<String, String> addQueryParams(Collection<Entry<String, String>> tokenValues, Method method,
615          Object... args) throws ExecutionException {
616       Multimap<String, String> queryMap = LinkedListMultimap.create();
617       if (declaring.isAnnotationPresent(QueryParams.class)) {
618          QueryParams query = declaring.getAnnotation(QueryParams.class);
619          addQuery(queryMap, query, tokenValues);
620       }
621 
622       if (method.isAnnotationPresent(QueryParams.class)) {
623          QueryParams query = method.getAnnotation(QueryParams.class);
624          addQuery(queryMap, query, tokenValues);
625       }
626 
627       for (Entry<String, String> query : getQueryParamKeyValues(method, args).entries()) {
628          queryMap.put(query.getKey(), Strings2.replaceTokens(query.getValue(), tokenValues));
629       }
630       return queryMap;
631    }
632 
633    private void addForm(Multimap<String, String> formParams, FormParams form,
634          Collection<Entry<String, String>> tokenValues) {
635       for (int i = 0; i < form.keys().length; i++) {
636          if (form.values()[i].equals(FormParams.NULL)) {
637             formParams.removeAll(form.keys()[i]);
638             formParams.put(form.keys()[i], null);
639          } else {
640             formParams.put(form.keys()[i], Strings2.replaceTokens(form.values()[i], tokenValues));
641          }
642       }
643    }
644 
645    private void addQuery(Multimap<String, String> queryParams, QueryParams query,
646          Collection<Entry<String, String>> tokenValues) {
647       for (int i = 0; i < query.keys().length; i++) {
648          if (query.values()[i].equals(QueryParams.NULL)) {
649             queryParams.removeAll(query.keys()[i]);
650             queryParams.put(query.keys()[i], null);
651          } else {
652             queryParams.put(query.keys()[i], Strings2.replaceTokens(query.values()[i], tokenValues));
653          }
654       }
655    }
656 
657    private void addMatrix(Multimap<String, String> matrixParams, MatrixParams matrix,
658          Collection<Entry<String, String>> tokenValues) {
659       for (int i = 0; i < matrix.keys().length; i++) {
660          if (matrix.values()[i].equals(MatrixParams.NULL)) {
661             matrixParams.removeAll(matrix.keys()[i]);
662             matrixParams.put(matrix.keys()[i], null);
663          } else {
664             matrixParams.put(matrix.keys()[i], Strings2.replaceTokens(matrix.values()[i], tokenValues));
665          }
666       }
667    }
668 
669    private void addMapPayload(Map<String, String> postParams, PayloadParams mapDefaults,
670          Collection<Entry<String, String>> tokenValues) {
671       for (int i = 0; i < mapDefaults.keys().length; i++) {
672          if (mapDefaults.values()[i].equals(PayloadParams.NULL)) {
673             postParams.put(mapDefaults.keys()[i], null);
674          } else {
675             postParams.put(mapDefaults.keys()[i], Strings2.replaceTokens(mapDefaults.values()[i], tokenValues));
676          }
677       }
678    }
679 
680    @VisibleForTesting
681    List<HttpRequestFilter> getFiltersIfAnnotated(Method method) {
682       List<HttpRequestFilter> filters = Lists.newArrayList();
683       if (declaring.isAnnotationPresent(RequestFilters.class)) {
684          for (Class<? extends HttpRequestFilter> clazz : declaring.getAnnotation(RequestFilters.class).value()) {
685             HttpRequestFilter instance = injector.getInstance(clazz);
686             filters.add(instance);
687             logger.trace("adding filter %s from annotation on %s", instance, declaring.getName());
688          }
689       }
690       if (method.isAnnotationPresent(RequestFilters.class)) {
691          if (method.isAnnotationPresent(OverrideRequestFilters.class))
692             filters.clear();
693          for (Class<? extends HttpRequestFilter> clazz : method.getAnnotation(RequestFilters.class).value()) {
694             HttpRequestFilter instance = injector.getInstance(clazz);
695             filters.add(instance);
696             logger.trace("adding filter %s from annotation on %s", instance, method.getName());
697          }
698       }
699       return filters;
700    }
701 
702    @VisibleForTesting
703    public static URI getEndpointInParametersOrNull(Method method, final Object[] args, Injector injector)
704          throws ExecutionException {
705       Map<Integer, Set<Annotation>> map = indexWithAtLeastOneAnnotation(method,
706             methodToIndexOfParamToEndpointParamAnnotations);
707       if (map.size() >= 1 && args.length > 0) {
708          EndpointParam firstAnnotation = (EndpointParam) get(get(map.values(), 0), 0);
709          Function<Object, URI> parser = injector.getInstance(firstAnnotation.parser());
710 
711          if (map.size() == 1) {
712             int index = map.keySet().iterator().next();
713             try {
714                URI returnVal = parser.apply(args[index]);
715                checkArgument(returnVal != null,
716                      String.format("endpoint for [%s] not configured for %s", args[index], method));
717                return returnVal;
718             } catch (NullPointerException e) {
719                throw new IllegalArgumentException(String.format("argument at index %d on method %s", index, method), e);
720             }
721          } else {
722             SortedSet<Integer> keys = newTreeSet(map.keySet());
723             Iterable<Object> argsToParse = transform(keys, new Function<Integer, Object>() {
724 
725                @Override
726                public Object apply(Integer from) {
727                   return args[from];
728                }
729 
730             });
731             try {
732                URI returnVal = parser.apply(argsToParse);
733                checkArgument(returnVal != null,
734                      String.format("endpoint for [%s] not configured for %s", argsToParse, method));
735                return returnVal;
736             } catch (NullPointerException e) {
737                throw new IllegalArgumentException(String.format("illegal argument in [%s] for method %s", argsToParse,
738                      method), e);
739             }
740          }
741       }
742       return null;
743    }
744 
745    public static URI getEndpointFor(Method method, Object[] args, Injector injector) throws ExecutionException {
746       URI endpoint = getEndpointInParametersOrNull(method, args, injector);
747       if (endpoint == null) {
748          Endpoint annotation;
749          if (method.isAnnotationPresent(Endpoint.class)) {
750             annotation = method.getAnnotation(Endpoint.class);
751          } else if (method.getDeclaringClass().isAnnotationPresent(Endpoint.class)) {
752             annotation = method.getDeclaringClass().getAnnotation(Endpoint.class);
753          } else {
754             throw new IllegalStateException("no annotations on class or method: " + method);
755          }
756          return injector.getInstance(Key.get(URI.class, annotation.value()));
757       }
758       return endpoint;
759    }
760 
761    public static final TypeLiteral<ListenableFuture<Boolean>> futureBooleanLiteral = new TypeLiteral<ListenableFuture<Boolean>>() {
762    };
763    public static final TypeLiteral<ListenableFuture<String>> futureStringLiteral = new TypeLiteral<ListenableFuture<String>>() {
764    };
765    public static final TypeLiteral<ListenableFuture<Void>> futureVoidLiteral = new TypeLiteral<ListenableFuture<Void>>() {
766    };
767    public static final TypeLiteral<ListenableFuture<URI>> futureURILiteral = new TypeLiteral<ListenableFuture<URI>>() {
768    };
769    public static final TypeLiteral<ListenableFuture<InputStream>> futureInputStreamLiteral = new TypeLiteral<ListenableFuture<InputStream>>() {
770    };
771    public static final TypeLiteral<ListenableFuture<HttpResponse>> futureHttpResponseLiteral = new TypeLiteral<ListenableFuture<HttpResponse>>() {
772    };
773 
774    @SuppressWarnings({ "unchecked", "rawtypes" })
775    public static Key<? extends Function<HttpResponse, ?>> getParserOrThrowException(Method method) {
776       ResponseParser annotation = method.getAnnotation(ResponseParser.class);
777       if (annotation == null) {
778          if (method.getReturnType().equals(void.class)
779                || TypeLiteral.get(method.getGenericReturnType()).equals(futureVoidLiteral)) {
780             return Key.get(ReleasePayloadAndReturn.class);
781          } else if (method.getReturnType().equals(boolean.class) || method.getReturnType().equals(Boolean.class)
782                || TypeLiteral.get(method.getGenericReturnType()).equals(futureBooleanLiteral)) {
783             return Key.get(ReturnTrueIf2xx.class);
784          } else if (method.getReturnType().equals(InputStream.class)
785                || TypeLiteral.get(method.getGenericReturnType()).equals(futureInputStreamLiteral)) {
786             return Key.get(ReturnInputStream.class);
787          } else if (method.getReturnType().equals(HttpResponse.class)
788                || TypeLiteral.get(method.getGenericReturnType()).equals(futureHttpResponseLiteral)) {
789             return Key.get((Class) IdentityFunction.class);
790          } else if (getAcceptHeadersOrNull(method).contains(MediaType.APPLICATION_JSON)) {
791             return getJsonParserKeyForMethod(method);
792          } else if (method.getReturnType().equals(String.class)
793                || TypeLiteral.get(method.getGenericReturnType()).equals(futureStringLiteral)) {
794             return Key.get(ReturnStringIf2xx.class);
795          } else if (method.getReturnType().equals(URI.class)
796                || TypeLiteral.get(method.getGenericReturnType()).equals(futureURILiteral)) {
797             return Key.get(ParseURIFromListOrLocationHeaderIf20x.class);
798          } else {
799             throw new IllegalStateException("You must specify a ResponseParser annotation on: " + method.toString());
800          }
801       }
802       return Key.get(annotation.value());
803    }
804 
805    public static Key<? extends Function<HttpResponse, ?>> getJsonParserKeyForMethod(Method method) {
806       Type returnVal = getReturnTypeForMethod(method);
807       return getJsonParserKeyForMethodAnType(method, returnVal);
808    }
809 
810    public static Type getReturnTypeForMethod(Method method) {
811       Type returnVal;
812       if (method.getReturnType().getTypeParameters().length == 0) {
813          returnVal = method.getReturnType();
814       } else if (method.getReturnType().equals(ListenableFuture.class)) {
815          ParameterizedType futureType = ((ParameterizedType) method.getGenericReturnType());
816          returnVal = futureType.getActualTypeArguments()[0];
817          if (returnVal instanceof WildcardType)
818             returnVal = WildcardType.class.cast(returnVal).getUpperBounds()[0];
819       } else {
820          returnVal = method.getGenericReturnType();
821       }
822       return returnVal;
823    }
824 
825    @SuppressWarnings({ "unchecked", "rawtypes" })
826    public static Key<? extends Function<HttpResponse, ?>> getJsonParserKeyForMethodAnType(Method method, Type returnVal) {
827       ParameterizedType parserType;
828       if (method.isAnnotationPresent(Unwrap.class)) {
829          int depth = method.getAnnotation(Unwrap.class).depth();
830          Class edgeCollection = method.getAnnotation(Unwrap.class).edgeCollection();
831          if (depth == 1 && edgeCollection == Map.class)
832             parserType = Types.newParameterizedType(UnwrapOnlyJsonValue.class, returnVal);
833          else if (depth == 2 && edgeCollection == Map.class)
834             parserType = Types.newParameterizedType(UnwrapOnlyNestedJsonValue.class, returnVal);
835          else if (depth == 2 && edgeCollection == Set.class)
836             parserType = Types.newParameterizedType(UnwrapOnlyJsonValueInSet.class, returnVal);
837          else if (depth == 3 && edgeCollection == Set.class)
838             parserType = Types.newParameterizedType(UnwrapOnlyNestedJsonValueInSet.class, returnVal);
839          else
840             throw new IllegalStateException(String.format("depth(%d) edgeCollection(%s) not yet supported for @Unwrap",
841                   depth, edgeCollection));
842       } else {
843          parserType = Types.newParameterizedType(ParseJson.class, returnVal);
844       }
845       return (Key<? extends Function<HttpResponse, ?>>) Key.get(parserType);
846    }
847 
848    public static Class<? extends HandlerWithResult<?>> getSaxResponseParserClassOrNull(Method method) {
849       XMLResponseParser annotation = method.getAnnotation(XMLResponseParser.class);
850       if (annotation != null) {
851          return annotation.value();
852       }
853       return null;
854    }
855 
856    public org.jclouds.rest.MapBinder getMapPayloadBinderOrNull(Method method, Object... args) {
857       if (args != null) {
858          for (Object arg : args) {
859             if (arg instanceof Object[]) {
860                Object[] postBinders = (Object[]) arg;
861                if (postBinders.length == 0) {
862                } else if (postBinders.length == 1) {
863                   if (postBinders[0] instanceof org.jclouds.rest.MapBinder) {
864                      org.jclouds.rest.MapBinder binder = (org.jclouds.rest.MapBinder) postBinders[0];
865                      injector.injectMembers(binder);
866                      return binder;
867                   }
868                } else {
869                   if (postBinders[0] instanceof org.jclouds.rest.MapBinder) {
870                      throw new IllegalArgumentException("we currently do not support multiple varargs postBinders in: "
871                            + method.getName());
872                   }
873                }
874             } else if (arg instanceof org.jclouds.rest.MapBinder) {
875                org.jclouds.rest.MapBinder binder = (org.jclouds.rest.MapBinder) arg;
876                injector.injectMembers(binder);
877                return binder;
878             }
879          }
880       }
881       if (method.isAnnotationPresent(MapBinder.class)) {
882          return injector.getInstance(method.getAnnotation(MapBinder.class).value());
883       } else if (method.isAnnotationPresent(org.jclouds.rest.annotations.Payload.class)) {
884          return injector.getInstance(BindMapToStringPayload.class);
885       }
886       return null;
887    }
888 
889    public static Set<String> getHttpMethods(Method method) {
890       Builder<String> methodsBuilder = ImmutableSet.<String> builder();
891       for (Annotation annotation : method.getAnnotations()) {
892          HttpMethod http = annotation.annotationType().getAnnotation(HttpMethod.class);
893          if (http != null)
894             methodsBuilder.add(http.value());
895       }
896       Set<String> methods = methodsBuilder.build();
897       return (methods.size() == 0) ? null : methods;
898    }
899 
900    public String getHttpMethodOrConstantOrThrowException(Method method) {
901       Set<String> requests = getHttpMethods(method);
902       if (requests == null || requests.size() != 1) {
903          throw new IllegalStateException(
904                "You must use at least one, but no more than one http method or pathparam annotation on: "
905                      + method.toString());
906       }
907       return requests.iterator().next();
908    }
909 
910    public boolean shouldAddHostHeader(Method method) {
911       if (declaring.isAnnotationPresent(VirtualHost.class) || method.isAnnotationPresent(VirtualHost.class)) {
912          return true;
913       }
914       return false;
915    }
916 
917    private static final Predicate<Set<?>> notEmpty = new Predicate<Set<?>>() {
918       public boolean apply(Set<?> input) {
919          return input.size() >= 1;
920       }
921    };
922 
923    public GeneratedHttpRequest<T> decorateRequest(GeneratedHttpRequest<T> request) throws NegativeArraySizeException,
924             ExecutionException {
925       OUTER: for (Entry<Integer, Set<Annotation>> entry : concat(//
926                filterValues(methodToIndexOfParamToBinderParamAnnotation.get(request.getJavaMethod()).asMap(), notEmpty)
927                         .entrySet(), //
928                filterValues(methodToIndexOfParamToWrapWithAnnotation.get(request.getJavaMethod()).asMap(), notEmpty)
929                         .entrySet())) {
930          boolean shouldBreak = false;
931          Annotation annotation = Iterables.get(entry.getValue(), 0);
932          Binder binder;
933          if (annotation instanceof BinderParam)
934             binder = injector.getInstance(BinderParam.class.cast(annotation).value());
935          else
936             binder = injector.getInstance(BindToJsonPayloadWrappedWith.Factory.class).create(
937                      WrapWith.class.cast(annotation).value());
938          if (request.getArgs().size() >= entry.getKey() + 1 && request.getArgs().get(entry.getKey()) != null) {
939             Object input;
940             Class<?> parameterType = request.getJavaMethod().getParameterTypes()[entry.getKey()];
941             Class<? extends Object> argType = request.getArgs().get(entry.getKey()).getClass();
942             if (!argType.isArray() && request.getJavaMethod().isVarArgs() && parameterType.isArray()) {
943                int arrayLength = request.getArgs().size() - request.getJavaMethod().getParameterTypes().length + 1;
944                if (arrayLength == 0)
945                   break OUTER;
946                input = (Object[]) Array.newInstance(request.getArgs().get(entry.getKey()).getClass(), arrayLength);
947                System.arraycopy(request.getArgs().toArray(), entry.getKey(), input, 0, arrayLength);
948                shouldBreak = true;
949             } else if (argType.isArray() && request.getJavaMethod().isVarArgs() && parameterType.isArray()) {
950                input = request.getArgs().get(entry.getKey());
951             } else {
952                input = request.getArgs().get(entry.getKey());
953                if (input.getClass().isArray()) {
954                   Object[] payloadArray = (Object[]) input;
955                   input = payloadArray.length > 0 ? payloadArray[0] : null;
956                }
957             }
958             if (input != null) {
959                request = binder.bindToRequest(request, input);
960             }
961             if (shouldBreak)
962                break OUTER;
963          } else {
964             // either arg is null, or request.getArgs().size() < entry.getKey() + 1
965             // in either case, we require that null be allowed
966             // (first, however, let's make sure we have enough args on the actual method)
967             if (entry.getKey() >= request.getJavaMethod().getParameterAnnotations().length) {
968                // not known whether this happens
969                throw new IllegalArgumentException("Argument index " + (entry.getKey() + 1)
970                         + " is out of bounds for method " + request.getJavaMethod());
971             }
972 
973             if (request.getJavaMethod().isVarArgs()
974                      && entry.getKey() + 1 == request.getJavaMethod().getParameterTypes().length)
975                // allow null/missing for var args
976                continue OUTER;
977 
978             Annotation[] annotations = request.getJavaMethod().getParameterAnnotations()[entry.getKey()];
979             for (Annotation a : annotations) {
980                if (Nullable.class.isAssignableFrom(a.annotationType()))
981                   continue OUTER;
982             }
983             Preconditions.checkNotNull(null, request.getJavaMethod().getName() + " parameter " + (entry.getKey() + 1));
984          }
985       }
986 
987       return request;
988    }
989 
990    public static Map<Integer, Set<Annotation>> indexWithOnlyOneAnnotation(Method method, String description,
991          Cache<Method, Cache<Integer, Set<Annotation>>> toRefine) throws ExecutionException {
992       Map<Integer, Set<Annotation>> indexToPayloadAnnotation = indexWithAtLeastOneAnnotation(method, toRefine);
993       if (indexToPayloadAnnotation.size() > 1) {
994          throw new IllegalStateException(String.format(
995                "You must not specify more than one %s annotation on: %s; found %s", description, method.toString(),
996                indexToPayloadAnnotation));
997       }
998       return indexToPayloadAnnotation;
999    }
1000 
1001    private static Map<Integer, Set<Annotation>> indexWithAtLeastOneAnnotation(Method method,
1002          Cache<Method, Cache<Integer, Set<Annotation>>> toRefine) throws ExecutionException {
1003       Map<Integer, Set<Annotation>> indexToPayloadAnnotation = filterValues(toRefine.get(method).asMap(),
1004             new Predicate<Set<Annotation>>() {
1005                public boolean apply(Set<Annotation> input) {
1006                   return input.size() == 1;
1007                }
1008             });
1009       return indexToPayloadAnnotation;
1010    }
1011 
1012    private HttpRequestOptions findOptionsIn(Method method, Object... args) throws ExecutionException {
1013       for (int index : methodToIndexesOfOptions.get(method)) {
1014          if (args.length >= index + 1) {// accomodate varargs
1015             if (args[index] instanceof Object[]) {
1016                Object[] options = (Object[]) args[index];
1017                if (options.length == 0) {
1018                } else if (options.length == 1) {
1019                   if (options[0] instanceof HttpRequestOptions) {
1020                      HttpRequestOptions binder = (HttpRequestOptions) options[0];
1021                      injector.injectMembers(binder);
1022                      return binder;
1023                   }
1024                } else {
1025                   if (options[0] instanceof HttpRequestOptions) {
1026                      throw new IllegalArgumentException("we currently do not support multiple varargs options in: "
1027                            + method.getName());
1028                   }
1029                }
1030             } else {
1031                return (HttpRequestOptions) args[index];
1032             }
1033          }
1034       }
1035       return null;
1036    }
1037 
1038    public Multimap<String, String> buildHeaders(Collection<Entry<String, String>> tokenValues, Method method,
1039          final Object... args) throws ExecutionException {
1040       Multimap<String, String> headers = LinkedHashMultimap.create();
1041       addHeaderIfAnnotationPresentOnMethod(headers, method, tokenValues);
1042       Cache<Integer, Set<Annotation>> indexToHeaderParam = methodToIndexOfParamToHeaderParamAnnotations.get(method);
1043       for (Entry<Integer, Set<Annotation>> entry : indexToHeaderParam.asMap().entrySet()) {
1044          for (Annotation key : entry.getValue()) {
1045             String value = args[entry.getKey()].toString();
1046             value = Strings2.replaceTokens(value, tokenValues);
1047             headers.put(((HeaderParam) key).value(), value);
1048          }
1049       }
1050       addProducesIfPresentOnTypeOrMethod(headers, method);
1051       addConsumesIfPresentOnTypeOrMethod(headers, method);
1052       return headers;
1053    }
1054 
1055    void addConsumesIfPresentOnTypeOrMethod(Multimap<String, String> headers, Method method) {
1056       List<String> accept = getAcceptHeadersOrNull(method);
1057       if (accept.size() > 0)
1058          headers.replaceValues(ACCEPT, accept);
1059    }
1060 
1061    private static List<String> getAcceptHeadersOrNull(Method method) {
1062       List<String> accept = Collections.emptyList();
1063       if (method.getDeclaringClass().isAnnotationPresent(Consumes.class)) {
1064          Consumes header = method.getDeclaringClass().getAnnotation(Consumes.class);
1065          accept = asList(header.value());
1066       }
1067       if (method.isAnnotationPresent(Consumes.class)) {
1068          Consumes header = method.getAnnotation(Consumes.class);
1069          accept = asList(header.value());
1070       }
1071       return accept;
1072    }
1073 
1074    void addProducesIfPresentOnTypeOrMethod(Multimap<String, String> headers, Method method) {
1075       if (declaring.isAnnotationPresent(Produces.class)) {
1076          Produces header = declaring.getAnnotation(Produces.class);
1077          headers.replaceValues(CONTENT_TYPE, asList(header.value()));
1078       }
1079       if (method.isAnnotationPresent(Produces.class)) {
1080          Produces header = method.getAnnotation(Produces.class);
1081          headers.replaceValues(CONTENT_TYPE, asList(header.value()));
1082       }
1083    }
1084 
1085    public void addHeaderIfAnnotationPresentOnMethod(Multimap<String, String> headers, Method method,
1086          Collection<Entry<String, String>> tokenValues) {
1087       if (declaring.isAnnotationPresent(Headers.class)) {
1088          Headers header = declaring.getAnnotation(Headers.class);
1089          addHeader(headers, header, tokenValues);
1090       }
1091       if (method.isAnnotationPresent(Headers.class)) {
1092          Headers header = method.getAnnotation(Headers.class);
1093          addHeader(headers, header, tokenValues);
1094       }
1095    }
1096 
1097    private void addHeader(Multimap<String, String> headers, Headers header,
1098          Collection<Entry<String, String>> tokenValues) {
1099       for (int i = 0; i < header.keys().length; i++) {
1100          String value = header.values()[i];
1101          value = Strings2.replaceTokens(value, tokenValues);
1102          headers.put(header.keys()[i], value);
1103       }
1104 
1105    }
1106 
1107    List<? extends Part> getParts(Method method, Object[] args, Iterable<Entry<String, String>> iterable) throws ExecutionException {
1108       List<Part> parts = newLinkedList();
1109       Cache<Integer, Set<Annotation>> indexToPartParam = methodToIndexOfParamToPartParamAnnotations.get(method);
1110       for (Entry<Integer, Set<Annotation>> entry : indexToPartParam.asMap().entrySet()) {
1111          for (Annotation key : entry.getValue()) {
1112             PartParam param = (PartParam) key;
1113             PartOptions options = new PartOptions();
1114             if (!PartParam.NO_CONTENT_TYPE.equals(param.contentType()))
1115                options.contentType(param.contentType());
1116             if (!PartParam.NO_FILENAME.equals(param.filename()))
1117                options.filename(Strings2.replaceTokens(param.filename(), iterable));
1118             Object arg = args[entry.getKey()];
1119             Preconditions.checkNotNull(arg, param.name());
1120             Part part = Part.create(param.name(), newPayload(arg), options);
1121             parts.add(part);
1122          }
1123       }
1124       return parts;
1125    }
1126 
1127    public static HttpRequest findHttpRequestInArgs(Object[] args) {
1128       if (args == null)
1129          return null;
1130       for (int i = 0; i < args.length; i++)
1131          if (args[i] instanceof HttpRequest)
1132             return HttpRequest.class.cast(args[i]);
1133       return null;
1134    }
1135 
1136    public static Payload findPayloadInArgs(Object[] args) {
1137       if (args == null)
1138          return null;
1139       for (int i = 0; i < args.length; i++)
1140          if (args[i] instanceof Payload)
1141             return Payload.class.cast(args[i]);
1142          else if (args[i] instanceof PayloadEnclosing)
1143             return PayloadEnclosing.class.cast(args[i]).getPayload();
1144       return null;
1145    }
1146 
1147    private Multimap<String, String> getPathParamKeyValues(Method method, Object... args) throws ExecutionException {
1148       Multimap<String, String> pathParamValues = LinkedHashMultimap.create();
1149       Cache<Integer, Set<Annotation>> indexToPathParam = methodToIndexOfParamToPathParamAnnotations.get(method);
1150 
1151       Cache<Integer, Set<Annotation>> indexToParamExtractor = methodToIndexOfParamToParamParserAnnotations.get(method);
1152       for (Entry<Integer, Set<Annotation>> entry : indexToPathParam.asMap().entrySet()) {
1153          for (Annotation key : entry.getValue()) {
1154             Set<Annotation> extractors = indexToParamExtractor.get(entry.getKey());
1155             String paramKey = ((PathParam) key).value();
1156             String paramValue;
1157             if (extractors != null && extractors.size() > 0) {
1158                ParamParser extractor = (ParamParser) extractors.iterator().next();
1159                paramValue = injector.getInstance(extractor.value()).apply(args[entry.getKey()]);
1160             } else {
1161                paramValue = args[entry.getKey()].toString();
1162             }
1163             pathParamValues.put(paramKey, paramValue);
1164          }
1165       }
1166 
1167       if (method.isAnnotationPresent(PathParam.class) && method.isAnnotationPresent(ParamParser.class)) {
1168          String paramKey = method.getAnnotation(PathParam.class).value();
1169          String paramValue = injector.getInstance(method.getAnnotation(ParamParser.class).value()).apply(args);
1170          pathParamValues.put(paramKey, paramValue);
1171 
1172       }
1173       return pathParamValues;
1174    }
1175 
1176    private Multimap<String, String> encodeValues(Multimap<String, String> unencoded, char... skips) {
1177       Multimap<String, String> encoded = LinkedHashMultimap.create();
1178       for (Entry<String, String> entry : unencoded.entries()) {
1179          encoded.put(entry.getKey(), Strings2.urlEncode(entry.getValue(), skips));
1180       }
1181       return encoded;
1182    }
1183 
1184    private Multimap<String, String> getMatrixParamKeyValues(Method method, Object... args) throws ExecutionException {
1185       Multimap<String, String> matrixParamValues = LinkedHashMultimap.create();
1186       Cache<Integer, Set<Annotation>> indexToMatrixParam = methodToIndexOfParamToMatrixParamAnnotations.get(method);
1187 
1188       Cache<Integer, Set<Annotation>> indexToParamExtractor = methodToIndexOfParamToParamParserAnnotations.get(method);
1189       for (Entry<Integer, Set<Annotation>> entry : indexToMatrixParam.asMap().entrySet()) {
1190          for (Annotation key : entry.getValue()) {
1191             Set<Annotation> extractors = indexToParamExtractor.get(entry.getKey());
1192             String paramKey = ((MatrixParam) key).value();
1193             String paramValue;
1194             if (extractors != null && extractors.size() > 0) {
1195                ParamParser extractor = (ParamParser) extractors.iterator().next();
1196                paramValue = injector.getInstance(extractor.value()).apply(args[entry.getKey()]);
1197             } else {
1198                paramValue = args[entry.getKey()].toString();
1199             }
1200             matrixParamValues.put(paramKey, paramValue);
1201          }
1202       }
1203 
1204       if (method.isAnnotationPresent(MatrixParam.class) && method.isAnnotationPresent(ParamParser.class)) {
1205          String paramKey = method.getAnnotation(MatrixParam.class).value();
1206          String paramValue = injector.getInstance(method.getAnnotation(ParamParser.class).value()).apply(args);
1207          matrixParamValues.put(paramKey, paramValue);
1208 
1209       }
1210       return matrixParamValues;
1211    }
1212 
1213    private Multimap<String, String> getFormParamKeyValues(Method method, Object... args) throws ExecutionException {
1214       Multimap<String, String> formParamValues = LinkedHashMultimap.create();
1215       Cache<Integer, Set<Annotation>> indexToFormParam = methodToIndexOfParamToFormParamAnnotations.get(method);
1216 
1217       Cache<Integer, Set<Annotation>> indexToParamExtractor = methodToIndexOfParamToParamParserAnnotations.get(method);
1218       for (Entry<Integer, Set<Annotation>> entry : indexToFormParam.asMap().entrySet()) {
1219          for (Annotation key : entry.getValue()) {
1220             Set<Annotation> extractors = indexToParamExtractor.get(entry.getKey());
1221             String paramKey = ((FormParam) key).value();
1222             String paramValue;
1223             if (extractors != null && extractors.size() > 0) {
1224                ParamParser extractor = (ParamParser) extractors.iterator().next();
1225                paramValue = injector.getInstance(extractor.value()).apply(args[entry.getKey()]);
1226             } else {
1227                Object pvo = args[entry.getKey()];
1228                Preconditions.checkNotNull(pvo, paramKey);
1229                paramValue = pvo.toString();
1230             }
1231             formParamValues.put(paramKey, paramValue);
1232          }
1233       }
1234 
1235       if (method.isAnnotationPresent(FormParam.class) && method.isAnnotationPresent(ParamParser.class)) {
1236          String paramKey = method.getAnnotation(FormParam.class).value();
1237          String paramValue = injector.getInstance(method.getAnnotation(ParamParser.class).value()).apply(args);
1238          formParamValues.put(paramKey, paramValue);
1239 
1240       }
1241       return formParamValues;
1242    }
1243 
1244    private Multimap<String, String> getQueryParamKeyValues(Method method, Object... args) throws ExecutionException {
1245       Multimap<String, String> queryParamValues = LinkedHashMultimap.create();
1246       Cache<Integer, Set<Annotation>> indexToQueryParam = methodToIndexOfParamToQueryParamAnnotations.get(method);
1247 
1248       Cache<Integer, Set<Annotation>> indexToParamExtractor = methodToIndexOfParamToParamParserAnnotations.get(method);
1249       for (Entry<Integer, Set<Annotation>> entry : indexToQueryParam.asMap().entrySet()) {
1250          for (Annotation key : entry.getValue()) {
1251             Set<Annotation> extractors = indexToParamExtractor.get(entry.getKey());
1252             String paramKey = ((QueryParam) key).value();
1253             String paramValue;
1254             if (extractors != null && extractors.size() > 0) {
1255                ParamParser extractor = (ParamParser) extractors.iterator().next();
1256                paramValue = injector.getInstance(extractor.value()).apply(args[entry.getKey()]);
1257             } else {
1258                paramValue = args[entry.getKey()].toString();
1259             }
1260             queryParamValues.put(paramKey, paramValue);
1261          }
1262       }
1263 
1264       if (method.isAnnotationPresent(QueryParam.class) && method.isAnnotationPresent(ParamParser.class)) {
1265          String paramKey = method.getAnnotation(QueryParam.class).value();
1266          String paramValue = injector.getInstance(method.getAnnotation(ParamParser.class).value()).apply(args);
1267          queryParamValues.put(paramKey, paramValue);
1268 
1269       }
1270       return queryParamValues;
1271    }
1272 
1273    private Map<String, String> buildPostParams(Method method, Object... args) throws ExecutionException {
1274       Map<String, String> postParams = newHashMap();
1275       Cache<Integer, Set<Annotation>> indexToPathParam = methodToIndexOfParamToPostParamAnnotations.get(method);
1276       Cache<Integer, Set<Annotation>> indexToParamExtractor = methodToIndexOfParamToParamParserAnnotations.get(method);
1277       for (Entry<Integer, Set<Annotation>> entry : indexToPathParam.asMap().entrySet()) {
1278          for (Annotation key : entry.getValue()) {
1279             Set<Annotation> extractors = indexToParamExtractor.get(entry.getKey());
1280             String paramKey = ((PayloadParam) key).value();
1281             String paramValue;
1282             if (extractors != null && extractors.size() > 0) {
1283                ParamParser extractor = (ParamParser) extractors.iterator().next();
1284                paramValue = injector.getInstance(extractor.value()).apply(args[entry.getKey()]);
1285             } else {
1286                paramValue = args[entry.getKey()] != null ? args[entry.getKey()].toString() : null;
1287             }
1288             postParams.put(paramKey, paramValue);
1289 
1290          }
1291       }
1292       return postParams;
1293    }
1294 
1295 }