View Javadoc

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