1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.jclouds.concurrent.internal;
20
21 import static com.google.common.base.Preconditions.checkState;
22
23 import java.lang.reflect.InvocationHandler;
24 import java.lang.reflect.Method;
25 import java.lang.reflect.Proxy;
26 import java.util.Arrays;
27 import java.util.Map;
28 import java.util.Set;
29 import java.util.concurrent.ExecutionException;
30 import java.util.concurrent.TimeUnit;
31
32 import javax.inject.Inject;
33 import javax.inject.Named;
34
35 import com.google.common.collect.Iterables;
36 import com.google.common.collect.Multimap;
37 import org.jclouds.concurrent.Timeout;
38 import org.jclouds.internal.ClassMethodArgs;
39 import org.jclouds.rest.annotations.Delegate;
40 import org.jclouds.util.Throwables2;
41
42 import com.google.common.cache.Cache;
43 import com.google.common.collect.ImmutableSet;
44 import com.google.common.collect.Maps;
45 import com.google.common.util.concurrent.ListenableFuture;
46 import com.google.inject.ProvisionException;
47
48
49
50
51
52
53 @SuppressWarnings("deprecation")
54 public class SyncProxy implements InvocationHandler {
55
56 @SuppressWarnings("unchecked")
57 public static <T> T proxy(Class<T> clazz, Object async,
58 @Named("sync") Cache<ClassMethodArgs, Object> delegateMap,
59 Map<Class<?>, Class<?>> sync2Async, Map<String, Long> timeouts) throws IllegalArgumentException, SecurityException,
60 NoSuchMethodException {
61 return (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class<?>[] { clazz },
62 new SyncProxy(clazz, async, delegateMap, sync2Async, timeouts));
63 }
64
65 private final Object delegate;
66 private final Class<?> declaring;
67 private final Map<Method, Method> methodMap;
68 private final Map<Method, Method> syncMethodMap;
69 private final Map<Method, Long> timeoutMap;
70 private final Cache<ClassMethodArgs, Object> delegateMap;
71 private final Map<Class<?>, Class<?>> sync2Async;
72 private static final Set<Method> objectMethods = ImmutableSet.of(Object.class.getMethods());
73
74 @Inject
75 private SyncProxy(Class<?> declaring, Object async,
76 @Named("sync") Cache<ClassMethodArgs, Object> delegateMap, Map<Class<?>,
77 Class<?>> sync2Async, final Map<String, Long> timeouts)
78 throws SecurityException, NoSuchMethodException {
79 this.delegateMap = delegateMap;
80 this.delegate = async;
81 this.declaring = declaring;
82 this.sync2Async = sync2Async;
83 if (!declaring.isAnnotationPresent(Timeout.class)) {
84 throw new IllegalArgumentException(String.format("type %s does not specify a default @Timeout", declaring));
85 }
86 Timeout typeTimeout = declaring.getAnnotation(Timeout.class);
87 long typeNanos = convertToNanos(typeTimeout);
88
89 methodMap = Maps.newHashMap();
90 syncMethodMap = Maps.newHashMap();
91 timeoutMap = Maps.newHashMap();
92 for (Method method : declaring.getMethods()) {
93 if (!objectMethods.contains(method)) {
94 Method delegatedMethod = delegate.getClass().getMethod(method.getName(), method.getParameterTypes());
95 if (!Arrays.equals(delegatedMethod.getExceptionTypes(), method.getExceptionTypes()))
96 throw new IllegalArgumentException(String.format(
97 "method %s has different typed exceptions than delegated method %s", method, delegatedMethod));
98 if (delegatedMethod.getReturnType().isAssignableFrom(ListenableFuture.class)) {
99 timeoutMap.put(method, getTimeout(method, typeNanos, timeouts));
100 methodMap.put(method, delegatedMethod);
101 } else {
102 syncMethodMap.put(method, delegatedMethod);
103 }
104 }
105 }
106 }
107
108 private Long getTimeout(Method method, long typeNanos, final Map<String,Long> timeouts) {
109 Long timeout = overrideTimeout(method, timeouts);
110 if (timeout == null && method.isAnnotationPresent(Timeout.class)) {
111 Timeout methodTimeout = method.getAnnotation(Timeout.class);
112 timeout = convertToNanos(methodTimeout);
113 }
114 return timeout != null ? timeout : typeNanos;
115
116 }
117
118 static long convertToNanos(Timeout timeout) {
119 long methodNanos = TimeUnit.NANOSECONDS.convert(timeout.duration(), timeout.timeUnit());
120 return methodNanos;
121 }
122
123 public Object invoke(Object o, Method method, Object[] args) throws Throwable {
124 if (method.getName().equals("equals")) {
125 return this.equals(o);
126 } else if (method.getName().equals("hashCode")) {
127 return this.hashCode();
128 } else if (method.getName().equals("toString")) {
129 return this.toString();
130 } else if (method.isAnnotationPresent(Delegate.class)) {
131 Class<?> asyncClass = sync2Async.get(method.getReturnType());
132 checkState(asyncClass != null, "please configure corresponding async class for " + method.getReturnType()
133 + " in your RestClientModule");
134 Object returnVal = delegateMap.get(new ClassMethodArgs(asyncClass, method, args));
135 return returnVal;
136 } else if (syncMethodMap.containsKey(method)) {
137 return syncMethodMap.get(method).invoke(delegate, args);
138 } else {
139 try {
140 return ((ListenableFuture<?>) methodMap.get(method).invoke(delegate, args)).get(timeoutMap.get(method),
141 TimeUnit.NANOSECONDS);
142 } catch (ProvisionException e) {
143 throw Throwables2.returnFirstExceptionIfInListOrThrowStandardExceptionOrCause(method.getExceptionTypes(), e);
144 } catch (ExecutionException e) {
145 throw Throwables2.returnFirstExceptionIfInListOrThrowStandardExceptionOrCause(method.getExceptionTypes(), e);
146 } catch (Exception e) {
147 throw Throwables2.returnFirstExceptionIfInListOrThrowStandardExceptionOrCause(method.getExceptionTypes(), e);
148 }
149 }
150 }
151
152
153 private Long overrideTimeout(final Method method, final Map<String, Long> timeouts) {
154 if (timeouts == null) {
155 return null;
156 }
157 final String className = declaring.getSimpleName();
158 Long timeout = timeouts.get(className + "." + method.getName());
159 if (timeout == null) {
160 timeout = timeouts.get(className);
161 }
162 return timeout != null ? TimeUnit.MILLISECONDS.toNanos(timeout) : null;
163 }
164
165 @Override
166 public boolean equals(Object obj) {
167 if (obj == null || !(obj instanceof SyncProxy))
168 return false;
169 SyncProxy other = (SyncProxy) obj;
170 if (other == this)
171 return true;
172 if (other.declaring != this.declaring)
173 return false;
174 return super.equals(obj);
175 }
176
177 @Override
178 public int hashCode() {
179 return declaring.hashCode();
180 }
181
182 public String toString() {
183 return "Sync Proxy for: " + delegate.getClass().getSimpleName();
184 }
185 }