| 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 | } |