1 | /** |
2 | * Licensed to jclouds, Inc. (jclouds) under one or more |
3 | * contributor license agreements. See the NOTICE file |
4 | * distributed with this work for additional information |
5 | * regarding copyright ownership. jclouds licenses this file |
6 | * to you under the Apache License, Version 2.0 (the |
7 | * "License"); you may not use this file except in compliance |
8 | * with the License. You may obtain a copy of the License at |
9 | * |
10 | * http://www.apache.org/licenses/LICENSE-2.0 |
11 | * |
12 | * Unless required by applicable law or agreed to in writing, |
13 | * software distributed under the License is distributed on an |
14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
15 | * KIND, either express or implied. See the License for the |
16 | * specific language governing permissions and limitations |
17 | * under the License. |
18 | */ |
19 | package org.jclouds.rest; |
20 | |
21 | import static com.google.common.base.Preconditions.checkNotNull; |
22 | import static com.google.common.base.Predicates.equalTo; |
23 | import static com.google.common.base.Predicates.instanceOf; |
24 | import static com.google.common.base.Predicates.not; |
25 | import static com.google.common.base.Splitter.on; |
26 | import static com.google.common.collect.Iterables.addAll; |
27 | import static com.google.common.collect.Iterables.any; |
28 | import static com.google.common.collect.Iterables.filter; |
29 | import static com.google.inject.Scopes.SINGLETON; |
30 | import static com.google.inject.name.Names.bindProperties; |
31 | import static com.google.inject.util.Types.newParameterizedType; |
32 | import static org.jclouds.Constants.PROPERTY_API; |
33 | import static org.jclouds.Constants.PROPERTY_API_VERSION; |
34 | import static org.jclouds.Constants.PROPERTY_CREDENTIAL; |
35 | import static org.jclouds.Constants.PROPERTY_ENDPOINT; |
36 | import static org.jclouds.Constants.PROPERTY_IDENTITY; |
37 | import static org.jclouds.Constants.PROPERTY_ISO3166_CODES; |
38 | import static org.jclouds.Constants.PROPERTY_PROVIDER; |
39 | import static org.jclouds.Constants.PROPERTY_TIMEOUTS_PREFIX; |
40 | |
41 | import java.net.URI; |
42 | import java.util.*; |
43 | import java.util.Map.Entry; |
44 | |
45 | import javax.inject.Inject; |
46 | import javax.inject.Named; |
47 | import javax.inject.Singleton; |
48 | |
49 | import com.google.common.collect.*; |
50 | import org.jclouds.concurrent.MoreExecutors; |
51 | import org.jclouds.concurrent.SingleThreaded; |
52 | import org.jclouds.concurrent.config.ConfiguresExecutorService; |
53 | import org.jclouds.concurrent.config.ExecutorServiceModule; |
54 | import org.jclouds.http.RequiresHttp; |
55 | import org.jclouds.http.config.ConfiguresHttpCommandExecutorService; |
56 | import org.jclouds.http.config.JavaUrlHttpCommandExecutorServiceModule; |
57 | import org.jclouds.location.Iso3166; |
58 | import org.jclouds.location.Provider; |
59 | import org.jclouds.location.config.ProvideIso3166CodesByLocationIdViaProperties; |
60 | import org.jclouds.logging.config.LoggingModule; |
61 | import org.jclouds.logging.jdk.config.JDKLoggingModule; |
62 | import org.jclouds.rest.annotations.Api; |
63 | import org.jclouds.rest.annotations.ApiVersion; |
64 | import org.jclouds.rest.annotations.Credential; |
65 | import org.jclouds.rest.annotations.Identity; |
66 | import org.jclouds.rest.config.CredentialStoreModule; |
67 | import org.jclouds.rest.config.RestClientModule; |
68 | import org.jclouds.rest.config.RestModule; |
69 | import org.jclouds.rest.internal.RestContextImpl; |
70 | |
71 | import com.google.common.annotations.VisibleForTesting; |
72 | import com.google.common.base.Predicate; |
73 | import com.google.inject.AbstractModule; |
74 | import com.google.inject.Guice; |
75 | import com.google.inject.Injector; |
76 | import com.google.inject.Key; |
77 | import com.google.inject.Module; |
78 | import com.google.inject.Provides; |
79 | import com.google.inject.TypeLiteral; |
80 | |
81 | /** |
82 | * Creates {@link RestContext} or {@link Injector} instances based on the most commonly requested |
83 | * arguments. |
84 | * <p/> |
85 | * Note that Threadsafe objects will be bound as singletons to the Injector or Context provided. |
86 | * <p/> |
87 | * <p/> |
88 | * If no <code>Module</code>s are specified, the default {@link JDKLoggingModule logging} and |
89 | * {@link JavaUrlHttpCommandExecutorServiceModule http transports} will be installed. |
90 | * |
91 | * @author Adrian Cole, Andrew Newdigate |
92 | * @see RestContext |
93 | */ |
94 | public class RestContextBuilder<S, A> { |
95 | |
96 | static class BindPropertiesAndPrincipalContext extends AbstractModule { |
97 | protected Properties properties; |
98 | |
99 | protected BindPropertiesAndPrincipalContext(Properties properties) { |
100 | this.properties = checkNotNull(properties, "properties"); |
101 | } |
102 | |
103 | @Provides |
104 | @Singleton |
105 | @Named("CONSTANTS") |
106 | protected Multimap<String, String> constants() { |
107 | ImmutableMultimap.Builder<String, String> builder = ImmutableMultimap.<String, String> builder(); |
108 | for (Entry<Object, Object> entry : properties.entrySet()) |
109 | if (entry.getValue() != null) |
110 | builder.put(entry.getKey().toString(), entry.getValue().toString()); |
111 | return LinkedHashMultimap.create(builder.build()); |
112 | } |
113 | |
114 | @Provides |
115 | @Singleton |
116 | @Named("TIMEOUTS") |
117 | protected Map<String, Long> timeouts() { |
118 | final ImmutableMap.Builder<String, Long> builder = ImmutableMap.<String, Long> builder(); |
119 | for (final Entry<Object, Object> entry : properties.entrySet()) { |
120 | final String key = String.valueOf(entry.getKey()); |
121 | if (key.startsWith(PROPERTY_TIMEOUTS_PREFIX) && entry.getValue() != null) { |
122 | try { |
123 | final Long val = Long.valueOf(String.valueOf(entry.getValue())); |
124 | builder.put(key.replaceFirst(PROPERTY_TIMEOUTS_PREFIX, ""), val); |
125 | } catch (final Throwable t) {} |
126 | } |
127 | } |
128 | return builder.build(); |
129 | } |
130 | |
131 | @Override |
132 | protected void configure() { |
133 | Properties toBind = new Properties(); |
134 | toBind.putAll(checkNotNull(properties, "properties")); |
135 | toBind.putAll(System.getProperties()); |
136 | bindProperties(binder(), toBind); |
137 | bind(String.class).annotatedWith(Provider.class).toInstance( |
138 | checkNotNull(toBind.getProperty(PROPERTY_PROVIDER), PROPERTY_PROVIDER)); |
139 | bind(URI.class).annotatedWith(Provider.class).toInstance( |
140 | URI.create(checkNotNull(toBind.getProperty(PROPERTY_ENDPOINT), PROPERTY_ENDPOINT))); |
141 | bind(new TypeLiteral<Set<String>>() { |
142 | }).annotatedWith(Iso3166.class).toInstance( |
143 | ImmutableSet.copyOf(filter(on(',').split( |
144 | checkNotNull(toBind.getProperty(PROPERTY_ISO3166_CODES), PROPERTY_ISO3166_CODES)), |
145 | not(equalTo(""))))); |
146 | bind(new TypeLiteral<Map<String, Set<String>>>() { |
147 | }).annotatedWith(Iso3166.class).toProvider(ProvideIso3166CodesByLocationIdViaProperties.class); |
148 | if (toBind.containsKey(PROPERTY_API)) |
149 | bind(String.class).annotatedWith(Api.class).toInstance(toBind.getProperty(PROPERTY_API)); |
150 | if (toBind.containsKey(PROPERTY_API_VERSION)) |
151 | bind(String.class).annotatedWith(ApiVersion.class).toInstance(toBind.getProperty(PROPERTY_API_VERSION)); |
152 | if (toBind.containsKey(PROPERTY_IDENTITY)) |
153 | bind(String.class).annotatedWith(Identity.class).toInstance( |
154 | checkNotNull(toBind.getProperty(PROPERTY_IDENTITY), PROPERTY_IDENTITY)); |
155 | if (toBind.containsKey(PROPERTY_CREDENTIAL)) |
156 | bind(String.class).annotatedWith(Credential.class).toInstance(toBind.getProperty(PROPERTY_CREDENTIAL)); |
157 | } |
158 | } |
159 | |
160 | protected Properties properties; |
161 | protected List<Module> modules = new ArrayList<Module>(3); |
162 | protected Class<A> asyncClientType; |
163 | protected Class<S> syncClientType; |
164 | |
165 | @Inject |
166 | public RestContextBuilder(Class<S> syncClientClass, Class<A> asyncClientClass, Properties properties) { |
167 | this.asyncClientType = checkNotNull(asyncClientClass, "asyncClientType"); |
168 | this.syncClientType = checkNotNull(syncClientClass, "syncClientType"); |
169 | this.properties = checkNotNull(properties, "properties"); |
170 | } |
171 | |
172 | public RestContextBuilder<S, A> withModules(Iterable<Module> modules) { |
173 | addAll(this.modules, modules); |
174 | return this; |
175 | } |
176 | |
177 | public Injector buildInjector() { |
178 | addContextModule(modules); |
179 | addClientModuleIfNotPresent(modules); |
180 | addLoggingModuleIfNotPresent(modules); |
181 | addHttpModuleIfNeededAndNotPresent(modules); |
182 | ifHttpConfigureRestOtherwiseGuiceClientFactory(modules); |
183 | addExecutorServiceIfNotPresent(modules); |
184 | addCredentialStoreIfNotPresent(modules); |
185 | modules.add(new BindPropertiesAndPrincipalContext(properties)); |
186 | return Guice.createInjector(modules); |
187 | } |
188 | |
189 | @VisibleForTesting |
190 | protected void addLoggingModuleIfNotPresent(List<Module> modules) { |
191 | if (!any(modules, instanceOf(LoggingModule.class))) |
192 | modules.add(new JDKLoggingModule()); |
193 | } |
194 | |
195 | @VisibleForTesting |
196 | protected void addHttpModuleIfNeededAndNotPresent(List<Module> modules) { |
197 | if (defaultOrAtLeastOneModuleRequiresHttp(modules) && nothingConfiguresAnHttpService(modules)) |
198 | modules.add(new JavaUrlHttpCommandExecutorServiceModule()); |
199 | } |
200 | |
201 | private boolean nothingConfiguresAnHttpService(List<Module> modules) { |
202 | return (!any(modules, new Predicate<Module>() { |
203 | public boolean apply(Module input) { |
204 | return input.getClass().isAnnotationPresent(ConfiguresHttpCommandExecutorService.class); |
205 | } |
206 | |
207 | })); |
208 | } |
209 | |
210 | @VisibleForTesting |
211 | protected void addContextModuleIfNotPresent(List<Module> modules) { |
212 | if (!any(modules, new Predicate<Module>() { |
213 | public boolean apply(Module input) { |
214 | return input.getClass().isAnnotationPresent(ConfiguresRestContext.class); |
215 | } |
216 | |
217 | })) { |
218 | addContextModule(modules); |
219 | } |
220 | } |
221 | |
222 | @VisibleForTesting |
223 | protected void addContextModule(List<Module> modules) { |
224 | modules.add(new AbstractModule() { |
225 | |
226 | @SuppressWarnings( { "unchecked", "rawtypes" }) |
227 | @Override |
228 | protected void configure() { |
229 | bind( |
230 | (TypeLiteral) TypeLiteral.get(newParameterizedType(RestContext.class, syncClientType, |
231 | asyncClientType))).to( |
232 | TypeLiteral.get(newParameterizedType(RestContextImpl.class, syncClientType, asyncClientType))).in( |
233 | SINGLETON); |
234 | |
235 | } |
236 | |
237 | public String toString() { |
238 | return String.format("configure rest context %s->%s", syncClientType.getSimpleName(), asyncClientType |
239 | .getSimpleName()); |
240 | } |
241 | |
242 | }); |
243 | } |
244 | |
245 | @VisibleForTesting |
246 | protected void ifHttpConfigureRestOtherwiseGuiceClientFactory(List<Module> modules) { |
247 | if (defaultOrAtLeastOneModuleRequiresHttp(modules)) { |
248 | modules.add(new RestModule()); |
249 | } |
250 | } |
251 | |
252 | private boolean defaultOrAtLeastOneModuleRequiresHttp(List<Module> modules) { |
253 | return atLeastOneModuleRequiresHttp(modules) || !restClientModulePresent(modules); |
254 | } |
255 | |
256 | private boolean atLeastOneModuleRequiresHttp(List<Module> modules) { |
257 | return any(modules, new Predicate<Module>() { |
258 | public boolean apply(Module input) { |
259 | return input.getClass().isAnnotationPresent(RequiresHttp.class); |
260 | } |
261 | }); |
262 | } |
263 | |
264 | @VisibleForTesting |
265 | protected void addClientModuleIfNotPresent(List<Module> modules) { |
266 | if (!restClientModulePresent(modules)) { |
267 | addClientModule(modules); |
268 | } |
269 | } |
270 | |
271 | private boolean restClientModulePresent(List<Module> modules) { |
272 | return any(modules, new Predicate<Module>() { |
273 | public boolean apply(Module input) { |
274 | return input.getClass().isAnnotationPresent(ConfiguresRestClient.class); |
275 | } |
276 | |
277 | }); |
278 | } |
279 | |
280 | protected void addClientModule(List<Module> modules) { |
281 | modules.add(new RestClientModule<S, A>(syncClientType, asyncClientType)); |
282 | } |
283 | |
284 | @VisibleForTesting |
285 | protected void addExecutorServiceIfNotPresent(List<Module> modules) { |
286 | if (!any(modules, new Predicate<Module>() { |
287 | public boolean apply(Module input) { |
288 | return input.getClass().isAnnotationPresent(ConfiguresExecutorService.class); |
289 | } |
290 | } |
291 | |
292 | )) { |
293 | if (any(modules, new Predicate<Module>() { |
294 | public boolean apply(Module input) { |
295 | return input.getClass().isAnnotationPresent(SingleThreaded.class); |
296 | } |
297 | })) { |
298 | modules.add(new ExecutorServiceModule(MoreExecutors.sameThreadExecutor(), MoreExecutors |
299 | .sameThreadExecutor())); |
300 | } else { |
301 | modules.add(new ExecutorServiceModule()); |
302 | } |
303 | } |
304 | } |
305 | |
306 | @VisibleForTesting |
307 | protected void addCredentialStoreIfNotPresent(List<Module> modules) { |
308 | if (!any(modules, new Predicate<Module>() { |
309 | public boolean apply(Module input) { |
310 | return input.getClass().isAnnotationPresent(ConfiguresCredentialStore.class); |
311 | } |
312 | } |
313 | |
314 | )) { |
315 | modules.add(new CredentialStoreModule()); |
316 | } |
317 | } |
318 | |
319 | @VisibleForTesting |
320 | public Properties getProperties() { |
321 | return properties; |
322 | } |
323 | |
324 | @SuppressWarnings("unchecked") |
325 | public <T extends RestContext<S, A>> T buildContext() { |
326 | Injector injector = buildInjector(); |
327 | return (T) injector |
328 | .getInstance(Key.get(newParameterizedType(RestContext.class, syncClientType, asyncClientType))); |
329 | } |
330 | } |