diff --git a/chef/core/src/main/java/org/jclouds/chef/ChefContextFactory.java b/chef/core/src/main/java/org/jclouds/chef/ChefContextFactory.java new file mode 100644 index 0000000000..a9afea5d2d --- /dev/null +++ b/chef/core/src/main/java/org/jclouds/chef/ChefContextFactory.java @@ -0,0 +1,124 @@ +package org.jclouds.chef; + +import static org.jclouds.rest.RestContextFactory.createContextBuilder; +import static org.jclouds.util.Utils.propagateAuthorizationOrOriginalException; + +import java.util.Properties; + +import javax.annotation.Nullable; + +import org.jclouds.rest.RestContextBuilder; +import org.jclouds.rest.RestContextFactory; +import org.jclouds.rest.RestContextFactory.ContextSpec; + +import com.google.inject.Module; + +/** + * Helper class to instantiate {@code ChefContext} instances. + * + * @author Adrian Cole + */ +public class ChefContextFactory { + + private final RestContextFactory contextFactory; + + /** + * Initializes with the default properties built-in to jclouds. This is + * typically stored in the classpath resource {@code rest.properties} + * + * @see RestContextFactory#getPropertiesFromResource + */ + public ChefContextFactory() { + this(new RestContextFactory()); + } + + /** + * Finds definitions in the specified properties. + */ + public ChefContextFactory(Properties properties) { + this(new RestContextFactory(properties)); + } + + /** + * + * Uses the supplied RestContextFactory to create {@link ChefContext}s + */ + public ChefContextFactory(RestContextFactory restContextFactory) { + this.contextFactory = restContextFactory; + } + + public static ChefContext buildContextUnwrappingExceptions(RestContextBuilder builder) { + try { + return (ChefContext) builder.buildContext(); + } catch (Exception e) { + return propagateAuthorizationOrOriginalException(e); + } + } + + /** + * @see RestContextFactory#createContextBuilder(String, String) + */ + public ChefContext createContext(String identity, String credential) { + RestContextBuilder builder = RestContextBuilder.class.cast(contextFactory.createContextBuilder("chef", + identity, credential)); + return buildContextUnwrappingExceptions(builder); + } + + /** + * @see RestContextFactory#createContextBuilder(Properties) + */ + public ChefContext createContext(Properties overrides) { + RestContextBuilder builder = RestContextBuilder.class.cast(contextFactory.createContextBuilder("chef", + overrides)); + return buildContextUnwrappingExceptions(builder); + } + + /** + * @see RestContextFactory#createContextBuilder(Iterable) + */ + public ChefContext createContext(Iterable modules, Properties overrides) { + RestContextBuilder builder = RestContextBuilder.class.cast(contextFactory.createContextBuilder("chef", + modules, overrides)); + return buildContextUnwrappingExceptions(builder); + + } + + /** + * @see RestContextFactory#createContextBuilder(String,String, Iterable) + */ + public ChefContext createContext(@Nullable String identity, @Nullable String credential, + Iterable modules) { + RestContextBuilder builder = RestContextBuilder.class.cast(contextFactory.createContextBuilder("chef", + identity, credential, modules)); + return buildContextUnwrappingExceptions(builder); + } + + /** + * @see RestContextFactory#createContextBuilder(String,String, Iterable, + * Properties) + */ + public ChefContext createContext(@Nullable String identity, @Nullable String credential, + Iterable modules, Properties overrides) { + RestContextBuilder builder = RestContextBuilder.class.cast(contextFactory.createContextBuilder("chef", + identity, credential, modules, overrides)); + return buildContextUnwrappingExceptions(builder); + } + + /** + * @see RestContextFactory#createContextBuilder(ContextSpec) + */ + public ChefContext createContext(ContextSpec contextSpec) { + RestContextBuilder builder = RestContextBuilder.class.cast(createContextBuilder(contextSpec)); + return buildContextUnwrappingExceptions(builder); + + } + + /** + * @see RestContextFactory#createContextBuilder(ContextSpec, Properties) + */ + public ChefContext createContext(ContextSpec contextSpec, Properties overrides) { + RestContextBuilder builder = RestContextBuilder.class.cast(createContextBuilder(contextSpec, overrides)); + return buildContextUnwrappingExceptions(builder); + } + +} \ No newline at end of file diff --git a/chef/core/src/main/java/org/jclouds/chef/ChefService.java b/chef/core/src/main/java/org/jclouds/chef/ChefService.java index e0eba8553e..03b6489679 100644 --- a/chef/core/src/main/java/org/jclouds/chef/ChefService.java +++ b/chef/core/src/main/java/org/jclouds/chef/ChefService.java @@ -1,9 +1,13 @@ package org.jclouds.chef; +import java.io.IOException; +import java.io.InputStream; + import org.jclouds.chef.domain.Node; import org.jclouds.chef.internal.BaseChefService; import com.google.common.base.Predicate; +import com.google.common.io.InputSupplier; import com.google.inject.ImplementedBy; /** @@ -18,6 +22,10 @@ public interface ChefService { */ ChefContext getContext(); + byte[] encrypt(InputSupplier supplier) throws IOException; + + byte[] decrypt(InputSupplier supplier) throws IOException; + void cleanupStaleNodesAndClients(String prefix, int secondsStale); void createNodeAndPopulateAutomaticAttributes(String nodeName, Iterable runList); diff --git a/chef/core/src/main/java/org/jclouds/chef/config/BaseChefRestClientModule.java b/chef/core/src/main/java/org/jclouds/chef/config/BaseChefRestClientModule.java index 49d932c8d3..901f10f4fa 100644 --- a/chef/core/src/main/java/org/jclouds/chef/config/BaseChefRestClientModule.java +++ b/chef/core/src/main/java/org/jclouds/chef/config/BaseChefRestClientModule.java @@ -64,7 +64,7 @@ public class BaseChefRestClientModule extends RestClientModule { } protected BaseChefRestClientModule(Class syncClientType, Class asyncClientType, - Map, Class> delegates) { + Map, Class> delegates) { super(syncClientType, asyncClientType, delegates); } @@ -90,10 +90,10 @@ public class BaseChefRestClientModule extends RestClientModule { @Provides @Singleton public PrivateKey provideKey(Crypto crypto, @Named(PROPERTY_CREDENTIAL) String pem) throws InvalidKeySpecException, - IOException { + IOException { return crypto.rsaKeyFactory().generatePrivate(Pems.privateKeySpec(InputSuppliers.of(pem))); } - + @Override protected void bindErrorHandlers() { bind(HttpErrorHandler.class).annotatedWith(Redirection.class).to(ChefErrorHandler.class); diff --git a/chef/core/src/main/java/org/jclouds/chef/config/ChefRestClientModule.java b/chef/core/src/main/java/org/jclouds/chef/config/ChefRestClientModule.java index f06a12b68c..18e851b99e 100644 --- a/chef/core/src/main/java/org/jclouds/chef/config/ChefRestClientModule.java +++ b/chef/core/src/main/java/org/jclouds/chef/config/ChefRestClientModule.java @@ -41,11 +41,23 @@ */ package org.jclouds.chef.config; +import static org.jclouds.Constants.PROPERTY_IDENTITY; + +import java.io.IOException; +import java.security.cert.X509Certificate; +import java.security.spec.InvalidKeySpecException; + +import javax.inject.Named; +import javax.inject.Singleton; + import org.jclouds.chef.ChefAsyncClient; import org.jclouds.chef.ChefClient; +import org.jclouds.crypto.Crypto; import org.jclouds.http.RequiresHttp; import org.jclouds.rest.ConfiguresRestClient; +import com.google.inject.Provides; + /** * Configures the Chef connection. * diff --git a/chef/core/src/main/java/org/jclouds/chef/domain/CookbookVersion.java b/chef/core/src/main/java/org/jclouds/chef/domain/CookbookVersion.java index 6b860adb5c..65b85fef36 100644 --- a/chef/core/src/main/java/org/jclouds/chef/domain/CookbookVersion.java +++ b/chef/core/src/main/java/org/jclouds/chef/domain/CookbookVersion.java @@ -105,7 +105,7 @@ public class CookbookVersion { return metadata; } - public Set getProviders() { + public Set getSuppliers() { return providers; } diff --git a/chef/core/src/main/java/org/jclouds/chef/internal/BaseChefService.java b/chef/core/src/main/java/org/jclouds/chef/internal/BaseChefService.java index d698e292a2..6de2cd9d15 100644 --- a/chef/core/src/main/java/org/jclouds/chef/internal/BaseChefService.java +++ b/chef/core/src/main/java/org/jclouds/chef/internal/BaseChefService.java @@ -2,9 +2,14 @@ package org.jclouds.chef.internal; import static com.google.common.base.Preconditions.checkNotNull; +import java.io.IOException; +import java.io.InputStream; +import java.security.PrivateKey; + import javax.annotation.Resource; import javax.inject.Inject; import javax.inject.Named; +import javax.inject.Provider; import javax.inject.Singleton; import org.jclouds.chef.ChefContext; @@ -16,9 +21,14 @@ import org.jclouds.chef.strategy.CreateNodeAndPopulateAutomaticAttributes; import org.jclouds.chef.strategy.DeleteAllNodesInList; import org.jclouds.chef.strategy.GetNodes; import org.jclouds.chef.strategy.UpdateAutomaticAttributesOnNode; +import org.jclouds.io.Payloads; +import org.jclouds.io.payloads.RSADecryptingPayload; +import org.jclouds.io.payloads.RSAEncryptingPayload; import org.jclouds.logging.Logger; import com.google.common.base.Predicate; +import com.google.common.io.ByteStreams; +import com.google.common.io.InputSupplier; /** * @@ -37,12 +47,13 @@ public class BaseChefService implements ChefService { private final DeleteAllNodesInList deleteAllNodesInList; private final GetNodes getNodes; private final UpdateAutomaticAttributesOnNode updateAutomaticAttributesOnNode; + private final Provider privateKey; @Inject protected BaseChefService(ChefContext chefContext, CleanupStaleNodesAndClients cleanupStaleNodesAndClients, CreateNodeAndPopulateAutomaticAttributes createNodeAndPopulateAutomaticAttributes, DeleteAllNodesInList deleteAllNodesInList, GetNodes getNodes, - UpdateAutomaticAttributesOnNode updateAutomaticAttributesOnNode) { + UpdateAutomaticAttributesOnNode updateAutomaticAttributesOnNode, Provider privateKey) { this.chefContext = checkNotNull(chefContext, "chefContext"); this.cleanupStaleNodesAndClients = checkNotNull(cleanupStaleNodesAndClients, "cleanupStaleNodesAndClients"); this.createNodeAndPopulateAutomaticAttributes = checkNotNull(createNodeAndPopulateAutomaticAttributes, @@ -51,6 +62,7 @@ public class BaseChefService implements ChefService { this.getNodes = checkNotNull(getNodes, "getNodes"); this.updateAutomaticAttributesOnNode = checkNotNull(updateAutomaticAttributesOnNode, "updateAutomaticAttributesOnNode"); + this.privateKey = checkNotNull(privateKey, "privateKey"); } @Override @@ -93,4 +105,16 @@ public class BaseChefService implements ChefService { return chefContext; } + @Override + public byte[] decrypt(InputSupplier supplier) throws IOException { + return ByteStreams.toByteArray(new RSADecryptingPayload(Payloads.newPayload(supplier.getInput()), privateKey + .get())); + } + + @Override + public byte[] encrypt(InputSupplier supplier) throws IOException { + return ByteStreams.toByteArray(new RSAEncryptingPayload(Payloads.newPayload(supplier.getInput()), privateKey + .get())); + } + } \ No newline at end of file diff --git a/chef/core/src/main/java/org/jclouds/chef/strategy/internal/CreateNodeAndPopulateAutomaticAttributesImpl.java b/chef/core/src/main/java/org/jclouds/chef/strategy/internal/CreateNodeAndPopulateAutomaticAttributesImpl.java index 28bf75b179..e815689c22 100644 --- a/chef/core/src/main/java/org/jclouds/chef/strategy/internal/CreateNodeAndPopulateAutomaticAttributesImpl.java +++ b/chef/core/src/main/java/org/jclouds/chef/strategy/internal/CreateNodeAndPopulateAutomaticAttributesImpl.java @@ -33,6 +33,7 @@ import org.jclouds.chef.reference.ChefConstants; import org.jclouds.chef.strategy.CreateNodeAndPopulateAutomaticAttributes; import org.jclouds.domain.JsonBall; import org.jclouds.logging.Logger; +import org.jclouds.ohai.Automatic; import com.google.common.base.Supplier; @@ -54,7 +55,7 @@ public class CreateNodeAndPopulateAutomaticAttributesImpl implements CreateNodeA @Inject public CreateNodeAndPopulateAutomaticAttributesImpl(ChefClient chef, - @Named("automatic") Supplier> automaticSupplier) { + @Automatic Supplier> automaticSupplier) { this.chef = checkNotNull(chef, "chef"); this.automaticSupplier = checkNotNull(automaticSupplier, "automaticSupplier"); } diff --git a/chef/core/src/main/java/org/jclouds/chef/strategy/internal/UpdateAutomaticAttributesOnNodeImpl.java b/chef/core/src/main/java/org/jclouds/chef/strategy/internal/UpdateAutomaticAttributesOnNodeImpl.java index fc50273994..1c59e878b4 100644 --- a/chef/core/src/main/java/org/jclouds/chef/strategy/internal/UpdateAutomaticAttributesOnNodeImpl.java +++ b/chef/core/src/main/java/org/jclouds/chef/strategy/internal/UpdateAutomaticAttributesOnNodeImpl.java @@ -33,6 +33,7 @@ import org.jclouds.chef.reference.ChefConstants; import org.jclouds.chef.strategy.UpdateAutomaticAttributesOnNode; import org.jclouds.domain.JsonBall; import org.jclouds.logging.Logger; +import org.jclouds.ohai.Automatic; import com.google.common.base.Supplier; @@ -54,7 +55,7 @@ public class UpdateAutomaticAttributesOnNodeImpl implements UpdateAutomaticAttri @Inject public UpdateAutomaticAttributesOnNodeImpl(ChefClient chef, - @Named("automatic") Supplier> automaticSupplier) { + @Automatic Supplier> automaticSupplier) { this.chef = checkNotNull(chef, "chef"); this.automaticSupplier = checkNotNull(automaticSupplier, "automaticSupplier"); } diff --git a/chef/core/src/main/java/org/jclouds/ohai/config/BaseOhaiJVMModule.java b/chef/core/src/main/java/org/jclouds/ohai/Automatic.java similarity index 64% rename from chef/core/src/main/java/org/jclouds/ohai/config/BaseOhaiJVMModule.java rename to chef/core/src/main/java/org/jclouds/ohai/Automatic.java index afb1481ba1..1b7871ff89 100644 --- a/chef/core/src/main/java/org/jclouds/ohai/config/BaseOhaiJVMModule.java +++ b/chef/core/src/main/java/org/jclouds/ohai/Automatic.java @@ -16,25 +16,24 @@ * limitations under the License. * ==================================================================== */ -package org.jclouds.ohai.config; +package org.jclouds.ohai; -import java.util.Properties; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; -import javax.inject.Named; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; -import com.google.inject.Provides; +import javax.inject.Qualifier; /** - * Wires the components needed to parse ohai data from a JVM * * @author Adrian Cole */ -public abstract class BaseOhaiJVMModule extends BaseOhaiModule { - - @Named("systemProperties") - @Provides - protected Properties systemProperties() { - return System.getProperties(); - } - -} \ No newline at end of file +@Retention(RUNTIME) +@Target( { TYPE, METHOD, PARAMETER }) +@Qualifier +public @interface Automatic { +} diff --git a/chef/core/src/main/java/org/jclouds/ohai/plugins/JMX.java b/chef/core/src/main/java/org/jclouds/ohai/AutomaticSupplier.java similarity index 57% rename from chef/core/src/main/java/org/jclouds/ohai/plugins/JMX.java rename to chef/core/src/main/java/org/jclouds/ohai/AutomaticSupplier.java index 5771fb6208..4c2eecb35a 100644 --- a/chef/core/src/main/java/org/jclouds/ohai/plugins/JMX.java +++ b/chef/core/src/main/java/org/jclouds/ohai/AutomaticSupplier.java @@ -16,47 +16,40 @@ * limitations under the License. * ==================================================================== */ -package org.jclouds.ohai.plugins; +package org.jclouds.ohai; import static com.google.common.base.Preconditions.checkNotNull; -import java.lang.management.RuntimeMXBean; import java.util.Map; -import javax.annotation.Resource; import javax.inject.Inject; -import javax.inject.Provider; import javax.inject.Singleton; import org.jclouds.domain.JsonBall; -import org.jclouds.logging.Logger; +import org.jclouds.ohai.functions.NestSlashKeys; import com.google.common.base.Supplier; -import com.google.common.collect.Maps; +import com.google.common.collect.Multimap; /** - * - * Gathers Ohai data from the JVM. * * @author Adrian Cole + * */ @Singleton -public class JMX implements Supplier> { - - @Resource - protected Logger logger = Logger.NULL; - private final Provider runtimeSupplier; +public class AutomaticSupplier implements Supplier> { + private final Multimap> autoAttrs; + private final NestSlashKeys nester; @Inject - public JMX(Provider runtimeSupplier) { - this.runtimeSupplier = checkNotNull(runtimeSupplier, "runtimeSupplier"); + AutomaticSupplier(@Automatic Multimap> autoAttrs, NestSlashKeys nester) { + this.autoAttrs = checkNotNull(autoAttrs, "autoAttrs"); + this.nester = checkNotNull(nester, "nester"); } + @Override public Map get() { - RuntimeMXBean runtime = runtimeSupplier.get(); - Map automatic = Maps.newLinkedHashMap(); - long uptimeInSeconds = runtime.getUptime() / 1000; - automatic.put("uptime_seconds", new JsonBall(uptimeInSeconds)); - return automatic; + return nester.apply(autoAttrs); } + } \ No newline at end of file diff --git a/chef/core/src/main/java/org/jclouds/ohai/OhaiContextBuilder.java b/chef/core/src/main/java/org/jclouds/ohai/OhaiContextBuilder.java index 11ee6768cf..598e9d8be1 100644 --- a/chef/core/src/main/java/org/jclouds/ohai/OhaiContextBuilder.java +++ b/chef/core/src/main/java/org/jclouds/ohai/OhaiContextBuilder.java @@ -23,7 +23,7 @@ import java.util.Properties; import javax.inject.Inject; import org.jclouds.ohai.config.ConfiguresOhai; -import org.jclouds.ohai.config.JMXOhaiJVMModule; +import org.jclouds.ohai.config.JMXOhaiModule; import org.jclouds.rest.RestContext; import org.jclouds.rest.RestContextBuilder; @@ -54,7 +54,7 @@ public class OhaiContextBuilder extends RestContextBuilder { } protected void addOhaiModule() { - modules.add(new JMXOhaiJVMModule()); + modules.add(new JMXOhaiModule()); } } \ No newline at end of file diff --git a/chef/core/src/main/java/org/jclouds/ohai/Util/OhaiUtils.java b/chef/core/src/main/java/org/jclouds/ohai/Util/OhaiUtils.java index 04192f392a..4a2d4be594 100644 --- a/chef/core/src/main/java/org/jclouds/ohai/Util/OhaiUtils.java +++ b/chef/core/src/main/java/org/jclouds/ohai/Util/OhaiUtils.java @@ -23,6 +23,12 @@ import static com.google.common.base.Preconditions.checkNotNull; import java.util.Date; import org.jclouds.domain.JsonBall; +import org.jclouds.ohai.Automatic; +import org.jclouds.ohai.config.multibindings.MapBinder; + +import com.google.common.base.Supplier; +import com.google.inject.Binder; +import com.google.inject.TypeLiteral; /** * @@ -35,11 +41,14 @@ public class OhaiUtils { return new Date(Long.parseLong(checkNotNull(ohaiDate, "ohaiDate").toString().replaceAll("\\.[0-9]*$", ""))); } - public static JsonBall toOhaiTime(long nanos) { - String now = nanos + ""; - StringBuilder nowBuilder = new StringBuilder(now).insert(now.length() - 6, '.'); - while (nowBuilder.lastIndexOf("0") != -1 && nowBuilder.lastIndexOf("0") == nowBuilder.length() - 1) - nowBuilder.deleteCharAt(nowBuilder.length() - 1); - return new JsonBall(nowBuilder.toString()); + public static JsonBall toOhaiTime(long millis) { + return new JsonBall(millis + ""); + } + + public static MapBinder> ohaiAutomaticAttributeBinder(Binder binder) { + MapBinder> mapbinder = MapBinder.newMapBinder(binder, new TypeLiteral() { + }, new TypeLiteral>() { + }, Automatic.class); + return mapbinder; } } \ No newline at end of file diff --git a/chef/core/src/main/java/org/jclouds/ohai/config/BaseOhaiModule.java b/chef/core/src/main/java/org/jclouds/ohai/config/BaseOhaiModule.java deleted file mode 100644 index 7f17341263..0000000000 --- a/chef/core/src/main/java/org/jclouds/ohai/config/BaseOhaiModule.java +++ /dev/null @@ -1,71 +0,0 @@ -/** - * - * Copyright (C) 2010 Cloud Conscious, LLC. - * - * ==================================================================== - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ==================================================================== - */ -package org.jclouds.ohai.config; - -import static org.jclouds.util.Utils.composeMapSupplier; - -import java.util.Map; - -import javax.inject.Named; -import javax.inject.Singleton; - -import org.jclouds.domain.JsonBall; -import org.jclouds.ohai.functions.ByteArrayToMacAddress; - -import com.google.common.base.Function; -import com.google.common.base.Supplier; -import com.google.inject.AbstractModule; -import com.google.inject.Injector; -import com.google.inject.Provides; -import com.google.inject.TypeLiteral; - -/** - * Wires the components needed to parse ohai data - * - * @author Adrian Cole - */ -public abstract class BaseOhaiModule extends AbstractModule { - - @Override - protected void configure() { - bind(new TypeLiteral>() { - }).to(new TypeLiteral() { - }); - } - - @Named("nanoTime") - @Provides - protected Long nanoTime() { - return System.nanoTime(); - } - - @Named("automatic") - @Provides - @Singleton - Supplier> automaticSupplier( - @Named("automatic") Iterable>> suppliers) { - return composeMapSupplier(suppliers); - } - - @Named("automatic") - @Singleton - @Provides - protected abstract Iterable>> suppliers(Injector injector); - -} \ No newline at end of file diff --git a/chef/core/src/main/java/org/jclouds/ohai/config/JMXOhaiJVMModule.java b/chef/core/src/main/java/org/jclouds/ohai/config/JMXOhaiModule.java similarity index 71% rename from chef/core/src/main/java/org/jclouds/ohai/config/JMXOhaiJVMModule.java rename to chef/core/src/main/java/org/jclouds/ohai/config/JMXOhaiModule.java index 2e7f066103..58056e5d58 100644 --- a/chef/core/src/main/java/org/jclouds/ohai/config/JMXOhaiJVMModule.java +++ b/chef/core/src/main/java/org/jclouds/ohai/config/JMXOhaiModule.java @@ -20,17 +20,14 @@ package org.jclouds.ohai.config; import java.lang.management.ManagementFactory; import java.lang.management.RuntimeMXBean; -import java.util.Map; import javax.inject.Singleton; import org.jclouds.domain.JsonBall; -import org.jclouds.ohai.plugins.JMX; -import org.jclouds.ohai.plugins.WhiteListCompliantJVM; +import org.jclouds.ohai.config.multibindings.MapBinder; +import org.jclouds.ohai.suppliers.UptimeSecondsSupplier; import com.google.common.base.Supplier; -import com.google.common.collect.ImmutableList; -import com.google.inject.Injector; import com.google.inject.Provides; /** @@ -39,7 +36,7 @@ import com.google.inject.Provides; * @author Adrian Cole */ @ConfiguresOhai -public class JMXOhaiJVMModule extends BaseOhaiJVMModule { +public class JMXOhaiModule extends OhaiModule { @Provides @Singleton @@ -47,9 +44,9 @@ public class JMXOhaiJVMModule extends BaseOhaiJVMModule { return ManagementFactory.getRuntimeMXBean(); } - @Override - protected Iterable>> suppliers(Injector injector) { - return ImmutableList.>> of(injector.getInstance(WhiteListCompliantJVM.class), - injector.getInstance(JMX.class)); + public MapBinder> bindOhai() { + MapBinder> mapBinder = super.bindOhai(); + mapBinder.addBinding("uptime_seconds").to(UptimeSecondsSupplier.class); + return mapBinder; } } \ No newline at end of file diff --git a/chef/core/src/main/java/org/jclouds/ohai/config/OhaiModule.java b/chef/core/src/main/java/org/jclouds/ohai/config/OhaiModule.java new file mode 100644 index 0000000000..b1293ba966 --- /dev/null +++ b/chef/core/src/main/java/org/jclouds/ohai/config/OhaiModule.java @@ -0,0 +1,187 @@ +/** + * + * Copyright (C) 2010 Cloud Conscious, LLC. + * + * ==================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ==================================================================== + */ +package org.jclouds.ohai.config; + +import static org.jclouds.ohai.Util.OhaiUtils.ohaiAutomaticAttributeBinder; +import static org.jclouds.ohai.Util.OhaiUtils.toOhaiTime; + +import java.util.Map; +import java.util.Properties; +import java.util.Set; + +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Provider; +import javax.inject.Singleton; + +import org.jclouds.domain.JsonBall; +import org.jclouds.json.Json; +import org.jclouds.ohai.Automatic; +import org.jclouds.ohai.AutomaticSupplier; +import org.jclouds.ohai.config.multibindings.MapBinder; +import org.jclouds.ohai.functions.ByteArrayToMacAddress; +import org.jclouds.ohai.functions.MapSetToMultimap; + +import com.google.common.base.Function; +import com.google.common.base.Supplier; +import com.google.common.collect.Multimap; +import com.google.inject.AbstractModule; +import com.google.inject.Provides; +import com.google.inject.TypeLiteral; + +/** + * Wires the components needed to parse ohai data + * + * @author Adrian Cole + */ +@ConfiguresOhai +public class OhaiModule extends AbstractModule { + + @Override + protected void configure() { + bind(new TypeLiteral>() { + }).to(new TypeLiteral() { + }); + bindOhai(); + } + + @Provides + @Automatic + protected Supplier> provideAutomatic(AutomaticSupplier in) { + return in; + } + + @Provides + @Automatic + Multimap> provideAutomatic(MapSetToMultimap> converter, + @Automatic Map>> input) { + return converter.apply(input); + + } + + @Named("systemProperties") + @Provides + protected Properties systemProperties() { + return System.getProperties(); + } + + public MapBinder> bindOhai() { + MapBinder> mapbinder = ohaiAutomaticAttributeBinder(binder()).permitDuplicates(); + mapbinder.addBinding("ohai_time").to(OhaiTimeProvider.class); + mapbinder.addBinding("jvm/system").to(SystemPropertiesProvider.class); + mapbinder.addBinding("platform").to(PlatformProvider.class); + mapbinder.addBinding("platform_version").to(PlatformVersionProvider.class); + mapbinder.addBinding("current_user").to(CurrentUserProvider.class); + return mapbinder; + } + + @Singleton + public static class OhaiTimeProvider implements Supplier { + private final Provider timeProvider; + + @Inject + OhaiTimeProvider(Provider timeProvider) { + this.timeProvider = timeProvider; + } + + @Override + public JsonBall get() { + return toOhaiTime(timeProvider.get()); + } + + } + + @Provides + protected Long millis() { + return System.currentTimeMillis(); + } + + @Singleton + public static class SystemPropertiesProvider implements Supplier { + + private final Json json; + private final Properties systemProperties; + + @Inject + SystemPropertiesProvider(Json json, @Named("systemProperties") Properties systemProperties) { + this.json = json; + this.systemProperties = systemProperties; + } + + @Override + public JsonBall get() { + return new JsonBall(json.toJson(systemProperties)); + } + + } + + @Singleton + public static class PlatformProvider extends SystemPropertyProvider { + + @Inject + PlatformProvider(@Named("systemProperties") Properties systemProperties) { + super("os.name", systemProperties); + } + + @Override + public JsonBall get() { + JsonBall returnValue = super.get(); + return returnValue != null ? new JsonBall(returnValue.toString().replaceAll("[ -]", "").toLowerCase()) : null; + } + + } + + @Singleton + public static class PlatformVersionProvider extends SystemPropertyProvider { + + @Inject + PlatformVersionProvider(@Named("systemProperties") Properties systemProperties) { + super("os.version", systemProperties); + } + + } + + @Singleton + public static class CurrentUserProvider extends SystemPropertyProvider { + + @Inject + CurrentUserProvider(@Named("systemProperties") Properties systemProperties) { + super("user.name", systemProperties); + } + + } + + public static class SystemPropertyProvider implements Supplier { + private final Properties systemProperties; + private final String property; + + @Inject + SystemPropertyProvider(String property, @Named("systemProperties") Properties systemProperties) { + this.property = property; + this.systemProperties = systemProperties; + } + + @Override + public JsonBall get() { + return systemProperties.containsKey(property) ? new JsonBall(systemProperties.getProperty(property)) : null; + } + + } + +} \ No newline at end of file diff --git a/chef/core/src/main/java/org/jclouds/ohai/config/multibindings/Element.java b/chef/core/src/main/java/org/jclouds/ohai/config/multibindings/Element.java new file mode 100644 index 0000000000..53fc892976 --- /dev/null +++ b/chef/core/src/main/java/org/jclouds/ohai/config/multibindings/Element.java @@ -0,0 +1,19 @@ +package org.jclouds.ohai.config.multibindings; + +import com.google.inject.BindingAnnotation; + +import java.lang.annotation.Retention; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * An internal binding annotation applied to each element in a multibinding. + * All elements are assigned a globally-unique id to allow different modules + * to contribute multibindings independently. + * + * @author jessewilson@google.com (Jesse Wilson) + */ +@Retention(RUNTIME) @BindingAnnotation +@interface Element { + String setName(); + int uniqueId(); +} diff --git a/chef/core/src/main/java/org/jclouds/ohai/config/multibindings/MapBinder.java b/chef/core/src/main/java/org/jclouds/ohai/config/multibindings/MapBinder.java new file mode 100644 index 0000000000..6b847b8f77 --- /dev/null +++ b/chef/core/src/main/java/org/jclouds/ohai/config/multibindings/MapBinder.java @@ -0,0 +1,518 @@ +package org.jclouds.ohai.config.multibindings; + +import static com.google.inject.util.Types.newParameterizedTypeWithOwner; +import static org.jclouds.ohai.config.multibindings.Multibinder.checkConfiguration; +import static org.jclouds.ohai.config.multibindings.Multibinder.checkNotNull; +import static org.jclouds.ohai.config.multibindings.Multibinder.setOf; + +import java.lang.annotation.Annotation; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; +import java.util.Map.Entry; + +import org.jclouds.ohai.config.multibindings.Multibinder.RealMultibinder; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.inject.Binder; +import com.google.inject.Inject; +import com.google.inject.Injector; +import com.google.inject.Key; +import com.google.inject.Module; +import com.google.inject.Provider; +import com.google.inject.TypeLiteral; +import com.google.inject.binder.LinkedBindingBuilder; +import com.google.inject.spi.Dependency; +import com.google.inject.spi.ProviderWithDependencies; +import com.google.inject.util.Types; + +/** + * An API to bind multiple map entries separately, only to later inject them as + * a complete map. MapBinder is intended for use in your application's module: + * + *
+ * 
+ * public class SnacksModule extends AbstractModule {
+ *   protected void configure() {
+ *     MapBinder<String, Snack> mapbinder
+ *         = MapBinder.newMapBinder(binder(), String.class, Snack.class);
+ *     mapbinder.addBinding("twix").toInstance(new Twix());
+ *     mapbinder.addBinding("snickers").toProvider(SnickersProvider.class);
+ *     mapbinder.addBinding("skittles").to(Skittles.class);
+ *   }
+ * }
+ * 
+ * + *

+ * With this binding, a {@link Map}{@code } can now be injected: + * + *

+ * 
+ * class SnackMachine {
+ *   {@literal @}Inject
+ *   public SnackMachine(Map<String, Snack> snacks) { ... }
+ * }
+ * 
+ * + *

+ * In addition to binding {@code Map}, a mapbinder will also bind {@code + * Map>} for lazy value provision: + * + *

+ * 
+ * class SnackMachine {
+ *   {@literal @}Inject
+ *   public SnackMachine(Map<String, Provider<Snack>> snackSuppliers) { ... }
+ * }
+ * 
+ * + *

+ * Contributing mapbindings from different modules is supported. For example, it + * is okay to have both {@code CandyModule} and {@code ChipsModule} both create + * their own {@code MapBinder}, and to each contribute bindings + * to the snacks map. When that map is injected, it will contain entries from + * both modules. + * + *

+ * The map's iteration order is consistent with the binding order. This is + * convenient when multiple elements are contributed by the same module because + * that module can order its bindings appropriately. Avoid relying on the + * iteration order of elements contributed by different modules, since there is + * no equivalent mechanism to order modules. + * + *

+ * Values are resolved at map injection time. If a value is bound to a provider, + * that provider's get method will be called each time the map is injected + * (unless the binding is also scoped, or a map of providers is injected). + * + *

+ * Annotations are used to create different maps of the same key/value type. + * Each distinct annotation gets its own independent map. + * + *

+ * Keys must be distinct. If the same key is bound more than + * once, map injection will fail. However, use {@link #permitDuplicates()} in + * order to allow duplicate keys; extra bindings to {@code Map>} and + * {@code Map>} will be added. + * + *

+ * Keys must be non-null. {@code addBinding(null)} will throw + * an unchecked exception. + * + *

+ * Values must be non-null to use map injection. If any value + * is null, map injection will fail (although injecting a map of providers will + * not). + * + * @author dpb@google.com (David P. Baker) + */ +public abstract class MapBinder { + private MapBinder() { + } + + /** + * Returns a new mapbinder that collects entries of {@code keyType}/{@code + * valueType} in a {@link Map} that is itself bound with no binding + * annotation. + */ + public static MapBinder newMapBinder(Binder binder, TypeLiteral keyType, TypeLiteral valueType) { + binder = binder.skipSources(MapBinder.class, RealMapBinder.class); + return newMapBinder(binder, valueType, Key.get(mapOf(keyType, valueType)), Key.get(mapOfProviderOf(keyType, + valueType)), Key.get(mapOf(keyType, setOf(valueType))), Key.get(mapOfSetOfProviderOf(keyType, valueType)), + Multibinder.newSetBinder(binder, entryOfProviderOf(keyType, valueType))); + } + + /** + * Returns a new mapbinder that collects entries of {@code keyType}/{@code + * valueType} in a {@link Map} that is itself bound with no binding + * annotation. + */ + public static MapBinder newMapBinder(Binder binder, Class keyType, Class valueType) { + return newMapBinder(binder, TypeLiteral.get(keyType), TypeLiteral.get(valueType)); + } + + /** + * Returns a new mapbinder that collects entries of {@code keyType}/{@code + * valueType} in a {@link Map} that is itself bound with {@code annotation}. + */ + public static MapBinder newMapBinder(Binder binder, TypeLiteral keyType, TypeLiteral valueType, + Annotation annotation) { + binder = binder.skipSources(MapBinder.class, RealMapBinder.class); + return newMapBinder(binder, valueType, Key.get(mapOf(keyType, valueType), annotation), Key.get(mapOfProviderOf( + keyType, valueType), annotation), Key.get(mapOf(keyType, setOf(valueType)), annotation), Key.get( + mapOfSetOfProviderOf(keyType, valueType), annotation), Multibinder.newSetBinder(binder, entryOfProviderOf( + keyType, valueType), annotation)); + } + + /** + * Returns a new mapbinder that collects entries of {@code keyType}/{@code + * valueType} in a {@link Map} that is itself bound with {@code annotation}. + */ + public static MapBinder newMapBinder(Binder binder, Class keyType, Class valueType, + Annotation annotation) { + return newMapBinder(binder, TypeLiteral.get(keyType), TypeLiteral.get(valueType), annotation); + } + + /** + * Returns a new mapbinder that collects entries of {@code keyType}/{@code + * valueType} in a {@link Map} that is itself bound with {@code + * annotationType}. + */ + public static MapBinder newMapBinder(Binder binder, TypeLiteral keyType, TypeLiteral valueType, + Class annotationType) { + binder = binder.skipSources(MapBinder.class, RealMapBinder.class); + return newMapBinder(binder, valueType, Key.get(mapOf(keyType, valueType), annotationType), Key.get( + mapOfProviderOf(keyType, valueType), annotationType), Key.get(mapOf(keyType, setOf(valueType)), + annotationType), Key.get(mapOfSetOfProviderOf(keyType, valueType), annotationType), Multibinder + .newSetBinder(binder, entryOfProviderOf(keyType, valueType), annotationType)); + } + + /** + * Returns a new mapbinder that collects entries of {@code keyType}/{@code + * valueType} in a {@link Map} that is itself bound with {@code + * annotationType}. + */ + public static MapBinder newMapBinder(Binder binder, Class keyType, Class valueType, + Class annotationType) { + return newMapBinder(binder, TypeLiteral.get(keyType), TypeLiteral.get(valueType), annotationType); + } + + @SuppressWarnings("unchecked") + // a map of is safely a Map + private static TypeLiteral> mapOf(TypeLiteral keyType, TypeLiteral valueType) { + return (TypeLiteral>) TypeLiteral.get(Types.mapOf(keyType.getType(), valueType.getType())); + } + + @SuppressWarnings("unchecked") + // a provider map is safely a Map> + private static TypeLiteral>> mapOfProviderOf(TypeLiteral keyType, + TypeLiteral valueType) { + return (TypeLiteral>>) TypeLiteral.get(Types.mapOf(keyType.getType(), Types + .providerOf(valueType.getType()))); + } + + @SuppressWarnings("unchecked") + // a provider map > is safely a Map>> + private static TypeLiteral>>> mapOfSetOfProviderOf(TypeLiteral keyType, + TypeLiteral valueType) { + return (TypeLiteral>>>) TypeLiteral.get(Types.mapOf(keyType.getType(), Types.setOf(Types + .providerOf(valueType.getType())))); + } + + @SuppressWarnings("unchecked") + // a provider entry is safely a Map.Entry> + private static TypeLiteral>> entryOfProviderOf(TypeLiteral keyType, + TypeLiteral valueType) { + return (TypeLiteral>>) TypeLiteral.get(newParameterizedTypeWithOwner(Map.class, Entry.class, + keyType.getType(), Types.providerOf(valueType.getType()))); + } + + private static MapBinder newMapBinder(Binder binder, TypeLiteral valueType, Key> mapKey, + Key>> providerMapKey, Key>> multimapKey, + Key>>> providerMultimapKey, Multibinder>> entrySetBinder) { + RealMapBinder mapBinder = new RealMapBinder(binder, valueType, mapKey, providerMapKey, multimapKey, + providerMultimapKey, entrySetBinder); + binder.install(mapBinder); + return mapBinder; + } + + /** + * Configures the {@code MapBinder} to handle duplicate entries. + *

+ * When multiple equal keys are bound, the value that gets included in the + * map is arbitrary. + *

+ * In addition to the {@code Map} and {@code Map>} maps + * that are normally bound, a {@code Map>} and {@code Map>>} are also bound, which contain all values bound + * to each key. + *

+ * When multiple modules contribute elements to the map, this configuration + * option impacts all of them. + * + * @return this map binder + */ + public abstract MapBinder permitDuplicates(); + + /** + * Returns a binding builder used to add a new entry in the map. Each key + * must be distinct (and non-null). Bound providers will be evaluated each + * time the map is injected. + * + *

+ * It is an error to call this method without also calling one of the {@code + * to} methods on the returned binding builder. + * + *

+ * Scoping elements independently is supported. Use the {@code in} method to + * specify a binding scope. + */ + public abstract LinkedBindingBuilder addBinding(K key); + + /** + * The actual mapbinder plays several roles: + * + *

+ * As a MapBinder, it acts as a factory for LinkedBindingBuilders for each of + * the map's values. It delegates to a {@link Multibinder} of entries (keys + * to value providers). + * + *

+ * As a Module, it installs the binding to the map itself, as well as to a + * corresponding map whose values are providers. It uses the entry set + * multibinder to construct the map and the provider map. + * + *

+ * As a module, this implements equals() and hashcode() in order to trick + * Guice into executing its configure() method only once. That makes it so + * that multiple mapbinders can be created for the same target map, but only + * one is bound. Since the list of bindings is retrieved from the injector + * itself (and not the mapbinder), each mapbinder has access to all + * contributions from all equivalent mapbinders. + * + *

+ * Rather than binding a single Map.Entry<K, V>, the map binder binds + * keys and values independently. This allows the values to be properly + * scoped. + * + *

+ * We use a subclass to hide 'implements Module' from the public API. + */ + private static final class RealMapBinder extends MapBinder implements Module { + private final TypeLiteral valueType; + private final Key> mapKey; + private final Key>> providerMapKey; + private final Key>> multimapKey; + private final Key>>> providerMultimapKey; + private final RealMultibinder>> entrySetBinder; + + /* + * the target injector's binder. non-null until initialization, null + * afterwards + */ + private Binder binder; + + private RealMapBinder(Binder binder, TypeLiteral valueType, Key> mapKey, + Key>> providerMapKey, Key>> multimapKey, + Key>>> providerMultimapKey, Multibinder>> entrySetBinder) { + this.valueType = valueType; + this.mapKey = mapKey; + this.providerMapKey = providerMapKey; + this.multimapKey = multimapKey; + this.providerMultimapKey = providerMultimapKey; + this.entrySetBinder = (RealMultibinder>>) entrySetBinder; + this.binder = binder; + } + + @Override + public MapBinder permitDuplicates() { + entrySetBinder.permitDuplicates(); + binder.install(new MultimapBinder(multimapKey, providerMultimapKey, entrySetBinder.getSetKey())); + return this; + } + + /** + * This creates two bindings. One for the {@code Map.Entry>} and another for {@code V}. + */ + @Override + public LinkedBindingBuilder addBinding(K key) { + checkNotNull(key, "key"); + checkConfiguration(!isInitialized(), "MapBinder was already initialized"); + + Key valueKey = Key.get(valueType, new RealElement(entrySetBinder.getSetName())); + entrySetBinder.addBinding().toInstance(new MapEntry>(key, binder.getProvider(valueKey))); + return binder.bind(valueKey); + } + + public void configure(Binder binder) { + checkConfiguration(!isInitialized(), "MapBinder was already initialized"); + + final ImmutableSet> dependencies = ImmutableSet.> of(Dependency.get(entrySetBinder + .getSetKey())); + + // Binds a Map> from a collection of Map>. + final Provider>>> entrySetProvider = binder.getProvider(entrySetBinder.getSetKey()); + binder.bind(providerMapKey).toProvider(new ProviderWithDependencies>>() { + private Map> providerMap; + + @SuppressWarnings("unused") + @Inject + void initialize(Injector injector) { + RealMapBinder.this.binder = null; + boolean permitDuplicates = entrySetBinder.permitsDuplicates(injector); + + Map> providerMapMutable = new LinkedHashMap>(); + for (Entry> entry : entrySetProvider.get()) { + Provider previous = providerMapMutable.put(entry.getKey(), entry.getValue()); + checkConfiguration(previous == null || permitDuplicates, + "Map injection failed due to duplicated key \"%s\"", entry.getKey()); + } + + providerMap = ImmutableMap.copyOf(providerMapMutable); + } + + public Map> get() { + return providerMap; + } + + public Set> getDependencies() { + return dependencies; + } + }); + + final Provider>> mapProvider = binder.getProvider(providerMapKey); + binder.bind(mapKey).toProvider(new ProviderWithDependencies>() { + public Map get() { + Map map = new LinkedHashMap(); + for (Entry> entry : mapProvider.get().entrySet()) { + V value = entry.getValue().get(); + K key = entry.getKey(); + checkConfiguration(value != null, "Map injection failed due to null value for key \"%s\"", key); + map.put(key, value); + } + return Collections.unmodifiableMap(map); + } + + public Set> getDependencies() { + return dependencies; + } + }); + } + + private boolean isInitialized() { + return binder == null; + } + + @Override + public boolean equals(Object o) { + return o instanceof RealMapBinder && ((RealMapBinder) o).mapKey.equals(mapKey); + } + + @Override + public int hashCode() { + return mapKey.hashCode(); + } + + /** + * Binds {@code Map>} and {{@code Map>>}. + */ + private static final class MultimapBinder implements Module { + + private final Key>> multimapKey; + private final Key>>> providerMultimapKey; + private final Key>>> entrySetKey; + + public MultimapBinder(Key>> multimapKey, Key>>> providerMultimapKey, + Key>>> entrySetKey) { + this.multimapKey = multimapKey; + this.providerMultimapKey = providerMultimapKey; + this.entrySetKey = entrySetKey; + } + + public void configure(Binder binder) { + final ImmutableSet> dependencies = ImmutableSet.> of(Dependency + .get(entrySetKey)); + + final Provider>>> entrySetProvider = binder.getProvider(entrySetKey); + // Binds a Map>> from a collection of + // Map> if + // permitDuplicates was called. + binder.bind(providerMultimapKey).toProvider(new ProviderWithDependencies>>>() { + private Map>> providerMultimap; + + @SuppressWarnings("unused") + @Inject + void initialize(Injector injector) { + Map>> providerMultimapMutable = new LinkedHashMap>>(); + for (Entry> entry : entrySetProvider.get()) { + if (!providerMultimapMutable.containsKey(entry.getKey())) { + providerMultimapMutable.put(entry.getKey(), ImmutableSet.> builder()); + } + providerMultimapMutable.get(entry.getKey()).add(entry.getValue()); + } + + ImmutableMap.Builder>> providerMultimapBuilder = ImmutableMap.builder(); + for (Entry>> entry : providerMultimapMutable.entrySet()) { + providerMultimapBuilder.put(entry.getKey(), entry.getValue().build()); + } + providerMultimap = providerMultimapBuilder.build(); + } + + public Map>> get() { + return providerMultimap; + } + + public Set> getDependencies() { + return dependencies; + } + }); + + final Provider>>> multimapProvider = binder.getProvider(providerMultimapKey); + binder.bind(multimapKey).toProvider(new ProviderWithDependencies>>() { + + public Map> get() { + ImmutableMap.Builder> multimapBuilder = ImmutableMap.builder(); + for (Entry>> entry : multimapProvider.get().entrySet()) { + K key = entry.getKey(); + ImmutableSet.Builder valuesBuilder = ImmutableSet.builder(); + for (Provider valueProvider : entry.getValue()) { + V value = valueProvider.get(); + checkConfiguration(value != null, "Multimap injection failed due to null value for key \"%s\"", + key); + valuesBuilder.add(value); + } + multimapBuilder.put(key, valuesBuilder.build()); + } + return multimapBuilder.build(); + } + + public Set> getDependencies() { + return dependencies; + } + }); + } + } + + private static final class MapEntry implements Map.Entry { + private final K key; + private final V value; + + private MapEntry(K key, V value) { + this.key = key; + this.value = value; + } + + public K getKey() { + return key; + } + + public V getValue() { + return value; + } + + public V setValue(V value) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean equals(Object obj) { + return obj instanceof Map.Entry && key.equals(((Map.Entry) obj).getKey()) + && value.equals(((Map.Entry) obj).getValue()); + } + + @Override + public int hashCode() { + return 127 * ("key".hashCode() ^ key.hashCode()) + 127 * ("value".hashCode() ^ value.hashCode()); + } + + @Override + public String toString() { + return "MapEntry(" + key + ", " + value + ")"; + } + } + } +} diff --git a/chef/core/src/main/java/org/jclouds/ohai/config/multibindings/Multibinder.java b/chef/core/src/main/java/org/jclouds/ohai/config/multibindings/Multibinder.java new file mode 100644 index 0000000000..cf14051c7a --- /dev/null +++ b/chef/core/src/main/java/org/jclouds/ohai/config/multibindings/Multibinder.java @@ -0,0 +1,409 @@ +/** + * Copyright (C) 2008 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jclouds.ohai.config.multibindings; + +import static com.google.inject.name.Names.named; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Lists; +import com.google.inject.AbstractModule; +import com.google.inject.Binder; +import com.google.inject.Binding; +import com.google.inject.ConfigurationException; +import com.google.inject.Inject; +import com.google.inject.Injector; +import com.google.inject.Key; +import com.google.inject.Module; +import com.google.inject.Provider; +import com.google.inject.TypeLiteral; +import com.google.inject.binder.LinkedBindingBuilder; +import com.google.inject.internal.Errors; +import com.google.inject.spi.Dependency; +import com.google.inject.spi.HasDependencies; +import com.google.inject.spi.Message; +import com.google.inject.spi.Toolable; +import com.google.inject.util.Types; + +/** + * + * An API to bind multiple values separately, only to later inject them as a + * complete collection. Multibinder is intended for use in your application's + * module: + * + *

+ * 
+ * public class SnacksModule extends AbstractModule {
+ *   protected void configure() {
+ *     Multibinder<Snack> multibinder
+ *         = Multibinder.newSetBinder(binder(), Snack.class);
+ *     multibinder.addBinding().toInstance(new Twix());
+ *     multibinder.addBinding().toProvider(SnickersProvider.class);
+ *     multibinder.addBinding().to(Skittles.class);
+ *   }
+ * }
+ * 
+ * + *

+ * With this binding, a {@link Set}{@code } can now be injected: + * + *

+ * 
+ * class SnackMachine {
+ *   {@literal @}Inject
+ *   public SnackMachine(Set<Snack> snacks) { ... }
+ * }
+ * 
+ * + *

+ * Contributing multibindings from different modules is supported. For example, + * it is okay to have both {@code CandyModule} and {@code ChipsModule} to both + * create their own {@code Multibinder}, and to each contribute bindings + * to the set of snacks. When that set is injected, it will contain elements + * from both modules. + * + *

+ * The set's iteration order is consistent with the binding order. This is + * convenient when multiple elements are contributed by the same module because + * that module can order its bindings appropriately. Avoid relying on the + * iteration order of elements contributed by different modules, since there is + * no equivalent mechanism to order modules. + * + *

+ * Elements are resolved at set injection time. If an element is bound to a + * provider, that provider's get method will be called each time the set is + * injected (unless the binding is also scoped). + * + *

+ * Annotations are be used to create different sets of the same element type. + * Each distinct annotation gets its own independent collection of elements. + * + *

+ * Elements must be distinct. If multiple bound elements have + * the same value, set injection will fail. + * + *

+ * Elements must be non-null. If any set element is null, set + * injection will fail. + * + * @author jessewilson@google.com (Jesse Wilson) + */ +public abstract class Multibinder { + private Multibinder() { + } + + /** + * Returns a new multibinder that collects instances of {@code type} in a + * {@link Set} that is itself bound with no binding annotation. + */ + public static Multibinder newSetBinder(Binder binder, TypeLiteral type) { + binder = binder.skipSources(RealMultibinder.class, Multibinder.class); + RealMultibinder result = new RealMultibinder(binder, type, "", Key.get(Multibinder. setOf(type))); + binder.install(result); + return result; + } + + /** + * Returns a new multibinder that collects instances of {@code type} in a + * {@link Set} that is itself bound with no binding annotation. + */ + public static Multibinder newSetBinder(Binder binder, Class type) { + return newSetBinder(binder, TypeLiteral.get(type)); + } + + /** + * Returns a new multibinder that collects instances of {@code type} in a + * {@link Set} that is itself bound with {@code annotation}. + */ + public static Multibinder newSetBinder(Binder binder, TypeLiteral type, Annotation annotation) { + binder = binder.skipSources(RealMultibinder.class, Multibinder.class); + RealMultibinder result = new RealMultibinder(binder, type, annotation.toString(), Key.get(Multibinder + . setOf(type), annotation)); + binder.install(result); + return result; + } + + /** + * Returns a new multibinder that collects instances of {@code type} in a + * {@link Set} that is itself bound with {@code annotation}. + */ + public static Multibinder newSetBinder(Binder binder, Class type, Annotation annotation) { + return newSetBinder(binder, TypeLiteral.get(type), annotation); + } + + /** + * Returns a new multibinder that collects instances of {@code type} in a + * {@link Set} that is itself bound with {@code annotationType}. + */ + public static Multibinder newSetBinder(Binder binder, TypeLiteral type, + Class annotationType) { + binder = binder.skipSources(RealMultibinder.class, Multibinder.class); + RealMultibinder result = new RealMultibinder(binder, type, "@" + annotationType.getName(), Key.get( + Multibinder. setOf(type), annotationType)); + binder.install(result); + return result; + } + + /** + * Returns a new multibinder that collects instances of {@code type} in a + * {@link Set} that is itself bound with {@code annotationType}. + */ + public static Multibinder newSetBinder(Binder binder, Class type, + Class annotationType) { + return newSetBinder(binder, TypeLiteral.get(type), annotationType); + } + + @SuppressWarnings("unchecked") + // wrapping a T in a Set safely returns a Set + static TypeLiteral> setOf(TypeLiteral elementType) { + Type type = Types.setOf(elementType.getType()); + return (TypeLiteral>) TypeLiteral.get(type); + } + + /** + * Configures the bound set to silently discard duplicate elements. When + * multiple equal values are bound, the one that gets included is arbitrary. + * When multiple modules contribute elements to the set, this configuration + * option impacts all of them. + * + * @return this multibinder + */ + public abstract Multibinder permitDuplicates(); + + /** + * Returns a binding builder used to add a new element in the set. Each bound + * element must have a distinct value. Bound providers will be evaluated each + * time the set is injected. + * + *

+ * It is an error to call this method without also calling one of the {@code + * to} methods on the returned binding builder. + * + *

+ * Scoping elements independently is supported. Use the {@code in} method to + * specify a binding scope. + */ + public abstract LinkedBindingBuilder addBinding(); + + /** + * The actual multibinder plays several roles: + * + *

+ * As a Multibinder, it acts as a factory for LinkedBindingBuilders for each + * of the set's elements. Each binding is given an annotation that identifies + * it as a part of this set. + * + *

+ * As a Module, it installs the binding to the set itself. As a module, this + * implements equals() and hashcode() in order to trick Guice into executing + * its configure() method only once. That makes it so that multiple + * multibinders can be created for the same target collection, but only one + * is bound. Since the list of bindings is retrieved from the injector itself + * (and not the multibinder), each multibinder has access to all + * contributions from all multibinders. + * + *

+ * As a Provider, this constructs the set instances. + * + *

+ * We use a subclass to hide 'implements Module, Provider' from the public + * API. + */ + static final class RealMultibinder extends Multibinder implements Module, Provider>, HasDependencies { + + private final TypeLiteral elementType; + private final String setName; + private final Key> setKey; + private final Key permitDuplicatesKey; + + /* + * the target injector's binder. non-null until initialization, null + * afterwards + */ + private Binder binder; + + /* + * a provider for each element in the set. null until initialization, + * non-null afterwards + */ + private List> providers; + private Set> dependencies; + + /** + * whether duplicates are allowed. Possibly configured by a different + * instance + */ + private boolean permitDuplicates; + + private RealMultibinder(Binder binder, TypeLiteral elementType, String setName, Key> setKey) { + this.binder = checkNotNull(binder, "binder"); + this.elementType = checkNotNull(elementType, "elementType"); + this.setName = checkNotNull(setName, "setName"); + this.setKey = checkNotNull(setKey, "setKey"); + this.permitDuplicatesKey = Key.get(Boolean.class, named(toString() + " permits duplicates")); + } + + public void configure(Binder binder) { + checkConfiguration(!isInitialized(), "Multibinder was already initialized"); + binder.bind(setKey).toProvider(this); + } + + @Override + public Multibinder permitDuplicates() { + binder.install(new PermitDuplicatesModule(permitDuplicatesKey)); + return this; + } + + @Override + public LinkedBindingBuilder addBinding() { + checkConfiguration(!isInitialized(), "Multibinder was already initialized"); + + return binder.bind(Key.get(elementType, new RealElement(setName))); + } + + /** + * Invoked by Guice at Injector-creation time to prepare providers for + * each element in this set. At this time the set's size is known, but its + * contents are only evaluated when get() is invoked. + */ + @Toolable + @Inject + void initialize(Injector injector) { + providers = Lists.newArrayList(); + List> dependencies = Lists.newArrayList(); + for (Binding entry : injector.findBindingsByType(elementType)) { + + if (keyMatches(entry.getKey())) { + @SuppressWarnings("unchecked") + // protected by findBindingsByType() + Binding binding = (Binding) entry; + providers.add(binding.getProvider()); + dependencies.add(Dependency.get(binding.getKey())); + } + } + + this.dependencies = ImmutableSet.copyOf(dependencies); + this.permitDuplicates = permitsDuplicates(injector); + this.binder = null; + } + + boolean permitsDuplicates(Injector injector) { + return injector.getBindings().containsKey(permitDuplicatesKey); + } + + private boolean keyMatches(Key key) { + return key.getTypeLiteral().equals(elementType) && key.getAnnotation() instanceof Element + && ((Element) key.getAnnotation()).setName().equals(setName); + } + + private boolean isInitialized() { + return binder == null; + } + + public Set get() { + checkConfiguration(isInitialized(), "Multibinder is not initialized"); + + Set result = new LinkedHashSet(); + for (Provider provider : providers) { + final T newValue = provider.get(); + checkConfiguration(newValue != null, "Set injection failed due to null element"); + checkConfiguration(result.add(newValue) || permitDuplicates, + "Set injection failed due to duplicated element \"%s\"", newValue); + } + return Collections.unmodifiableSet(result); + } + + String getSetName() { + return setName; + } + + Key> getSetKey() { + return setKey; + } + + public Set> getDependencies() { + return dependencies; + } + + @Override + public boolean equals(Object o) { + return o instanceof RealMultibinder && ((RealMultibinder) o).setKey.equals(setKey); + } + + @Override + public int hashCode() { + return setKey.hashCode(); + } + + @Override + public String toString() { + return new StringBuilder().append(setName).append(setName.length() > 0 ? " " : "").append("Multibinder<") + .append(elementType).append(">").toString(); + } + } + + /** + * We install the permit duplicates configuration as its own binding, all by + * itself. This way, if only one of a multibinder's users remember to call + * permitDuplicates(), they're still permitted. + */ + private static class PermitDuplicatesModule extends AbstractModule { + private final Key key; + + PermitDuplicatesModule(Key key) { + this.key = key; + } + + @Override + protected void configure() { + bind(key).toInstance(true); + } + + @Override + public boolean equals(Object o) { + return o instanceof PermitDuplicatesModule && ((PermitDuplicatesModule) o).key.equals(key); + } + + @Override + public int hashCode() { + return getClass().hashCode() ^ key.hashCode(); + } + } + + static void checkConfiguration(boolean condition, String format, Object... args) { + if (condition) { + return; + } + + throw new ConfigurationException(ImmutableSet.of(new Message(Errors.format(format, args)))); + } + + static T checkNotNull(T reference, String name) { + if (reference != null) { + return reference; + } + + NullPointerException npe = new NullPointerException(name); + throw new ConfigurationException(ImmutableSet.of(new Message(ImmutableList.of(), npe.toString(), npe))); + } +} diff --git a/chef/core/src/main/java/org/jclouds/ohai/config/multibindings/RealElement.java b/chef/core/src/main/java/org/jclouds/ohai/config/multibindings/RealElement.java new file mode 100644 index 0000000000..edcb9b2a67 --- /dev/null +++ b/chef/core/src/main/java/org/jclouds/ohai/config/multibindings/RealElement.java @@ -0,0 +1,50 @@ + + +package org.jclouds.ohai.config.multibindings; + +import java.lang.annotation.Annotation; +import java.util.concurrent.atomic.AtomicInteger; + + +/** + * @author jessewilson@google.com (Jesse Wilson) + */ +class RealElement implements Element { + private static final AtomicInteger nextUniqueId = new AtomicInteger(1); + + private final int uniqueId; + private final String setName; + + RealElement(String setName) { + uniqueId = nextUniqueId.getAndIncrement(); + this.setName = setName; + } + + public String setName() { + return setName; + } + + public int uniqueId() { + return uniqueId; + } + + public Class annotationType() { + return Element.class; + } + + @Override public String toString() { + return "@" + Element.class.getName() + "(setName=" + setName + + ",uniqueId=" + uniqueId + ")"; + } + + @Override public boolean equals(Object o) { + return o instanceof Element + && ((Element) o).setName().equals(setName()) + && ((Element) o).uniqueId() == uniqueId(); + } + + @Override public int hashCode() { + return 127 * ("setName".hashCode() ^ setName.hashCode()) + + 127 * ("uniqueId".hashCode() ^ uniqueId); + } +} diff --git a/chef/core/src/main/java/org/jclouds/ohai/config/multibindings/package-info.java b/chef/core/src/main/java/org/jclouds/ohai/config/multibindings/package-info.java new file mode 100644 index 0000000000..2c9fc2ddd8 --- /dev/null +++ b/chef/core/src/main/java/org/jclouds/ohai/config/multibindings/package-info.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2008 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Taken from r1154 of Guice, as they decided to stop supporting multiple bindings and instead silently throw them away. + */ +package org.jclouds.ohai.config.multibindings; + diff --git a/chef/core/src/main/java/org/jclouds/ohai/functions/MapSetToMultimap.java b/chef/core/src/main/java/org/jclouds/ohai/functions/MapSetToMultimap.java new file mode 100644 index 0000000000..1d3c73291b --- /dev/null +++ b/chef/core/src/main/java/org/jclouds/ohai/functions/MapSetToMultimap.java @@ -0,0 +1,48 @@ +/** + * + * Copyright (C) 2010 Cloud Conscious, LLC. + * + * ==================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ==================================================================== + */ +package org.jclouds.ohai.functions; + +import java.util.Map; +import java.util.Set; +import java.util.Map.Entry; + +import javax.inject.Singleton; + +import com.google.common.base.Function; +import com.google.common.collect.LinkedHashMultimap; +import com.google.common.collect.Multimap; + +/** + * + * @author Adrian Cole + */ +@Singleton +public class MapSetToMultimap implements Function>, Multimap> { + + @Override + public Multimap apply(Map> from) { + Multimap returnV = LinkedHashMultimap.create(); + for (Entry> entry : from.entrySet()) { + for (V value : entry.getValue()) + returnV.put(entry.getKey(), value); + } + return returnV; + } + +} \ No newline at end of file diff --git a/chef/core/src/main/java/org/jclouds/ohai/functions/NestSlashKeys.java b/chef/core/src/main/java/org/jclouds/ohai/functions/NestSlashKeys.java new file mode 100644 index 0000000000..effc6e8873 --- /dev/null +++ b/chef/core/src/main/java/org/jclouds/ohai/functions/NestSlashKeys.java @@ -0,0 +1,160 @@ +/** + * + * Copyright (C) 2010 Cloud Conscious, LLC. + * + * ==================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ==================================================================== + */ +package org.jclouds.ohai.functions; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import java.lang.reflect.Type; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.Map.Entry; + +import javax.inject.Inject; +import javax.inject.Singleton; + +import org.jclouds.domain.JsonBall; +import org.jclouds.json.Json; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Function; +import com.google.common.base.Predicate; +import com.google.common.base.Splitter; +import com.google.common.base.Supplier; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.collect.Multimap; +import com.google.common.collect.Sets; +import com.google.inject.TypeLiteral; + +/** + * + * + * @author Adrian Cole + */ +@Singleton +public class NestSlashKeys implements Function>, Map> { + + private final Json json; + + @Inject + NestSlashKeys(Json json) { + this.json = checkNotNull(json, "json"); + } + + @Override + public Map apply(Multimap> from) { + + Map autoAttrs = mergeSameKeys(from); + + Map modifiableFlatMap = Maps.newLinkedHashMap(Maps.filterKeys(autoAttrs, + new Predicate() { + + @Override + public boolean apply(String input) { + return input.indexOf('/') == -1; + } + + })); + Map withSlashesMap = Maps.difference(autoAttrs, modifiableFlatMap).entriesOnlyOnLeft(); + for (Entry entry : withSlashesMap.entrySet()) { + List keyParts = Lists.newArrayList(Splitter.on('/').split(entry.getKey())); + JsonBall toInsert = entry.getValue(); + try { + putUnderContext(keyParts, toInsert, modifiableFlatMap); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException("error inserting value in entry: " + entry.getKey(), e); + } + } + return modifiableFlatMap; + } + + private Map mergeSameKeys(Multimap> from) { + Map merged = Maps.newLinkedHashMap(); + for (Entry> entry : from.entries()) { + if (merged.containsKey(entry.getKey())) { + mergeAsPeer(entry.getKey(), entry.getValue().get(), merged); + } else { + merged.put(entry.getKey(), entry.getValue().get()); + } + } + return merged; + } + + @VisibleForTesting + void mergeAsPeer(String key, JsonBall value, Map insertionContext) { + Map valueContext = json.fromJson(insertionContext.get(key).toString(), mapLiteral); + Map toPut = json.> fromJson(value.toString(), mapLiteral); + Set uniques = Sets.difference(toPut.keySet(), valueContext.keySet()); + for (String k : uniques) + valueContext.put(k, toPut.get(k)); + Set conflicts = Sets.difference(toPut.keySet(), uniques); + for (String k : conflicts) { + JsonBall v = toPut.get(k); + if (v.toString().matches("^\\{.*\\}$")) { + mergeAsPeer(k, v, valueContext); + } else { + // replace + valueContext.put(k, v); + } + } + insertionContext.put(key, new JsonBall(json.toJson(valueContext, mapLiteral))); + } + + /** + * @param keyParts + * @param toInsert + * @param destination + * @throws IllegalArgumentException + *

+ * if destination.get(keyParts(0)) is not a map * + *

+ * keyParts is zero length + */ + void putUnderContext(List keyParts, JsonBall toInsert, Map destination) { + checkNotNull(keyParts, "keyParts"); + checkArgument(keyParts.size() >= 1, "keyParts must contain at least one element"); + + checkNotNull(toInsert, "toInsert"); + checkNotNull(destination, "destination"); + + String rootKey = keyParts.remove(0); + String rootValue = destination.containsKey(rootKey) ? destination.get(rootKey).toString() : "{}"; + + checkArgument(rootValue.matches("^\\{.*\\}$"), "value must be a hash: %s", rootValue); + Map insertionContext = json.fromJson(rootValue, mapLiteral); + if (keyParts.size() == 1) { + if (!insertionContext.containsKey(keyParts.get(0))) { + insertionContext.put(keyParts.get(0), toInsert); + } else { + String key = keyParts.get(0); + mergeAsPeer(key, toInsert, insertionContext); + } + } else { + putUnderContext(keyParts, toInsert, insertionContext); + } + destination.put(rootKey, new JsonBall(json.toJson(insertionContext, mapLiteral))); + } + + final Type mapLiteral = new TypeLiteral>() { + }.getType(); + final Type listLiteral = new TypeLiteral>() { + }.getType(); +} \ No newline at end of file diff --git a/chef/core/src/main/java/org/jclouds/ohai/plugins/WhiteListCompliantJVM.java b/chef/core/src/main/java/org/jclouds/ohai/plugins/WhiteListCompliantJVM.java deleted file mode 100644 index 9407da5255..0000000000 --- a/chef/core/src/main/java/org/jclouds/ohai/plugins/WhiteListCompliantJVM.java +++ /dev/null @@ -1,86 +0,0 @@ -/** - * - * Copyright (C) 2010 Cloud Conscious, LLC. - * - * ==================================================================== - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ==================================================================== - */ -package org.jclouds.ohai.plugins; - -import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.inject.internal.Maps.newLinkedHashMap; -import static org.jclouds.ohai.Util.OhaiUtils.toOhaiTime; - -import java.util.Map; -import java.util.Properties; - -import javax.annotation.Resource; -import javax.inject.Inject; -import javax.inject.Named; -import javax.inject.Provider; -import javax.inject.Singleton; - -import org.jclouds.domain.JsonBall; -import org.jclouds.json.Json; -import org.jclouds.logging.Logger; - -import com.google.common.base.Supplier; - -/** - * - * Gathers Ohai data from the JVM. Note that this is intended to be Google App - * Engine compatible, so please do not access the network, JMX, or other classes - * not on the whitelist. - * - * @author Adrian Cole - */ -@Singleton -public class WhiteListCompliantJVM implements Supplier> { - - @Resource - protected Logger logger = Logger.NULL; - - private final Json json; - private final Provider nanoTimeProvider; - private final Provider systemPropertiesProvider; - - @Inject - public WhiteListCompliantJVM(Json json, @Named("nanoTime") Provider nanoTimeProvider, - @Named("systemProperties") Provider systemPropertiesProvider) { - this.json = checkNotNull(json, "json"); - this.nanoTimeProvider = checkNotNull(nanoTimeProvider, "nanoTimeProvider"); - this.systemPropertiesProvider = checkNotNull(systemPropertiesProvider, "systemPropertiesProvider"); - } - - public Map get() { - Map returnVal = newLinkedHashMap(); - Properties systemProperties = systemPropertiesProvider.get(); - - returnVal.put("ohai_time", toOhaiTime(nanoTimeProvider.get())); - - returnVal.put("java", new JsonBall(json.toJson(systemProperties))); - - String platform = systemProperties.getProperty("os.name"); - platform = platform.replaceAll("[ -]", "").toLowerCase(); - - returnVal.put("platform", new JsonBall(platform)); - - if (systemProperties.getProperty("os.version") != null) - returnVal.put("platform_version", new JsonBall(systemProperties.getProperty("os.version"))); - - if (systemProperties.getProperty("user.name") != null) - returnVal.put("current_user", new JsonBall(systemProperties.getProperty("user.name"))); - return returnVal; - } -} \ No newline at end of file diff --git a/chef/core/src/main/java/org/jclouds/ohai/config/WhiteListCompliantOhaiJVMModule.java b/chef/core/src/main/java/org/jclouds/ohai/suppliers/UptimeSecondsSupplier.java similarity index 61% rename from chef/core/src/main/java/org/jclouds/ohai/config/WhiteListCompliantOhaiJVMModule.java rename to chef/core/src/main/java/org/jclouds/ohai/suppliers/UptimeSecondsSupplier.java index 286205bc4b..64d91d8407 100644 --- a/chef/core/src/main/java/org/jclouds/ohai/config/WhiteListCompliantOhaiJVMModule.java +++ b/chef/core/src/main/java/org/jclouds/ohai/suppliers/UptimeSecondsSupplier.java @@ -16,27 +16,35 @@ * limitations under the License. * ==================================================================== */ -package org.jclouds.ohai.config; +package org.jclouds.ohai.suppliers; -import java.util.Map; +import java.lang.management.RuntimeMXBean; + +import javax.inject.Inject; +import javax.inject.Singleton; import org.jclouds.domain.JsonBall; -import org.jclouds.ohai.plugins.WhiteListCompliantJVM; import com.google.common.base.Supplier; -import com.google.common.collect.ImmutableList; -import com.google.inject.Injector; /** - * Wires the components needed to parse ohai data without violating the GAE JVM * * @author Adrian Cole */ -@ConfiguresOhai -public class WhiteListCompliantOhaiJVMModule extends BaseOhaiJVMModule { +@Singleton +public class UptimeSecondsSupplier implements Supplier { + + @Inject + UptimeSecondsSupplier(RuntimeMXBean runtime) { + this.runtime = runtime; + } + + private final RuntimeMXBean runtime; @Override - protected Iterable>> suppliers(Injector injector) { - return ImmutableList.>> of(injector.getInstance(WhiteListCompliantJVM.class)); + public JsonBall get() { + long uptimeInSeconds = runtime.getUptime() / 1000; + return new JsonBall(uptimeInSeconds); } + } \ No newline at end of file diff --git a/chef/core/src/test/java/org/jclouds/chef/BaseChefClientLiveTest.java b/chef/core/src/test/java/org/jclouds/chef/BaseChefClientLiveTest.java index 32c282f322..a2fdab9bc7 100644 --- a/chef/core/src/test/java/org/jclouds/chef/BaseChefClientLiveTest.java +++ b/chef/core/src/test/java/org/jclouds/chef/BaseChefClientLiveTest.java @@ -174,7 +174,7 @@ public abstract class BaseChefClientLiveTest { System.err.printf("%s/%s:%n", cookbook, version); CookbookVersion cookbookO = getAdminConnection().getCookbook(cookbook, version); for (Resource resource : ImmutableList. builder().addAll(cookbookO.getDefinitions()).addAll( - cookbookO.getFiles()).addAll(cookbookO.getLibraries()).addAll(cookbookO.getProviders()).addAll( + cookbookO.getFiles()).addAll(cookbookO.getLibraries()).addAll(cookbookO.getSuppliers()).addAll( cookbookO.getRecipes()).addAll(cookbookO.getResources()).addAll(cookbookO.getRootFiles()).addAll( cookbookO.getTemplates()).build()) { try { diff --git a/chef/core/src/test/java/org/jclouds/chef/ChefClientLiveTest.java b/chef/core/src/test/java/org/jclouds/chef/ChefClientLiveTest.java index 258f137c33..ab08a3319a 100644 --- a/chef/core/src/test/java/org/jclouds/chef/ChefClientLiveTest.java +++ b/chef/core/src/test/java/org/jclouds/chef/ChefClientLiveTest.java @@ -34,7 +34,6 @@ import org.jclouds.json.Json; import org.jclouds.json.config.GsonModule; import org.jclouds.logging.log4j.config.Log4JLoggingModule; import org.jclouds.rest.HttpClient; -import org.jclouds.rest.RestContextFactory; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; @@ -80,8 +79,8 @@ public class ChefClientLiveTest extends BaseChefClientLiveTest { private ChefContext createConnection(String identity, String key) throws IOException { Properties props = new Properties(); props.setProperty("chef.endpoint", endpoint); - return (ChefContext) new RestContextFactory(). createContext("chef", identity, key, - ImmutableSet. of(new Log4JLoggingModule()), props); + return new ChefContextFactory().createContext(identity, key, ImmutableSet. of(new Log4JLoggingModule()), + props); } @Override diff --git a/chef/core/src/test/java/org/jclouds/chef/strategy/internal/CreateNodeAndPopulateAutomaticAttributesImplLiveTest.java b/chef/core/src/test/java/org/jclouds/chef/strategy/internal/CreateNodeAndPopulateAutomaticAttributesImplLiveTest.java index 90b99ac481..29aa475e55 100644 --- a/chef/core/src/test/java/org/jclouds/chef/strategy/internal/CreateNodeAndPopulateAutomaticAttributesImplLiveTest.java +++ b/chef/core/src/test/java/org/jclouds/chef/strategy/internal/CreateNodeAndPopulateAutomaticAttributesImplLiveTest.java @@ -31,17 +31,16 @@ import java.util.Set; import org.jclouds.chef.ChefClient; import org.jclouds.chef.domain.Node; import org.jclouds.domain.JsonBall; -import org.jclouds.ohai.config.BaseOhaiModule; +import org.jclouds.ohai.AutomaticSupplier; import org.jclouds.ohai.config.ConfiguresOhai; +import org.jclouds.ohai.config.OhaiModule; import org.testng.annotations.BeforeTest; import org.testng.annotations.Test; import com.google.common.base.Supplier; import com.google.common.base.Suppliers; -import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; -import com.google.inject.Injector; import com.google.inject.Module; /** @@ -57,13 +56,11 @@ public class CreateNodeAndPopulateAutomaticAttributesImplLiveTest extends BaseCh private ChefClient chef; @ConfiguresOhai - static class TestOhaiModule extends BaseOhaiModule { + static class TestOhaiModule extends OhaiModule { @Override - protected Iterable>> suppliers(Injector injector) { - Supplier> supplier = Suppliers.> ofInstance(ImmutableMap - . of("foo", new JsonBall("bar"))); - return ImmutableList.>> of(supplier); + protected Supplier> provideAutomatic(AutomaticSupplier in) { + return Suppliers.> ofInstance(ImmutableMap.of("foo", new JsonBall("bar"))); } } diff --git a/chef/core/src/test/java/org/jclouds/chef/strategy/internal/UpdateAutomaticAttributesOnNodeImplLiveTest.java b/chef/core/src/test/java/org/jclouds/chef/strategy/internal/UpdateAutomaticAttributesOnNodeImplLiveTest.java index d4129bbc08..91bb2d0028 100644 --- a/chef/core/src/test/java/org/jclouds/chef/strategy/internal/UpdateAutomaticAttributesOnNodeImplLiveTest.java +++ b/chef/core/src/test/java/org/jclouds/chef/strategy/internal/UpdateAutomaticAttributesOnNodeImplLiveTest.java @@ -31,17 +31,16 @@ import java.util.Set; import org.jclouds.chef.ChefClient; import org.jclouds.chef.domain.Node; import org.jclouds.domain.JsonBall; -import org.jclouds.ohai.config.BaseOhaiModule; +import org.jclouds.ohai.AutomaticSupplier; import org.jclouds.ohai.config.ConfiguresOhai; +import org.jclouds.ohai.config.OhaiModule; import org.testng.annotations.BeforeTest; import org.testng.annotations.Test; import com.google.common.base.Supplier; import com.google.common.base.Suppliers; -import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; -import com.google.inject.Injector; import com.google.inject.Module; /** @@ -55,13 +54,10 @@ public class UpdateAutomaticAttributesOnNodeImplLiveTest extends BaseChefStrateg private ChefClient chef; @ConfiguresOhai - static class TestOhaiModule extends BaseOhaiModule { - + static class TestOhaiModule extends OhaiModule { @Override - protected Iterable>> suppliers(Injector injector) { - Supplier> supplier = Suppliers.> ofInstance(ImmutableMap - . of("foo", new JsonBall("bar"))); - return ImmutableList.>> of(supplier); + protected Supplier> provideAutomatic(AutomaticSupplier in) { + return Suppliers.> ofInstance(ImmutableMap.of("foo", new JsonBall("bar"))); } } diff --git a/chef/core/src/test/java/org/jclouds/ohai/plugins/JMXTest.java b/chef/core/src/test/java/org/jclouds/ohai/config/JMXTest.java similarity index 77% rename from chef/core/src/test/java/org/jclouds/ohai/plugins/JMXTest.java rename to chef/core/src/test/java/org/jclouds/ohai/config/JMXTest.java index bcac75eef4..f12da293ae 100644 --- a/chef/core/src/test/java/org/jclouds/ohai/plugins/JMXTest.java +++ b/chef/core/src/test/java/org/jclouds/ohai/config/JMXTest.java @@ -21,22 +21,27 @@ * under the License. * ==================================================================== */ -package org.jclouds.ohai.plugins; +package org.jclouds.ohai.config; import static org.easymock.EasyMock.expect; import static org.easymock.classextension.EasyMock.createMock; import static org.easymock.classextension.EasyMock.replay; -import static org.easymock.classextension.EasyMock.verify; import static org.testng.Assert.assertEquals; import java.lang.management.RuntimeMXBean; +import java.util.Map; + +import javax.inject.Inject; import org.jclouds.chef.config.ChefParserModule; +import org.jclouds.domain.JsonBall; import org.jclouds.json.Json; import org.jclouds.json.config.GsonModule; -import org.jclouds.ohai.config.JMXOhaiJVMModule; +import org.jclouds.ohai.Automatic; +import org.jclouds.ohai.config.JMXOhaiModule; import org.testng.annotations.Test; +import com.google.common.base.Supplier; import com.google.inject.Guice; import com.google.inject.Injector; @@ -57,18 +62,23 @@ public class JMXTest { replay(runtime); - Injector injector = Guice.createInjector(new ChefParserModule(), new GsonModule(), new JMXOhaiJVMModule() { + Injector injector = Guice.createInjector(new ChefParserModule(), new GsonModule(), new JMXOhaiModule() { @Override protected RuntimeMXBean provideRuntimeMXBean() { return runtime; } }); Json json = injector.getInstance(Json.class); - JMX jmx = injector.getInstance(JMX.class); + Ohai ohai = injector.getInstance(Ohai.class); + assertEquals(json.toJson(ohai.ohai.get().get("uptime_seconds")), "69876"); + } - assertEquals(json.toJson(jmx.get()), "{\"uptime_seconds\":69876}"); - - verify(runtime); + static class Ohai { + private Supplier> ohai; + @Inject + public Ohai(@Automatic Supplier> ohai) { + this.ohai = ohai; + } } } diff --git a/chef/core/src/test/java/org/jclouds/ohai/config/OhaiModuleTest.java b/chef/core/src/test/java/org/jclouds/ohai/config/OhaiModuleTest.java new file mode 100644 index 0000000000..1052033ca4 --- /dev/null +++ b/chef/core/src/test/java/org/jclouds/ohai/config/OhaiModuleTest.java @@ -0,0 +1,144 @@ +/** + * + * Copyright (C) 2010 Cloud Conscious, LLC. + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + *http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ +package org.jclouds.ohai.config; + +import static org.jclouds.ohai.Util.OhaiUtils.ohaiAutomaticAttributeBinder; +import static org.testng.Assert.assertEquals; + +import java.net.SocketException; +import java.util.Map; +import java.util.Properties; + +import javax.inject.Inject; + +import org.jclouds.chef.config.ChefParserModule; +import org.jclouds.domain.JsonBall; +import org.jclouds.json.Json; +import org.jclouds.json.config.GsonModule; +import org.jclouds.ohai.Automatic; +import org.jclouds.ohai.config.multibindings.MapBinder; +import org.testng.annotations.Test; + +import com.google.common.base.Supplier; +import com.google.common.base.Suppliers; +import com.google.inject.AbstractModule; +import com.google.inject.Guice; +import com.google.inject.Injector; +import com.google.inject.TypeLiteral; +import com.google.inject.util.Providers; + +/** + * Tests behavior of {@code OhaiModule} + * + * @author Adrian Cole + */ +@Test(groups = "unit", testName = "ohai.OhaiModuleTest") +public class OhaiModuleTest { + + @Test + public void test() throws SocketException { + + final Properties sysProperties = new Properties(); + + sysProperties.setProperty("os.name", "Mac OS X"); + sysProperties.setProperty("os.version", "10.3.0"); + sysProperties.setProperty("user.name", "user"); + + Injector injector = Guice.createInjector(new ChefParserModule(), new GsonModule(), new OhaiModule() { + @Override + protected Long millis() { + return 127999291932529l; + } + + @Override + protected Properties systemProperties() { + return sysProperties; + } + + }); + Ohai ohai = injector.getInstance(Ohai.class); + Json json = injector.getInstance(Json.class); + + assertEquals( + json.toJson(ohai.ohai.get(), new TypeLiteral>() { + }.getType()), + "{\"ohai_time\":127999291932529,\"platform\":\"macosx\",\"platform_version\":\"10.3.0\",\"current_user\":\"user\",\"jvm\":{\"system\":{\"user.name\":\"user\",\"os.version\":\"10.3.0\",\"os.name\":\"Mac OS X\"}}}"); + } + + public void test2modules() throws SocketException { + + final Properties sysProperties = new Properties(); + + sysProperties.setProperty("os.name", "Mac OS X"); + sysProperties.setProperty("os.version", "10.3.0"); + sysProperties.setProperty("user.name", "user"); + + Injector injector = Guice.createInjector(new ChefParserModule(), new GsonModule(), new OhaiModule() { + @Override + protected Long millis() { + return 1279992919l; + } + + @Override + protected Properties systemProperties() { + return sysProperties; + } + + }, new AbstractModule() { + + @Override + protected void configure() { + MapBinder> mapbinder = ohaiAutomaticAttributeBinder(binder()); + mapbinder.addBinding("test").toProvider( + Providers.of(Suppliers.ofInstance(new JsonBall("{\"prop1\":\"test1\"}")))); + } + + }, new AbstractModule() { + + @Override + protected void configure() { + MapBinder> mapbinder = ohaiAutomaticAttributeBinder(binder()); + mapbinder.addBinding("test").toProvider( + Providers.of(Suppliers.ofInstance(new JsonBall("{\"prop2\":\"test2\"}")))); + } + + }); + Ohai ohai = injector.getInstance(Ohai.class); + Json json = injector.getInstance(Json.class); + + assertEquals( + json.toJson(ohai.ohai.get(), new TypeLiteral>() { + }.getType()), + "{\"ohai_time\":1279992919,\"platform\":\"macosx\",\"platform_version\":\"10.3.0\",\"current_user\":\"user\",\"test\":{\"prop1\":\"test1\",\"prop2\":\"test2\"},\"jvm\":{\"system\":{\"user.name\":\"user\",\"os.version\":\"10.3.0\",\"os.name\":\"Mac OS X\"}}}"); + } + + static class Ohai { + private Supplier> ohai; + + @Inject + public Ohai(@Automatic Supplier> ohai) { + this.ohai = ohai; + } + } +} diff --git a/chef/core/src/test/java/org/jclouds/ohai/functions/NestSlashKeysTest.java b/chef/core/src/test/java/org/jclouds/ohai/functions/NestSlashKeysTest.java new file mode 100644 index 0000000000..646efefb65 --- /dev/null +++ b/chef/core/src/test/java/org/jclouds/ohai/functions/NestSlashKeysTest.java @@ -0,0 +1,106 @@ +/** + * + * Copyright (C) 2010 Cloud Conscious, LLC. + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + *http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ +package org.jclouds.ohai.functions; + +import static org.testng.Assert.assertEquals; + +import java.io.IOException; + +import org.jclouds.chef.config.ChefParserModule; +import org.jclouds.domain.JsonBall; +import org.jclouds.json.Json; +import org.jclouds.json.config.GsonModule; +import org.testng.annotations.BeforeTest; +import org.testng.annotations.Test; + +import com.google.common.base.Supplier; +import com.google.common.base.Suppliers; +import com.google.common.collect.ImmutableMultimap; +import com.google.inject.Guice; +import com.google.inject.Injector; + +/** + * Tests behavior of {@code NestSlashKeys} + * + * @author Adrian Cole + */ +@Test(groups = "unit", sequential = true, testName = "ohai.NestSlashKeysTest") +public class NestSlashKeysTest { + + private NestSlashKeys converter; + private Json json; + + @BeforeTest + protected void setUpInjector() throws IOException { + Injector injector = Guice.createInjector(new ChefParserModule(), new GsonModule()); + converter = injector.getInstance(NestSlashKeys.class); + json = injector.getInstance(Json.class); + } + + @Test + public void testBase() { + assertEquals(json.toJson(converter.apply(ImmutableMultimap.> of("java", Suppliers + .ofInstance(new JsonBall("java"))))), "{\"java\":\"java\"}"); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void testIllegal() { + json.toJson(converter.apply(ImmutableMultimap.> of("java", Suppliers + .ofInstance(new JsonBall("java")), "java/system", Suppliers.ofInstance(new JsonBall("system"))))); + } + + @Test + public void testOne() { + assertEquals(json.toJson(converter.apply(ImmutableMultimap.> of("java", Suppliers + .ofInstance(new JsonBall("{\"time\":\"time\"}")), "java/system", Suppliers + .ofInstance(new JsonBall("system"))))), "{\"java\":{\"time\":\"time\",\"system\":\"system\"}}"); + } + + @Test + public void testOneDuplicate() { + assertEquals(json.toJson(converter.apply(ImmutableMultimap.> of("java", Suppliers + .ofInstance(new JsonBall("{\"time\":\"time\"}")), "java", Suppliers.ofInstance(new JsonBall( + "{\"system\":\"system\"}"))))), "{\"java\":{\"time\":\"time\",\"system\":\"system\"}}"); + } + + @Test + public void testMerge() { + assertEquals(json.toJson(converter.apply(ImmutableMultimap.> of("java", Suppliers + .ofInstance(new JsonBall("{\"time\":{\"1\":\"hello\"}}")), "java/time", Suppliers.ofInstance(new JsonBall( + "{\"2\":\"goodbye\"}"))))), "{\"java\":{\"time\":{\"1\":\"hello\",\"2\":\"goodbye\"}}}"); + } + @Test + public void testMergeNestedTwice() { + assertEquals(json.toJson(converter.apply(ImmutableMultimap.> of("java", Suppliers + .ofInstance(new JsonBall("{\"time\":{\"1\":\"hello\"}}")), "java", Suppliers.ofInstance(new JsonBall( + "{\"time\":{\"2\":\"goodbye\"}}"))))), "{\"java\":{\"time\":{\"1\":\"hello\",\"2\":\"goodbye\"}}}"); + } + + @Test + public void testReplaceList() { + assertEquals(json.toJson(converter.apply(ImmutableMultimap.> of("java", Suppliers + .ofInstance(new JsonBall("{\"time\":{\"1\":[\"hello\"]}}")), "java/time", Suppliers.ofInstance(new JsonBall( + "{\"1\":[\"goodbye\"]}"))))), "{\"java\":{\"time\":{\"1\":[\"goodbye\"]}}}"); + } +} diff --git a/chef/core/src/test/java/org/jclouds/ohai/plugins/WhiteListCompliantJVMTest.java b/chef/core/src/test/java/org/jclouds/ohai/plugins/WhiteListCompliantJVMTest.java deleted file mode 100644 index 7f016660e9..0000000000 --- a/chef/core/src/test/java/org/jclouds/ohai/plugins/WhiteListCompliantJVMTest.java +++ /dev/null @@ -1,78 +0,0 @@ -/** - * - * Copyright (C) 2010 Cloud Conscious, LLC. - * - * ==================================================================== - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - *http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * ==================================================================== - */ -package org.jclouds.ohai.plugins; - -import static org.testng.Assert.assertEquals; - -import java.net.SocketException; -import java.util.Properties; - -import org.jclouds.chef.config.ChefParserModule; -import org.jclouds.json.Json; -import org.jclouds.json.config.GsonModule; -import org.jclouds.ohai.config.WhiteListCompliantOhaiJVMModule; -import org.testng.annotations.Test; - -import com.google.inject.Guice; -import com.google.inject.Injector; - -/** - * Tests behavior of {@code WhiteListCompliantJVM} - * - * @author Adrian Cole - */ -@Test(groups = "unit", testName = "ohai.WhiteListCompliantJVMTest") -public class WhiteListCompliantJVMTest { - - @Test - public void test() throws SocketException { - - final Properties sysProperties = new Properties(); - - sysProperties.setProperty("os.name", "Mac OS X"); - sysProperties.setProperty("os.version", "10.3.0"); - sysProperties.setProperty("user.name", "user"); - - Injector injector = Guice.createInjector(new ChefParserModule(), new GsonModule(), - new WhiteListCompliantOhaiJVMModule() { - @Override - protected Long nanoTime() { - return 1279992919325290l; - } - - @Override - protected Properties systemProperties() { - return sysProperties; - } - - }); - Json json = injector.getInstance(Json.class); - WhiteListCompliantJVM WhiteListCompliantJVM = injector.getInstance(WhiteListCompliantJVM.class); - - assertEquals( - json.toJson(WhiteListCompliantJVM.get()), - "{\"ohai_time\":1279992919.32529,\"java\":{\"user.name\":\"user\",\"os.version\":\"10.3.0\",\"os.name\":\"Mac OS X\"},\"platform\":\"macosx\",\"platform_version\":\"10.3.0\",\"current_user\":\"user\"}"); - - } -} diff --git a/chef/core/src/test/java/org/jclouds/ohai/util/OhaiUtilsTest.java b/chef/core/src/test/java/org/jclouds/ohai/util/OhaiUtilsTest.java index dbba9b539c..3c836b80c7 100644 --- a/chef/core/src/test/java/org/jclouds/ohai/util/OhaiUtilsTest.java +++ b/chef/core/src/test/java/org/jclouds/ohai/util/OhaiUtilsTest.java @@ -38,16 +38,16 @@ import org.testng.annotations.Test; */ @Test(groups = "unit", sequential = true, testName = "ohai.OhaiUtilsTest") public class OhaiUtilsTest { - public static long nanotime = 1280251180727244000l; - public static String nanotimeString = "1280251180727.244"; + public static long millis = 1280251180727l; + public static String millisString = "1280251180727"; public static Date now = new Date(1280251180727l); public void testToOhaiTime() { - assertEquals(OhaiUtils.toOhaiTime(nanotime).toString(), nanotimeString); + assertEquals(OhaiUtils.toOhaiTime(millis).toString(), millisString); } public void testFromOhaiTime() { - assertEquals(OhaiUtils.fromOhaiTime(new JsonBall(nanotimeString)), now); + assertEquals(OhaiUtils.fromOhaiTime(new JsonBall(millisString)), now); } diff --git a/chef/servlet/pom.xml b/chef/servlet/pom.xml index 9909784184..5d32cad055 100644 --- a/chef/servlet/pom.xml +++ b/chef/servlet/pom.xml @@ -54,6 +54,13 @@ jclouds-chef ${project.version} + + ${project.groupId} + jclouds-core + ${project.version} + test-jar + test + org.apache.geronimo.specs geronimo-servlet_2.5_spec diff --git a/chef/servlet/src/main/java/org/jclouds/chef/servlet/ChefRegistrationListener.java b/chef/servlet/src/main/java/org/jclouds/chef/servlet/ChefRegistrationListener.java index 59c6250c28..d74ad7bfee 100644 --- a/chef/servlet/src/main/java/org/jclouds/chef/servlet/ChefRegistrationListener.java +++ b/chef/servlet/src/main/java/org/jclouds/chef/servlet/ChefRegistrationListener.java @@ -28,18 +28,19 @@ import static org.jclouds.chef.reference.ChefConstants.CHEF_SERVICE_CLIENT; import java.util.Properties; import java.util.Set; +import javax.servlet.ServletContext; import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; -import org.jclouds.chef.ChefAsyncClient; -import org.jclouds.chef.ChefClient; -import org.jclouds.chef.ChefContext; +import org.jclouds.chef.ChefContextFactory; import org.jclouds.chef.ChefService; import org.jclouds.chef.reference.ChefConstants; import org.jclouds.chef.servlet.functions.InitParamsToProperties; import org.jclouds.logging.Logger; import org.jclouds.logging.jdk.JDKLogger; -import org.jclouds.rest.RestContextFactory; + +import com.google.common.collect.ImmutableSet; +import com.google.inject.AbstractModule; /** * Registers a new node in Chef and binds its name to @@ -58,12 +59,12 @@ public class ChefRegistrationListener implements ServletContextListener { public void contextInitialized(ServletContextEvent servletContextEvent) { try { logger.debug("starting initialization"); - Properties overrides = InitParamsToProperties.INSTANCE.apply(servletContextEvent); + Properties overrides = InitParamsToProperties.INSTANCE.apply(servletContextEvent.getServletContext()); String role = getInitParam(servletContextEvent, CHEF_ROLE); logger.trace("creating client connection"); - ChefService client = createService(overrides); + ChefService client = createService(overrides, servletContextEvent); logger.debug("created client connection"); String nodeName; @@ -103,9 +104,15 @@ public class ChefRegistrationListener implements ServletContextListener { return nodeName; } - private ChefService createService(Properties props) { - return ((ChefContext) new RestContextFactory(). createContext("chef", props)) - .getChefService(); + private ChefService createService(Properties props, final ServletContextEvent servletContextEvent) { + return new ChefContextFactory().createContext(ImmutableSet.of(new AbstractModule() { + + @Override + protected void configure() { + bind(ServletContext.class).toInstance(servletContextEvent.getServletContext()); + } + + }), props).getChefService(); } private static String getInitParam(ServletContextEvent servletContextEvent, String name) { diff --git a/chef/servlet/src/main/java/org/jclouds/chef/servlet/functions/InitParamsToProperties.java b/chef/servlet/src/main/java/org/jclouds/chef/servlet/functions/InitParamsToProperties.java index 084448abba..5000d2c9ba 100644 --- a/chef/servlet/src/main/java/org/jclouds/chef/servlet/functions/InitParamsToProperties.java +++ b/chef/servlet/src/main/java/org/jclouds/chef/servlet/functions/InitParamsToProperties.java @@ -22,7 +22,7 @@ import java.util.Enumeration; import java.util.Properties; import javax.inject.Singleton; -import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContext; import com.google.common.base.Function; @@ -31,16 +31,16 @@ import com.google.common.base.Function; * @author Adrian Cole */ @Singleton -public class InitParamsToProperties implements Function { +public class InitParamsToProperties implements Function { public static final InitParamsToProperties INSTANCE = new InitParamsToProperties(); - public Properties apply(ServletContextEvent servletContextEvent) { + public Properties apply(ServletContext servletContext) { Properties overrides = new Properties(); - Enumeration e = servletContextEvent.getServletContext().getInitParameterNames(); + Enumeration e = servletContext.getInitParameterNames(); while (e.hasMoreElements()) { String propertyName = e.nextElement().toString(); - overrides.setProperty(propertyName, servletContextEvent.getServletContext().getInitParameter(propertyName)); + overrides.setProperty(propertyName, servletContext.getInitParameter(propertyName)); } return overrides; } diff --git a/chef/servlet/src/main/java/org/jclouds/ohai/servlet/config/ServletOhaiModule.java b/chef/servlet/src/main/java/org/jclouds/ohai/servlet/config/ServletOhaiModule.java new file mode 100644 index 0000000000..8355834058 --- /dev/null +++ b/chef/servlet/src/main/java/org/jclouds/ohai/servlet/config/ServletOhaiModule.java @@ -0,0 +1,44 @@ +/** + * + * Copyright (C) 2010 Cloud Conscious, LLC. + * + * ==================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ==================================================================== + */ +package org.jclouds.ohai.servlet.config; + + +import static org.jclouds.ohai.Util.OhaiUtils.ohaiAutomaticAttributeBinder; + +import org.jclouds.domain.JsonBall; +import org.jclouds.ohai.config.multibindings.MapBinder; +import org.jclouds.ohai.servlet.suppliers.ServletContextInfoSupplier; + +import com.google.common.base.Supplier; +import com.google.inject.AbstractModule; + +/** + * Wires the components needed to parse ohai data + * + * @author Adrian Cole + */ +public class ServletOhaiModule extends AbstractModule { + + @Override + protected void configure() { + MapBinder> mapbinder = ohaiAutomaticAttributeBinder(binder()); + mapbinder.addBinding("webapp").to(ServletContextInfoSupplier.class); + } + +} \ No newline at end of file diff --git a/chef/servlet/src/main/java/org/jclouds/ohai/servlet/suppliers/ServletContextInfoSupplier.java b/chef/servlet/src/main/java/org/jclouds/ohai/servlet/suppliers/ServletContextInfoSupplier.java new file mode 100644 index 0000000000..eff2c71f9c --- /dev/null +++ b/chef/servlet/src/main/java/org/jclouds/ohai/servlet/suppliers/ServletContextInfoSupplier.java @@ -0,0 +1,49 @@ +/** + * + * Copyright (C) 2010 Cloud Conscious, LLC. + * + * ==================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ==================================================================== + */ +package org.jclouds.ohai.servlet.suppliers; + +import javax.inject.Inject; +import javax.inject.Singleton; +import javax.servlet.ServletContext; + +import org.jclouds.chef.servlet.functions.InitParamsToProperties; +import org.jclouds.json.Json; + +/** + * + * @author Adrian Cole + */ +@Singleton +public class ServletContextInfoSupplier extends ServletContextSupplier { + private final InitParamsToProperties converter; + private final Json json; + + @Inject + public ServletContextInfoSupplier(ServletContext servletContext, InitParamsToProperties converter, Json json) { + super(servletContext); + this.converter = converter; + this.json = json; + } + + @Override + protected String provideJson(ServletContext servletContext) { + return String.format("{\"server_info\":\"%s\",\"init_params\":%s}", servletContext.getServerInfo(), json + .toJson(converter.apply(servletContext))); + } +} \ No newline at end of file diff --git a/chef/servlet/src/main/java/org/jclouds/ohai/servlet/suppliers/ServletContextSupplier.java b/chef/servlet/src/main/java/org/jclouds/ohai/servlet/suppliers/ServletContextSupplier.java new file mode 100644 index 0000000000..5a095ebd5d --- /dev/null +++ b/chef/servlet/src/main/java/org/jclouds/ohai/servlet/suppliers/ServletContextSupplier.java @@ -0,0 +1,51 @@ +/** + * + * Copyright (C) 2010 Cloud Conscious, LLC. + * + * ==================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ==================================================================== + */ +package org.jclouds.ohai.servlet.suppliers; + +import javax.inject.Inject; +import javax.inject.Singleton; +import javax.servlet.ServletContext; + +import org.jclouds.domain.JsonBall; + +import com.google.common.base.Supplier; + +/** + * + * @author Adrian Cole + */ +@Singleton +public abstract class ServletContextSupplier implements Supplier { + private final ServletContext servletContext; + + @Inject + public ServletContextSupplier(ServletContext servletContext) { + this.servletContext = servletContext; + } + + @Override + public JsonBall get() { + String path = servletContext.getContextPath(); + if (path == null || path.equals("")) + path = "/"; + return new JsonBall(String.format("{\"%s\":%s}", path, provideJson(servletContext))); + } + + protected abstract String provideJson(ServletContext servletContext); +} \ No newline at end of file diff --git a/chef/servlet/src/test/java/org/jclouds/chef/servlet/suppliers/ServletContextInfoSupplierTest.java b/chef/servlet/src/test/java/org/jclouds/chef/servlet/suppliers/ServletContextInfoSupplierTest.java new file mode 100644 index 0000000000..e9c2a5a211 --- /dev/null +++ b/chef/servlet/src/test/java/org/jclouds/chef/servlet/suppliers/ServletContextInfoSupplierTest.java @@ -0,0 +1,156 @@ +/** + * + * Copyright (C) 2010 Cloud Conscious, LLC. + * + * ==================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ==================================================================== + */ +package org.jclouds.chef.servlet.suppliers; + +import static org.easymock.EasyMock.expect; +import static org.easymock.classextension.EasyMock.createMock; +import static org.easymock.classextension.EasyMock.replay; +import static org.testng.Assert.assertEquals; + +import java.util.Properties; + +import javax.servlet.ServletContext; + +import org.jclouds.chef.servlet.functions.InitParamsToProperties; +import org.jclouds.json.Json; +import org.jclouds.json.config.GsonModule; +import org.jclouds.ohai.servlet.config.ServletOhaiModule; +import org.jclouds.ohai.servlet.suppliers.ServletContextInfoSupplier; +import org.testng.annotations.Test; + +import com.google.inject.Guice; +import com.google.inject.Injector; +import com.google.inject.Provides; + +/** + * + * @author Adrian Cole + */ + +@Test(groups = "unit", testName = "chef.ServletContextInfoSupplierTest") +public class ServletContextInfoSupplierTest { + + @Test + public void testApply() { + + final ServletContext servletContext = createMock(ServletContext.class); + final InitParamsToProperties converter = createMock(InitParamsToProperties.class); + Properties props = new Properties(); + props.setProperty("foo", "bar"); + + expect(servletContext.getContextPath()).andReturn("path"); + expect(servletContext.getServerInfo()).andReturn("serverinfo"); + expect(converter.apply(servletContext)).andReturn(props); + + replay(servletContext); + replay(converter); + + Injector injector = Guice.createInjector(new GsonModule(), new ServletOhaiModule() { + @SuppressWarnings("unused") + @Provides + protected ServletContext provideServletContext() { + return servletContext; + } + + @SuppressWarnings("unused") + @Provides + protected InitParamsToProperties provideInitParamsToProperties() { + return converter; + } + }); + + Json json = injector.getInstance(Json.class); + ServletContextInfoSupplier ohai = injector.getInstance(ServletContextInfoSupplier.class); + assertEquals(json.toJson(ohai.get()), + "{\"path\":{\"server_info\":\"serverinfo\",\"init_params\":{\"foo\":\"bar\"}}}"); + + } + + @Test + public void testApplyNullPath() { + + final ServletContext servletContext = createMock(ServletContext.class); + final InitParamsToProperties converter = createMock(InitParamsToProperties.class); + Properties props = new Properties(); + props.setProperty("foo", "bar"); + + expect(servletContext.getContextPath()).andReturn(null); + expect(servletContext.getServerInfo()).andReturn("serverinfo"); + expect(converter.apply(servletContext)).andReturn(props); + + replay(servletContext); + replay(converter); + + Injector injector = Guice.createInjector(new GsonModule(), new ServletOhaiModule() { + @SuppressWarnings("unused") + @Provides + protected ServletContext provideServletContext() { + return servletContext; + } + + @SuppressWarnings("unused") + @Provides + protected InitParamsToProperties provideInitParamsToProperties() { + return converter; + } + }); + + Json json = injector.getInstance(Json.class); + ServletContextInfoSupplier ohai = injector.getInstance(ServletContextInfoSupplier.class); + assertEquals(json.toJson(ohai.get()), + "{\"/\":{\"server_info\":\"serverinfo\",\"init_params\":{\"foo\":\"bar\"}}}"); + + } + + @Test + public void testApplyEmptyPath() { + + final ServletContext servletContext = createMock(ServletContext.class); + final InitParamsToProperties converter = createMock(InitParamsToProperties.class); + Properties props = new Properties(); + props.setProperty("foo", "bar"); + + expect(servletContext.getContextPath()).andReturn(""); + expect(servletContext.getServerInfo()).andReturn("serverinfo"); + expect(converter.apply(servletContext)).andReturn(props); + + replay(servletContext); + replay(converter); + + Injector injector = Guice.createInjector(new GsonModule(), new ServletOhaiModule() { + @SuppressWarnings("unused") + @Provides + protected ServletContext provideServletContext() { + return servletContext; + } + + @SuppressWarnings("unused") + @Provides + protected InitParamsToProperties provideInitParamsToProperties() { + return converter; + } + }); + + Json json = injector.getInstance(Json.class); + ServletContextInfoSupplier ohai = injector.getInstance(ServletContextInfoSupplier.class); + assertEquals(json.toJson(ohai.get()), + "{\"/\":{\"server_info\":\"serverinfo\",\"init_params\":{\"foo\":\"bar\"}}}"); + + } +}