1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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.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
163
164
165
166 public class RestAnnotationProcessor<T> {
167
168 @Resource
169 protected Logger logger = Logger.NULL;
170
171 private final Class<T> declaring;
172
173
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);
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
544 ImmutableMultimap.Builder<String, String> headersBuilder = ImmutableMultimap.builder();
545
546
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
965
966
967 if (entry.getKey() >= request.getJavaMethod().getParameterAnnotations().length) {
968
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
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) {
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 }