Adds EnricherSpec
Project: http://git-wip-us.apache.org/repos/asf/brooklyn-server/repo Commit: http://git-wip-us.apache.org/repos/asf/brooklyn-server/commit/b8216c5e Tree: http://git-wip-us.apache.org/repos/asf/brooklyn-server/tree/b8216c5e Diff: http://git-wip-us.apache.org/repos/asf/brooklyn-server/diff/b8216c5e Branch: refs/heads/0.6.0 Commit: b8216c5e5e076160f23d4f8e869e0b3f4d024cd8 Parents: 9f591ed Author: Aled Sage <[email protected]> Authored: Mon Nov 4 23:22:58 2013 +0000 Committer: Aled Sage <[email protected]> Committed: Tue Nov 5 13:05:47 2013 +0000 ---------------------------------------------------------------------- .../brooklyn/entity/proxying/EntitySpec.java | 41 ++++- .../main/java/brooklyn/policy/EnricherSpec.java | 175 +++++++++++++++++++ .../main/java/brooklyn/policy/EnricherType.java | 38 ++++ .../entity/proxying/InternalEntityFactory.java | 10 ++ .../entity/proxying/InternalPolicyFactory.java | 71 +++++++- .../brooklyn/entity/basic/EntitySpecTest.java | 42 ++++- 6 files changed, 371 insertions(+), 6 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/b8216c5e/api/src/main/java/brooklyn/entity/proxying/EntitySpec.java ---------------------------------------------------------------------- diff --git a/api/src/main/java/brooklyn/entity/proxying/EntitySpec.java b/api/src/main/java/brooklyn/entity/proxying/EntitySpec.java index d654be0..28476a5 100644 --- a/api/src/main/java/brooklyn/entity/proxying/EntitySpec.java +++ b/api/src/main/java/brooklyn/entity/proxying/EntitySpec.java @@ -19,6 +19,8 @@ import brooklyn.config.ConfigKey; import brooklyn.config.ConfigKey.HasConfigKey; import brooklyn.entity.Entity; import brooklyn.management.Task; +import brooklyn.policy.Enricher; +import brooklyn.policy.EnricherSpec; import brooklyn.policy.Policy; import brooklyn.policy.PolicySpec; import brooklyn.util.exceptions.Exceptions; @@ -112,6 +114,8 @@ public class EntitySpec<T extends Entity> implements Serializable { private final Map<ConfigKey<?>, Object> config = Maps.newLinkedHashMap(); private final List<Policy> policies = Lists.newArrayList(); private final List<PolicySpec<?>> policySpecs = Lists.newArrayList(); + private final List<Enricher> enrichers = Lists.newArrayList(); + private final List<EnricherSpec<?>> enricherSpecs = Lists.newArrayList(); private final Set<Class<?>> additionalInterfaces = Sets.newLinkedHashSet(); private final List<EntityInitializer> entityInitializers = Lists.newArrayList(); private volatile boolean immutable; @@ -188,6 +192,14 @@ public class EntitySpec<T extends Entity> implements Serializable { return policies; } + public List<EnricherSpec<?>> getEnricherSpecs() { + return enricherSpecs; + } + + public List<Enricher> getEnrichers() { + return enrichers; + } + public EntitySpec<T> displayName(String val) { checkMutable(); displayName = val; @@ -313,7 +325,6 @@ public class EntitySpec<T extends Entity> implements Serializable { return this; } - /** adds the supplied policies to the spec */ public <V> EntitySpec<T> policies(Iterable<? extends Policy> val) { checkMutable(); @@ -321,6 +332,34 @@ public class EntitySpec<T extends Entity> implements Serializable { return this; } + /** adds a policy to the spec */ + public <V> EntitySpec<T> enricher(Enricher val) { + checkMutable(); + enrichers.add(checkNotNull(val, "enricher")); + return this; + } + + /** adds a policy to the spec */ + public <V> EntitySpec<T> enricher(EnricherSpec<?> val) { + checkMutable(); + enricherSpecs.add(checkNotNull(val, "enricherSpec")); + return this; + } + + /** adds the supplied policies to the spec */ + public <V> EntitySpec<T> enricherSpecs(Iterable<? extends EnricherSpec<?>> val) { + checkMutable(); + enricherSpecs.addAll(Sets.newLinkedHashSet(checkNotNull(val, "enricherSpecs"))); + return this; + } + + /** adds the supplied policies to the spec */ + public <V> EntitySpec<T> enrichers(Iterable<? extends Enricher> val) { + checkMutable(); + enrichers.addAll(Sets.newLinkedHashSet(checkNotNull(val, "enrichers"))); + return this; + } + /** "seals" this spec, preventing any future changes */ public EntitySpec<T> immutable() { immutable = true; http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/b8216c5e/api/src/main/java/brooklyn/policy/EnricherSpec.java ---------------------------------------------------------------------- diff --git a/api/src/main/java/brooklyn/policy/EnricherSpec.java b/api/src/main/java/brooklyn/policy/EnricherSpec.java new file mode 100644 index 0000000..d25f832 --- /dev/null +++ b/api/src/main/java/brooklyn/policy/EnricherSpec.java @@ -0,0 +1,175 @@ +package brooklyn.policy; + +import static com.google.common.base.Preconditions.checkNotNull; + +import java.io.Serializable; +import java.lang.reflect.Modifier; +import java.util.Collections; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import brooklyn.config.ConfigKey; +import brooklyn.config.ConfigKey.HasConfigKey; +import brooklyn.management.Task; +import brooklyn.util.exceptions.Exceptions; + +import com.google.common.base.Objects; +import com.google.common.collect.Maps; + +/** + * Gives details of an enricher to be created. It describes the enricher's configuration, and is + * reusable to create multiple enrichers with the same configuration. + * + * To create an EnricherSpec, it is strongly encouraged to use {@code create(...)} methods. + * + * @param <T> The type of enricher to be created + * + * @author aled + */ +public class EnricherSpec<T extends Enricher> implements Serializable { + + private static final Logger log = LoggerFactory.getLogger(EnricherSpec.class); + + private final static long serialVersionUID = 1L; + + + /** + * Creates a new {@link EnricherSpec} instance for an enricher of the given type. The returned + * {@link EnricherSpec} can then be customized. + * + * @param type A {@link Enricher} class + */ + public static <T extends Enricher> EnricherSpec<T> create(Class<T> type) { + return new EnricherSpec<T>(type); + } + + /** + * Creates a new {@link EnricherSpec} instance with the given config, for an enricher of the given type. + * + * This is primarily for groovy code; equivalent to {@code EnricherSpec.create(type).configure(config)}. + * + * @param config The spec's configuration (see {@link EnricherSpec#configure(Map)}). + * @param type An {@link Enricher} class + */ + public static <T extends Enricher> EnricherSpec<T> create(Map<?,?> config, Class<T> type) { + return EnricherSpec.create(type).configure(config); + } + + private final Class<T> type; + private String displayName; + private final Map<String, Object> flags = Maps.newLinkedHashMap(); + private final Map<ConfigKey<?>, Object> config = Maps.newLinkedHashMap(); + + protected EnricherSpec(Class<T> type) { + checkIsImplementation(type); + checkIsNewStyleImplementation(type); + this.type = type; + } + + public EnricherSpec<T> displayName(String val) { + displayName = val; + return this; + } + + public EnricherSpec<T> configure(Map<?,?> val) { + for (Map.Entry<?, ?> entry: val.entrySet()) { + if (entry.getKey()==null) throw new NullPointerException("Null key not permitted"); + if (entry.getKey() instanceof CharSequence) + flags.put(entry.getKey().toString(), entry.getValue()); + else if (entry.getKey() instanceof ConfigKey<?>) + config.put((ConfigKey<?>)entry.getKey(), entry.getValue()); + else if (entry.getKey() instanceof HasConfigKey<?>) + config.put(((HasConfigKey<?>)entry.getKey()).getConfigKey(), entry.getValue()); + else { + log.warn("Spec "+this+" ignoring unknown config key "+entry.getKey()); + } + } + return this; + } + + public EnricherSpec<T> configure(CharSequence key, Object val) { + flags.put(checkNotNull(key, "key").toString(), val); + return this; + } + + public <V> EnricherSpec<T> configure(ConfigKey<V> key, V val) { + config.put(checkNotNull(key, "key"), val); + return this; + } + + public <V> EnricherSpec<T> configureIfNotNull(ConfigKey<V> key, V val) { + return (val != null) ? configure(key, val) : this; + } + + public <V> EnricherSpec<T> configure(ConfigKey<V> key, Task<? extends V> val) { + config.put(checkNotNull(key, "key"), val); + return this; + } + + public <V> EnricherSpec<T> configure(HasConfigKey<V> key, V val) { + config.put(checkNotNull(key, "key").getConfigKey(), val); + return this; + } + + public <V> EnricherSpec<T> configure(HasConfigKey<V> key, Task<? extends V> val) { + config.put(checkNotNull(key, "key").getConfigKey(), val); + return this; + } + + /** + * @return The type of the enricher + */ + public Class<T> getType() { + return type; + } + + /** + * @return The display name of the enricher + */ + public String getDisplayName() { + return displayName; + } + + /** + * @return Read-only construction flags + * @see SetFromFlag declarations on the enricher type + */ + public Map<String, ?> getFlags() { + return Collections.unmodifiableMap(flags); + } + + /** + * @return Read-only configuration values + */ + public Map<ConfigKey<?>, Object> getConfig() { + return Collections.unmodifiableMap(config); + } + + @Override + public String toString() { + return Objects.toStringHelper(this).add("type", type).toString(); + } + + // TODO Duplicates method in EntitySpec and BasicEntityTypeRegistry + private void checkIsImplementation(Class<?> val) { + if (!Enricher.class.isAssignableFrom(val)) throw new IllegalStateException("Implementation "+val+" does not implement "+Enricher.class.getName()); + if (val.isInterface()) throw new IllegalStateException("Implementation "+val+" is an interface, but must be a non-abstract class"); + if (Modifier.isAbstract(val.getModifiers())) throw new IllegalStateException("Implementation "+val+" is abstract, but must be a non-abstract class"); + } + + // TODO Duplicates method in EntitySpec, BasicEntityTypeRegistry, and InternalEntityFactory.isNewStyleEntity + private void checkIsNewStyleImplementation(Class<?> implClazz) { + try { + implClazz.getConstructor(new Class[0]); + } catch (NoSuchMethodException e) { + throw new IllegalStateException("Implementation "+implClazz+" must have a no-argument constructor"); + } catch (SecurityException e) { + throw Exceptions.propagate(e); + } + + if (implClazz.isInterface()) throw new IllegalStateException("Implementation "+implClazz+" is an interface, but must be a non-abstract class"); + if (Modifier.isAbstract(implClazz.getModifiers())) throw new IllegalStateException("Implementation "+implClazz+" is abstract, but must be a non-abstract class"); + } +} http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/b8216c5e/api/src/main/java/brooklyn/policy/EnricherType.java ---------------------------------------------------------------------- diff --git a/api/src/main/java/brooklyn/policy/EnricherType.java b/api/src/main/java/brooklyn/policy/EnricherType.java new file mode 100644 index 0000000..43fb6ea --- /dev/null +++ b/api/src/main/java/brooklyn/policy/EnricherType.java @@ -0,0 +1,38 @@ +package brooklyn.policy; + +import java.io.Serializable; +import java.util.Set; + +import brooklyn.config.ConfigKey; + +import com.google.common.annotations.Beta; + +/** + * Gives type information for an {@link Enricher}. It is immutable. + * + * For enrichers that can support config keys etc being added on-the-fly, + * then this EnricherType will be a snapshot and subsequent snapshots will + * include the changes. + * + * @since 0.6 + */ +@Beta +public interface EnricherType extends Serializable { + + // TODO Consider merging this with PolicyType? Have a common super-type? It also has overlap with EntityType. + + /** + * The type name of this policy (normally the fully qualified class name). + */ + String getName(); + + /** + * ConfigKeys available on this policy. + */ + Set<ConfigKey<?>> getConfigKeys(); + + /** + * The ConfigKey with the given name, or null if not found. + */ + ConfigKey<?> getConfigKey(String name); +} http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/b8216c5e/core/src/main/java/brooklyn/entity/proxying/InternalEntityFactory.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/brooklyn/entity/proxying/InternalEntityFactory.java b/core/src/main/java/brooklyn/entity/proxying/InternalEntityFactory.java index b8e870a..6bc4791 100644 --- a/core/src/main/java/brooklyn/entity/proxying/InternalEntityFactory.java +++ b/core/src/main/java/brooklyn/entity/proxying/InternalEntityFactory.java @@ -16,6 +16,8 @@ import brooklyn.entity.basic.EntityInternal; import brooklyn.entity.basic.EntityLocal; import brooklyn.management.ManagementContext; import brooklyn.management.internal.ManagementContextInternal; +import brooklyn.policy.Enricher; +import brooklyn.policy.EnricherSpec; import brooklyn.policy.Policy; import brooklyn.policy.PolicySpec; import brooklyn.policy.basic.AbstractPolicy; @@ -154,6 +156,14 @@ public class InternalEntityFactory { for (EntityInitializer initializer: spec.getInitializers()) initializer.apply((EntityInternal)entity); + for (Enricher enricher : spec.getEnrichers()) { + entity.addEnricher(enricher); + } + + for (EnricherSpec<?> enricherSpec : spec.getEnricherSpecs()) { + entity.addEnricher(policyFactory.createEnricher(enricherSpec)); + } + for (Policy policy : spec.getPolicies()) { entity.addPolicy((AbstractPolicy)policy); } http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/b8216c5e/core/src/main/java/brooklyn/entity/proxying/InternalPolicyFactory.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/brooklyn/entity/proxying/InternalPolicyFactory.java b/core/src/main/java/brooklyn/entity/proxying/InternalPolicyFactory.java index c4d4358..c51144d 100644 --- a/core/src/main/java/brooklyn/entity/proxying/InternalPolicyFactory.java +++ b/core/src/main/java/brooklyn/entity/proxying/InternalPolicyFactory.java @@ -6,8 +6,11 @@ import java.lang.reflect.InvocationTargetException; import java.util.Map; import brooklyn.config.ConfigKey; +import brooklyn.enricher.basic.AbstractEnricher; import brooklyn.management.ManagementContext; import brooklyn.management.internal.ManagementContextInternal; +import brooklyn.policy.Enricher; +import brooklyn.policy.EnricherSpec; import brooklyn.policy.Policy; import brooklyn.policy.PolicySpec; import brooklyn.policy.basic.AbstractPolicy; @@ -73,7 +76,20 @@ public class InternalPolicyFactory { public static boolean isNewStylePolicy(Class<?> clazz) { if (!Policy.class.isAssignableFrom(clazz)) { - throw new IllegalArgumentException("Class "+clazz+" is not an policy"); + throw new IllegalArgumentException("Class "+clazz+" is not a policy"); + } + + try { + clazz.getConstructor(new Class[0]); + return true; + } catch (NoSuchMethodException e) { + return false; + } + } + + public static boolean isNewStyleEnricher(Class<?> clazz) { + if (!Enricher.class.isAssignableFrom(clazz)) { + throw new IllegalArgumentException("Class "+clazz+" is not an enricher"); } try { @@ -129,6 +145,47 @@ public class InternalPolicyFactory { } } + @SuppressWarnings({ "unchecked", "rawtypes" }) + public <T extends Enricher> T createEnricher(EnricherSpec<T> spec) { + if (spec.getFlags().containsKey("parent")) { + throw new IllegalArgumentException("Spec's flags must not contain parent; use spec.parent() instead for "+spec); + } + + try { + Class<? extends T> clazz = spec.getType(); + + FactoryConstructionTracker.setConstructing(); + T enricher; + try { + enricher = construct(clazz, spec); + } finally { + FactoryConstructionTracker.reset(); + } + + if (spec.getDisplayName()!=null) + ((AbstractEnricher)enricher).setName(spec.getDisplayName()); + + if (isNewStyleEnricher(clazz)) { + ((AbstractEnricher)enricher).setManagementContext(managementContext); + Map<String, Object> config = ConfigBag.newInstance().putAll(spec.getFlags()).putAll(spec.getConfig()).getAllConfig(); + ((AbstractEnricher)enricher).configure(MutableMap.copyOf(config)); // TODO AbstractEnricher.configure modifies the map + } + + // TODO Can we avoid this for "new-style policies"? Should we just trust the configure() method, + // which the user may have overridden? + // Also see InternalLocationFactory for same issue, which this code is based on. + for (Map.Entry<ConfigKey<?>, Object> entry : spec.getConfig().entrySet()) { + ((AbstractEnricher)enricher).setConfig((ConfigKey)entry.getKey(), entry.getValue()); + } + ((AbstractEnricher)enricher).init(); + + return enricher; + + } catch (Exception e) { + throw Exceptions.propagate(e); + } + } + private <T extends Policy> T construct(Class<? extends T> clazz, PolicySpec<T> spec) throws InstantiationException, IllegalAccessException, InvocationTargetException { if (isNewStylePolicy(clazz)) { return clazz.newInstance(); @@ -137,8 +194,16 @@ public class InternalPolicyFactory { } } - private <T extends Policy> T constructOldStyle(Class<? extends T> clazz, Map<String,?> flags) throws InstantiationException, IllegalAccessException, InvocationTargetException { - Optional<? extends T> v = Reflections.invokeConstructorWithArgs(clazz, new Object[] {MutableMap.copyOf(flags)}, true); + private <T extends Enricher> T construct(Class<? extends T> clazz, EnricherSpec<T> spec) throws InstantiationException, IllegalAccessException, InvocationTargetException { + if (isNewStyleEnricher(clazz)) { + return clazz.newInstance(); + } else { + return constructOldStyle(clazz, MutableMap.copyOf(spec.getFlags())); + } + } + + private <T> T constructOldStyle(Class<T> clazz, Map<String,?> flags) throws InstantiationException, IllegalAccessException, InvocationTargetException { + Optional<T> v = Reflections.invokeConstructorWithArgs(clazz, new Object[] {MutableMap.copyOf(flags)}, true); if (v.isPresent()) { return v.get(); } else { http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/b8216c5e/core/src/test/java/brooklyn/entity/basic/EntitySpecTest.java ---------------------------------------------------------------------- diff --git a/core/src/test/java/brooklyn/entity/basic/EntitySpecTest.java b/core/src/test/java/brooklyn/entity/basic/EntitySpecTest.java index 3d5f05d..19e4842 100644 --- a/core/src/test/java/brooklyn/entity/basic/EntitySpecTest.java +++ b/core/src/test/java/brooklyn/entity/basic/EntitySpecTest.java @@ -3,13 +3,20 @@ package brooklyn.entity.basic; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertTrue; +import java.util.Map; + import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; +import brooklyn.config.ConfigKey; +import brooklyn.enricher.basic.AbstractEnricher; import brooklyn.entity.proxying.EntitySpec; import brooklyn.event.basic.BasicConfigKey; import brooklyn.location.basic.SimulatedLocation; +import brooklyn.policy.Enricher; +import brooklyn.policy.EnricherSpec; +import brooklyn.policy.EnricherType; import brooklyn.policy.Policy; import brooklyn.policy.PolicySpec; import brooklyn.policy.basic.AbstractPolicy; @@ -69,9 +76,40 @@ public class EntitySpecTest { assertEquals(Iterables.getOnlyElement(entity.getPolicies()), policy); } + @Test + public void testAddsEnricherSpec() throws Exception { + entity = app.createAndManageChild(EntitySpec.create(TestEntity.class) + .enricher(EnricherSpec.create(MyEnricher.class) + .displayName("myenrichername") + .configure(MyEnricher.CONF1, "myconf1val") + .configure("myfield", "myfieldval"))); + + Enricher enricher = Iterables.getOnlyElement(entity.getEnrichers()); + assertTrue(enricher instanceof MyEnricher, "enricher="+enricher); + assertEquals(enricher.getName(), "myenrichername"); + assertEquals(enricher.getConfig(MyEnricher.CONF1), "myconf1val"); + } + + @Test + public void testAddsEnricher() throws Exception { + MyEnricher enricher = new MyEnricher(); + entity = app.createAndManageChild(EntitySpec.create(TestEntity.class) + .enricher(enricher)); + + assertEquals(Iterables.getOnlyElement(entity.getEnrichers()), enricher); + } + public static class MyPolicy extends AbstractPolicy { - public static final BasicConfigKey<String> CONF1 = new BasicConfigKey<String>(String.class, "test.conf1", "my descr, conf1", "defaultval1"); - public static final BasicConfigKey<Integer> CONF2 = new BasicConfigKey<Integer>(Integer.class, "test.conf2", "my descr, conf2", 2); + public static final BasicConfigKey<String> CONF1 = new BasicConfigKey<String>(String.class, "testpolicy.conf1", "my descr, conf1", "defaultval1"); + public static final BasicConfigKey<Integer> CONF2 = new BasicConfigKey<Integer>(Integer.class, "testpolicy.conf2", "my descr, conf2", 2); + + @SetFromFlag + public String myfield; + } + + public static class MyEnricher extends AbstractEnricher { + public static final BasicConfigKey<String> CONF1 = new BasicConfigKey<String>(String.class, "testenricher.conf1", "my descr, conf1", "defaultval1"); + public static final BasicConfigKey<Integer> CONF2 = new BasicConfigKey<Integer>(Integer.class, "testenricher.conf2", "my descr, conf2", 2); @SetFromFlag public String myfield;
