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; |
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 | |
40 | import java.net.URI; |
41 | import java.util.ArrayList; |
42 | import java.util.List; |
43 | import java.util.Map; |
44 | import java.util.Properties; |
45 | import java.util.Set; |
46 | import java.util.Map.Entry; |
47 | |
48 | import javax.inject.Inject; |
49 | import javax.inject.Named; |
50 | import javax.inject.Singleton; |
51 | |
52 | import org.jclouds.concurrent.MoreExecutors; |
53 | import org.jclouds.concurrent.SingleThreaded; |
54 | import org.jclouds.concurrent.config.ConfiguresExecutorService; |
55 | import org.jclouds.concurrent.config.ExecutorServiceModule; |
56 | import org.jclouds.http.RequiresHttp; |
57 | import org.jclouds.http.config.ConfiguresHttpCommandExecutorService; |
58 | import org.jclouds.http.config.JavaUrlHttpCommandExecutorServiceModule; |
59 | import org.jclouds.location.Iso3166; |
60 | import org.jclouds.location.Provider; |
61 | import org.jclouds.location.config.ProvideIso3166CodesByLocationIdViaProperties; |
62 | import org.jclouds.logging.config.LoggingModule; |
63 | import org.jclouds.logging.jdk.config.JDKLoggingModule; |
64 | import org.jclouds.rest.annotations.Api; |
65 | import org.jclouds.rest.annotations.ApiVersion; |
66 | import org.jclouds.rest.annotations.Credential; |
67 | import org.jclouds.rest.annotations.Identity; |
68 | import org.jclouds.rest.config.CredentialStoreModule; |
69 | import org.jclouds.rest.config.RestClientModule; |
70 | import org.jclouds.rest.config.RestModule; |
71 | import org.jclouds.rest.internal.RestContextImpl; |
72 | |
73 | import com.google.common.annotations.VisibleForTesting; |
74 | import com.google.common.base.Predicate; |
75 | import com.google.common.collect.ImmutableMultimap; |
76 | import com.google.common.collect.ImmutableSet; |
77 | import com.google.common.collect.LinkedHashMultimap; |
78 | import com.google.common.collect.Multimap; |
79 | import com.google.inject.AbstractModule; |
80 | import com.google.inject.Guice; |
81 | import com.google.inject.Injector; |
82 | import com.google.inject.Key; |
83 | import com.google.inject.Module; |
84 | import com.google.inject.Provides; |
85 | import com.google.inject.TypeLiteral; |
86 | |
87 | /** |
88 | * Creates {@link RestContext} or {@link Injector} instances based on the most commonly requested |
89 | * arguments. |
90 | * <p/> |
91 | * Note that Threadsafe objects will be bound as singletons to the Injector or Context provided. |
92 | * <p/> |
93 | * <p/> |
94 | * If no <code>Module</code>s are specified, the default {@link JDKLoggingModule logging} and |
95 | * {@link JavaUrlHttpCommandExecutorServiceModule http transports} will be installed. |
96 | * |
97 | * @author Adrian Cole, Andrew Newdigate |
98 | * @see RestContext |
99 | */ |
100 | public class RestContextBuilder<S, A> { |
101 | |
102 | static class BindPropertiesAndPrincipalContext extends AbstractModule { |
103 | protected Properties properties; |
104 | |
105 | protected BindPropertiesAndPrincipalContext(Properties properties) { |
106 | this.properties = checkNotNull(properties, "properties"); |
107 | } |
108 | |
109 | @Provides |
110 | @Singleton |
111 | @Named("CONSTANTS") |
112 | protected Multimap<String, String> constants() { |
113 | ImmutableMultimap.Builder<String, String> builder = ImmutableMultimap.<String, String> builder(); |
114 | for (Entry<Object, Object> entry : properties.entrySet()) |
115 | if (entry.getValue() != null) |
116 | builder.put(entry.getKey().toString(), entry.getValue().toString()); |
117 | return LinkedHashMultimap.create(builder.build()); |
118 | } |
119 | |
120 | @Override |
121 | protected void configure() { |
122 | Properties toBind = new Properties(); |
123 | toBind.putAll(checkNotNull(properties, "properties")); |
124 | toBind.putAll(System.getProperties()); |
125 | bindProperties(binder(), toBind); |
126 | bind(String.class).annotatedWith(Provider.class).toInstance( |
127 | checkNotNull(toBind.getProperty(PROPERTY_PROVIDER), PROPERTY_PROVIDER)); |
128 | bind(URI.class).annotatedWith(Provider.class).toInstance( |
129 | URI.create(checkNotNull(toBind.getProperty(PROPERTY_ENDPOINT), PROPERTY_ENDPOINT))); |
130 | bind(new TypeLiteral<Set<String>>() { |
131 | }).annotatedWith(Iso3166.class).toInstance( |
132 | ImmutableSet.copyOf(filter(on(',').split( |
133 | checkNotNull(toBind.getProperty(PROPERTY_ISO3166_CODES), PROPERTY_ISO3166_CODES)), |
134 | not(equalTo(""))))); |
135 | bind(new TypeLiteral<Map<String, Set<String>>>() { |
136 | }).annotatedWith(Iso3166.class).toProvider(ProvideIso3166CodesByLocationIdViaProperties.class); |
137 | if (toBind.containsKey(PROPERTY_API)) |
138 | bind(String.class).annotatedWith(Api.class).toInstance(toBind.getProperty(PROPERTY_API)); |
139 | if (toBind.containsKey(PROPERTY_API_VERSION)) |
140 | bind(String.class).annotatedWith(ApiVersion.class).toInstance(toBind.getProperty(PROPERTY_API_VERSION)); |
141 | if (toBind.containsKey(PROPERTY_IDENTITY)) |
142 | bind(String.class).annotatedWith(Identity.class).toInstance( |
143 | checkNotNull(toBind.getProperty(PROPERTY_IDENTITY), PROPERTY_IDENTITY)); |
144 | if (toBind.containsKey(PROPERTY_CREDENTIAL)) |
145 | bind(String.class).annotatedWith(Credential.class).toInstance(toBind.getProperty(PROPERTY_CREDENTIAL)); |
146 | } |
147 | } |
148 | |
149 | protected Properties properties; |
150 | protected List<Module> modules = new ArrayList<Module>(3); |
151 | protected Class<A> asyncClientType; |
152 | protected Class<S> syncClientType; |
153 | |
154 | @Inject |
155 | public RestContextBuilder(Class<S> syncClientClass, Class<A> asyncClientClass, Properties properties) { |
156 | this.asyncClientType = checkNotNull(asyncClientClass, "asyncClientType"); |
157 | this.syncClientType = checkNotNull(syncClientClass, "syncClientType"); |
158 | this.properties = checkNotNull(properties, "properties"); |
159 | } |
160 | |
161 | public RestContextBuilder<S, A> withModules(Iterable<Module> modules) { |
162 | addAll(this.modules, modules); |
163 | return this; |
164 | } |
165 | |
166 | public Injector buildInjector() { |
167 | addContextModule(modules); |
168 | addClientModuleIfNotPresent(modules); |
169 | addLoggingModuleIfNotPresent(modules); |
170 | addHttpModuleIfNeededAndNotPresent(modules); |
171 | ifHttpConfigureRestOtherwiseGuiceClientFactory(modules); |
172 | addExecutorServiceIfNotPresent(modules); |
173 | addCredentialStoreIfNotPresent(modules); |
174 | modules.add(new BindPropertiesAndPrincipalContext(properties)); |
175 | return Guice.createInjector(modules); |
176 | } |
177 | |
178 | @VisibleForTesting |
179 | protected void addLoggingModuleIfNotPresent(List<Module> modules) { |
180 | if (!any(modules, instanceOf(LoggingModule.class))) |
181 | modules.add(new JDKLoggingModule()); |
182 | } |
183 | |
184 | @VisibleForTesting |
185 | protected void addHttpModuleIfNeededAndNotPresent(List<Module> modules) { |
186 | if (defaultOrAtLeastOneModuleRequiresHttp(modules) && nothingConfiguresAnHttpService(modules)) |
187 | modules.add(new JavaUrlHttpCommandExecutorServiceModule()); |
188 | } |
189 | |
190 | private boolean nothingConfiguresAnHttpService(List<Module> modules) { |
191 | return (!any(modules, new Predicate<Module>() { |
192 | public boolean apply(Module input) { |
193 | return input.getClass().isAnnotationPresent(ConfiguresHttpCommandExecutorService.class); |
194 | } |
195 | |
196 | })); |
197 | } |
198 | |
199 | @VisibleForTesting |
200 | protected void addContextModuleIfNotPresent(List<Module> modules) { |
201 | if (!any(modules, new Predicate<Module>() { |
202 | public boolean apply(Module input) { |
203 | return input.getClass().isAnnotationPresent(ConfiguresRestContext.class); |
204 | } |
205 | |
206 | })) { |
207 | addContextModule(modules); |
208 | } |
209 | } |
210 | |
211 | @VisibleForTesting |
212 | protected void addContextModule(List<Module> modules) { |
213 | modules.add(new AbstractModule() { |
214 | |
215 | @SuppressWarnings( { "unchecked", "rawtypes" }) |
216 | @Override |
217 | protected void configure() { |
218 | bind( |
219 | (TypeLiteral) TypeLiteral.get(newParameterizedType(RestContext.class, syncClientType, |
220 | asyncClientType))).to( |
221 | TypeLiteral.get(newParameterizedType(RestContextImpl.class, syncClientType, asyncClientType))).in( |
222 | SINGLETON); |
223 | |
224 | } |
225 | |
226 | public String toString() { |
227 | return String.format("configure rest context %s->%s", syncClientType.getSimpleName(), asyncClientType |
228 | .getSimpleName()); |
229 | } |
230 | |
231 | }); |
232 | } |
233 | |
234 | @VisibleForTesting |
235 | protected void ifHttpConfigureRestOtherwiseGuiceClientFactory(List<Module> modules) { |
236 | if (defaultOrAtLeastOneModuleRequiresHttp(modules)) { |
237 | modules.add(new RestModule()); |
238 | } |
239 | } |
240 | |
241 | private boolean defaultOrAtLeastOneModuleRequiresHttp(List<Module> modules) { |
242 | return atLeastOneModuleRequiresHttp(modules) || !restClientModulePresent(modules); |
243 | } |
244 | |
245 | private boolean atLeastOneModuleRequiresHttp(List<Module> modules) { |
246 | return any(modules, new Predicate<Module>() { |
247 | public boolean apply(Module input) { |
248 | return input.getClass().isAnnotationPresent(RequiresHttp.class); |
249 | } |
250 | }); |
251 | } |
252 | |
253 | @VisibleForTesting |
254 | protected void addClientModuleIfNotPresent(List<Module> modules) { |
255 | if (!restClientModulePresent(modules)) { |
256 | addClientModule(modules); |
257 | } |
258 | } |
259 | |
260 | private boolean restClientModulePresent(List<Module> modules) { |
261 | return any(modules, new Predicate<Module>() { |
262 | public boolean apply(Module input) { |
263 | return input.getClass().isAnnotationPresent(ConfiguresRestClient.class); |
264 | } |
265 | |
266 | }); |
267 | } |
268 | |
269 | protected void addClientModule(List<Module> modules) { |
270 | modules.add(new RestClientModule<S, A>(syncClientType, asyncClientType)); |
271 | } |
272 | |
273 | @VisibleForTesting |
274 | protected void addExecutorServiceIfNotPresent(List<Module> modules) { |
275 | if (!any(modules, new Predicate<Module>() { |
276 | public boolean apply(Module input) { |
277 | return input.getClass().isAnnotationPresent(ConfiguresExecutorService.class); |
278 | } |
279 | } |
280 | |
281 | )) { |
282 | if (any(modules, new Predicate<Module>() { |
283 | public boolean apply(Module input) { |
284 | return input.getClass().isAnnotationPresent(SingleThreaded.class); |
285 | } |
286 | })) { |
287 | modules.add(new ExecutorServiceModule(MoreExecutors.sameThreadExecutor(), MoreExecutors |
288 | .sameThreadExecutor())); |
289 | } else { |
290 | modules.add(new ExecutorServiceModule()); |
291 | } |
292 | } |
293 | } |
294 | |
295 | @VisibleForTesting |
296 | protected void addCredentialStoreIfNotPresent(List<Module> modules) { |
297 | if (!any(modules, new Predicate<Module>() { |
298 | public boolean apply(Module input) { |
299 | return input.getClass().isAnnotationPresent(ConfiguresCredentialStore.class); |
300 | } |
301 | } |
302 | |
303 | )) { |
304 | modules.add(new CredentialStoreModule()); |
305 | } |
306 | } |
307 | |
308 | @VisibleForTesting |
309 | public Properties getProperties() { |
310 | return properties; |
311 | } |
312 | |
313 | @SuppressWarnings("unchecked") |
314 | public <T extends RestContext<S, A>> T buildContext() { |
315 | Injector injector = buildInjector(); |
316 | return (T) injector |
317 | .getInstance(Key.get(newParameterizedType(RestContext.class, syncClientType, asyncClientType))); |
318 | } |
319 | } |