View Javadoc

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 }