View Javadoc

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 }