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