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
22
23
24
25
26 import java.lang.annotation.Annotation;
27 import java.lang.reflect.InvocationHandler;
28 import java.lang.reflect.Method;
29 import java.util.NoSuchElementException;
30 import java.util.concurrent.ExecutionException;
31
32 import javax.annotation.Resource;
33 import javax.inject.Named;
34 import javax.inject.Qualifier;
35 import javax.inject.Singleton;
36
37 import org.jclouds.Constants;
38 import org.jclouds.concurrent.ExceptionParsingListenableFuture;
39 import org.jclouds.http.HttpRequest;
40 import org.jclouds.http.HttpResponse;
41 import org.jclouds.http.TransformingHttpCommand;
42 import org.jclouds.internal.ClassMethodArgs;
43 import org.jclouds.logging.Logger;
44 import org.jclouds.rest.AuthorizationException;
45 import org.jclouds.rest.InvocationContext;
46 import org.jclouds.rest.annotations.Delegate;
47 import org.jclouds.util.Throwables2;
48
49 import com.google.common.base.Function;
50 import com.google.common.base.Predicate;
51 import com.google.common.cache.Cache;
52 import com.google.common.collect.ImmutableList;
53 import com.google.common.collect.Iterables;
54 import com.google.common.util.concurrent.Futures;
55 import com.google.common.util.concurrent.ListenableFuture;
56 import com.google.inject.Inject;
57 import com.google.inject.Injector;
58 import com.google.inject.Key;
59 import com.google.inject.Provides;
60 import com.google.inject.ProvisionException;
61 import com.google.inject.TypeLiteral;
62
63 @Singleton
64 public class AsyncRestClientProxy<T> implements InvocationHandler {
65 private final Injector injector;
66 private final RestAnnotationProcessor<T> annotationProcessor;
67 private final Class<T> declaring;
68 private final Factory commandFactory;
69
70
71
72
73 @Inject(optional = true)
74 @Named(Constants.PROPERTY_REQUEST_TIMEOUT)
75 protected long requestTimeoutMilliseconds = 30000;
76
77 @Resource
78 protected Logger logger = Logger.NULL;
79 private final Cache<ClassMethodArgs, Object> delegateMap;
80
81 @SuppressWarnings("unchecked")
82 @Inject
83 public AsyncRestClientProxy(Injector injector, Factory factory, RestAnnotationProcessor<T> util,
84 TypeLiteral<T> typeLiteral, @Named("async") Cache<ClassMethodArgs, Object> delegateMap) {
85 this.injector = injector;
86 this.annotationProcessor = util;
87 this.declaring = (Class<T>) typeLiteral.getRawType();
88 this.commandFactory = factory;
89 this.delegateMap = delegateMap;
90 }
91
92 private static final Predicate<Annotation> isQualifierPresent = new Predicate<Annotation>() {
93
94 @Override
95 public boolean apply(Annotation input) {
96 return input.annotationType().isAnnotationPresent(Qualifier.class);
97 }
98
99 };
100
101 public Object invoke(Object o, Method method, Object[] args) throws Throwable {
102 if (method.getName().equals("equals")) {
103 return this.equals(o);
104 } else if (method.getName().equals("toString")) {
105 return this.toString();
106 } else if (method.getName().equals("hashCode")) {
107 return this.hashCode();
108 } else if (method.isAnnotationPresent(Provides.class)) {
109 try {
110 try {
111 Annotation qualifier = Iterables.find(ImmutableList.copyOf(method.getAnnotations()), isQualifierPresent);
112 return injector.getInstance(Key.get(method.getGenericReturnType(), qualifier));
113 } catch (NoSuchElementException e) {
114 return injector.getInstance(Key.get(method.getGenericReturnType()));
115 }
116 } catch (ProvisionException e) {
117 AuthorizationException aex = Throwables2.getFirstThrowableOfType(e, AuthorizationException.class);
118 if (aex != null)
119 throw aex;
120 throw e;
121 }
122 } else if (method.isAnnotationPresent(Delegate.class)) {
123 return delegateMap.get(new ClassMethodArgs(method.getReturnType(), method, args));
124 } else if (annotationProcessor.getDelegateOrNull(method) != null
125 && ListenableFuture.class.isAssignableFrom(method.getReturnType())) {
126 return createListenableFuture(method, args);
127 } else {
128 throw new RuntimeException("method is intended solely to set constants: " + method);
129 }
130 }
131
132 @SuppressWarnings( { "unchecked", "rawtypes" })
133 private ListenableFuture<?> createListenableFuture(Method method, Object[] args) throws ExecutionException {
134 method = annotationProcessor.getDelegateOrNull(method);
135 logger.trace("Converting %s.%s", declaring.getSimpleName(), method.getName());
136 Function<Exception, ?> exceptionParser = annotationProcessor
137 .createExceptionParserOrThrowResourceNotFoundOn404IfNoAnnotation(method);
138
139
140 if (exceptionParser instanceof InvocationContext) {
141 ((InvocationContext) exceptionParser).setContext((HttpRequest) null);
142 }
143 ListenableFuture<?> result;
144 try {
145 GeneratedHttpRequest<T> request = annotationProcessor.createRequest(method, args);
146 if (exceptionParser instanceof InvocationContext) {
147 ((InvocationContext) exceptionParser).setContext(request);
148 }
149 logger.trace("Converted %s.%s to %s", declaring.getSimpleName(), method.getName(), request.getRequestLine());
150
151 Function<HttpResponse, ?> transformer = annotationProcessor.createResponseParser(method, request);
152 logger.trace("Response from %s.%s is parsed by %s", declaring.getSimpleName(), method.getName(), transformer
153 .getClass().getSimpleName());
154
155 logger.debug("Invoking %s.%s", declaring.getSimpleName(), method.getName());
156 result = commandFactory.create(request, transformer).execute();
157
158 } catch (RuntimeException e) {
159 AuthorizationException aex = Throwables2.getFirstThrowableOfType(e, AuthorizationException.class);
160 if (aex != null)
161 e = aex;
162 if (exceptionParser != null) {
163 try {
164 return Futures.immediateFuture(exceptionParser.apply(e));
165 } catch (Exception ex) {
166 return Futures.immediateFailedFuture(ex);
167 }
168 }
169 return Futures.immediateFailedFuture(e);
170 }
171
172 if (exceptionParser != null) {
173 logger.trace("Exceptions from %s.%s are parsed by %s", declaring.getSimpleName(), method.getName(),
174 exceptionParser.getClass().getSimpleName());
175 result = new ExceptionParsingListenableFuture(result, exceptionParser);
176 }
177 return result;
178 }
179
180 public static interface Factory {
181 public TransformingHttpCommand<?> create(HttpRequest request, Function<HttpResponse, ?> transformer);
182 }
183
184 @Override
185 public boolean equals(Object obj) {
186 if (obj == null || !(obj instanceof AsyncRestClientProxy<?>))
187 return false;
188 AsyncRestClientProxy<?> other = (AsyncRestClientProxy<?>) obj;
189 if (other == this)
190 return true;
191 if (other.declaring != this.declaring)
192 return false;
193 return super.equals(obj);
194 }
195
196 @Override
197 public int hashCode() {
198 return declaring.hashCode();
199 }
200
201 public String toString() {
202 return "Client Proxy for :" + declaring.getName();
203 }
204 }