You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by ha...@apache.org on 2015/08/17 21:17:33 UTC

[02/42] incubator-brooklyn git commit: [BROOKLYN-162] Refactor package in ./core/policy

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6eacb3c3/core/src/main/java/org/apache/brooklyn/core/policy/basic/AbstractPolicy.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/policy/basic/AbstractPolicy.java b/core/src/main/java/org/apache/brooklyn/core/policy/basic/AbstractPolicy.java
new file mode 100644
index 0000000..9d809e3
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/core/policy/basic/AbstractPolicy.java
@@ -0,0 +1,119 @@
+/*
+ * 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.apache.brooklyn.core.policy.basic;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.apache.brooklyn.api.entity.rebind.RebindSupport;
+import org.apache.brooklyn.api.entity.trait.Configurable;
+import org.apache.brooklyn.api.mementos.PolicyMemento;
+import org.apache.brooklyn.api.policy.Policy;
+import org.apache.brooklyn.api.policy.PolicyType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import brooklyn.entity.rebind.BasicPolicyRebindSupport;
+
+import com.google.common.base.Objects;
+
+/**
+ * Base {@link Policy} implementation; all policies should extend this or its children
+ */
+public abstract class AbstractPolicy extends AbstractEntityAdjunct implements Policy, Configurable {
+    @SuppressWarnings("unused")
+    private static final Logger log = LoggerFactory.getLogger(AbstractPolicy.class);
+
+    protected String policyStatus;
+    protected AtomicBoolean suspended = new AtomicBoolean(false);
+
+    private final PolicyDynamicType policyType;
+    
+    public AbstractPolicy() {
+        this(Collections.emptyMap());
+    }
+    
+    public AbstractPolicy(Map<?,?> flags) {
+        super(flags);
+        
+        // TODO Don't let `this` reference escape during construction
+        policyType = new PolicyDynamicType(this);
+        
+        if (isLegacyConstruction() && !isLegacyNoConstructionInit()) {
+            init();
+        }
+    }
+
+    @Override
+    public PolicyType getPolicyType() {
+        return policyType.getSnapshot();
+    }
+
+    @Override
+    public void suspend() {
+        suspended.set(true);
+    }
+
+    @Override
+    public void resume() {
+        suspended.set(false);
+    }
+
+    @Override
+    public boolean isSuspended() {
+        if (suspended==null) {
+            // only if accessed during construction in super, e.g. by a call to toString in configure
+            return true;
+        }
+        return suspended.get();
+    }
+
+    @Override
+    public void destroy(){
+        suspend();
+        super.destroy();
+    }
+
+    @Override
+    public boolean isRunning() {
+        return !isSuspended() && !isDestroyed();
+    }
+
+    @Override
+    protected void onChanged() {
+        // currently changes simply trigger re-persistence; there is no intermediate listener as we do for EntityChangeListener
+        if (getManagementContext() != null) {
+            getManagementContext().getRebindManager().getChangeListener().onChanged(this);
+        }
+    }
+    
+    @Override
+    public RebindSupport<PolicyMemento> getRebindSupport() {
+        return new BasicPolicyRebindSupport(this);
+    }
+
+    @Override
+    public String toString() {
+        return Objects.toStringHelper(getClass())
+                .add("name", name)
+                .add("running", isRunning())
+                .toString();
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6eacb3c3/core/src/main/java/org/apache/brooklyn/core/policy/basic/AdjunctType.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/policy/basic/AdjunctType.java b/core/src/main/java/org/apache/brooklyn/core/policy/basic/AdjunctType.java
new file mode 100644
index 0000000..f3d6a6c
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/core/policy/basic/AdjunctType.java
@@ -0,0 +1,174 @@
+/*
+ * 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.apache.brooklyn.core.policy.basic;
+
+import java.io.Serializable;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.brooklyn.api.policy.EntityAdjunct;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import brooklyn.config.ConfigKey;
+import brooklyn.config.ConfigKey.HasConfigKey;
+
+import com.google.common.base.Joiner;
+import com.google.common.base.Objects;
+import com.google.common.base.Throwables;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Maps;
+
+/**
+ * This is the actual type of a policy instance at runtime.
+ */
+public class AdjunctType implements Serializable {
+    private static final long serialVersionUID = -662979234559595903L;
+
+    private static final Logger LOG = LoggerFactory.getLogger(AdjunctType.class);
+
+    private final String name;
+    private final Map<String, ConfigKey<?>> configKeys;
+    private final Set<ConfigKey<?>> configKeysSet;
+
+    public AdjunctType(AbstractEntityAdjunct adjunct) {
+        this(adjunct.getClass(), adjunct);
+    }
+    
+    protected AdjunctType(Class<? extends EntityAdjunct> clazz) {
+        this(clazz, null);
+    }
+    
+    private AdjunctType(Class<? extends EntityAdjunct> clazz, AbstractEntityAdjunct adjunct) {
+        name = clazz.getCanonicalName();
+        configKeys = Collections.unmodifiableMap(findConfigKeys(clazz, null));
+        configKeysSet = ImmutableSet.copyOf(this.configKeys.values());
+        if (LOG.isTraceEnabled())
+            LOG.trace("Policy {} config keys: {}", name, Joiner.on(", ").join(configKeys.keySet()));
+    }
+    
+    AdjunctType(String name, Map<String, ConfigKey<?>> configKeys) {
+        this.name = name;
+        this.configKeys = ImmutableMap.copyOf(configKeys);
+        this.configKeysSet = ImmutableSet.copyOf(this.configKeys.values());
+    }
+
+    public String getName() {
+        return name;
+    }
+    
+    public Set<ConfigKey<?>> getConfigKeys() {
+        return configKeysSet;
+    }
+    
+    public ConfigKey<?> getConfigKey(String name) {
+        return configKeys.get(name);
+    }
+    
+    @Override
+    public int hashCode() {
+        return Objects.hashCode(name, configKeys);
+    }
+    
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) return true;
+        if (getClass() != obj.getClass()) return false;
+        AdjunctType o = (AdjunctType) obj;
+        if (!Objects.equal(name, o.getName())) return false;
+        if (!Objects.equal(getConfigKeys(), o.getConfigKeys())) return false;
+        return true;
+    }
+    
+    @Override
+    public String toString() {
+        return Objects.toStringHelper(name)
+                .add("configKeys", configKeys)
+                .toString();
+    }
+    
+    /**
+     * Finds the config keys defined on the entity's class, statics and optionally any non-static (discouraged).
+     */
+    // TODO Remove duplication from EntityDynamicType
+    protected static Map<String,ConfigKey<?>> findConfigKeys(Class<? extends EntityAdjunct> clazz, EntityAdjunct optionalInstance) {
+        try {
+            Map<String,ConfigKey<?>> result = Maps.newLinkedHashMap();
+            Map<String,Field> configFields = Maps.newLinkedHashMap();
+            for (Field f : clazz.getFields()) {
+                boolean isConfigKey = ConfigKey.class.isAssignableFrom(f.getType());
+                if (!isConfigKey) {
+                    if (!HasConfigKey.class.isAssignableFrom(f.getType())) {
+                        // neither ConfigKey nor HasConfigKey
+                        continue;
+                    }
+                }
+                if (!Modifier.isStatic(f.getModifiers())) {
+                    // require it to be static or we have an instance
+                    LOG.warn("Discouraged use of non-static config key "+f+" defined in " + (optionalInstance!=null ? optionalInstance : clazz));
+                    if (optionalInstance==null) continue;
+                }
+                ConfigKey<?> k = isConfigKey ? (ConfigKey<?>) f.get(optionalInstance) : 
+                    ((HasConfigKey<?>)f.get(optionalInstance)).getConfigKey();
+
+                Field alternativeField = configFields.get(k.getName());
+                // Allow overriding config keys (e.g. to set default values) when there is an assignable-from relationship between classes
+                Field definitiveField = alternativeField != null ? inferSubbestField(alternativeField, f) : f;
+                boolean skip = false;
+                if (definitiveField != f) {
+                    // If they refer to the _same_ instance, just keep the one we already have
+                    if (alternativeField.get(optionalInstance) == f.get(optionalInstance)) skip = true;
+                }
+                if (skip) {
+                    //nothing
+                } else if (definitiveField == f) {
+                    result.put(k.getName(), k);
+                    configFields.put(k.getName(), f);
+                } else if (definitiveField != null) {
+                    if (LOG.isDebugEnabled()) LOG.debug("multiple definitions for config key {} on {}; preferring that in sub-class: {} to {}", new Object[] {
+                            k.getName(), optionalInstance!=null ? optionalInstance : clazz, alternativeField, f});
+                } else if (definitiveField == null) {
+                    LOG.warn("multiple definitions for config key {} on {}; preferring {} to {}", new Object[] {
+                            k.getName(), optionalInstance!=null ? optionalInstance : clazz, alternativeField, f});
+                }
+            }
+            
+            return result;
+        } catch (IllegalAccessException e) {
+            throw Throwables.propagate(e);
+        }
+    }
+    
+    /**
+     * Gets the field that is in the sub-class; or null if one field does not come from a sub-class of the other field's class
+     */
+    // TODO Remove duplication from EntityDynamicType
+    private static Field inferSubbestField(Field f1, Field f2) {
+        Class<?> c1 = f1.getDeclaringClass();
+        Class<?> c2 = f2.getDeclaringClass();
+        boolean isSuper1 = c1.isAssignableFrom(c2);
+        boolean isSuper2 = c2.isAssignableFrom(c1);
+        return (isSuper1) ? (isSuper2 ? null : f2) : (isSuper2 ? f1 : null);
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6eacb3c3/core/src/main/java/org/apache/brooklyn/core/policy/basic/ConfigMapImpl.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/policy/basic/ConfigMapImpl.java b/core/src/main/java/org/apache/brooklyn/core/policy/basic/ConfigMapImpl.java
new file mode 100644
index 0000000..5cdc279
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/core/policy/basic/ConfigMapImpl.java
@@ -0,0 +1,140 @@
+/*
+ * 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.apache.brooklyn.core.policy.basic;
+
+import static brooklyn.util.GroovyJavaMethods.elvis;
+
+import java.util.Collections;
+import java.util.Map;
+
+import org.apache.brooklyn.api.entity.basic.EntityLocal;
+import org.apache.brooklyn.api.management.ExecutionContext;
+import org.apache.brooklyn.core.util.flags.TypeCoercions;
+import org.apache.brooklyn.core.util.internal.ConfigKeySelfExtracting;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Preconditions;
+import com.google.common.base.Predicate;
+import com.google.common.collect.Maps;
+
+import brooklyn.config.ConfigKey;
+import brooklyn.config.internal.AbstractConfigMapImpl;
+import brooklyn.entity.basic.ConfigKeys;
+import brooklyn.entity.basic.EntityInternal;
+import brooklyn.entity.basic.Sanitizer;
+import brooklyn.event.basic.StructuredConfigKey;
+import brooklyn.util.guava.Maybe;
+
+public class ConfigMapImpl extends AbstractConfigMapImpl {
+
+    private static final Logger LOG = LoggerFactory.getLogger(ConfigMapImpl.class);
+
+    /** policy against which config resolution / task execution will occur */
+    private final AbstractEntityAdjunct adjunct;
+
+    /*
+     * TODO An alternative implementation approach would be to have:
+     *   setParent(Entity o, Map<ConfigKey,Object> inheritedConfig=[:])
+     * The idea is that the parent could in theory decide explicitly what in its config
+     * would be shared.
+     * I (Aled) am undecided as to whether that would be better...
+     * 
+     * (Alex) i lean toward the config key getting to make the decision
+     */
+
+    public ConfigMapImpl(AbstractEntityAdjunct adjunct) {
+        this.adjunct = Preconditions.checkNotNull(adjunct, "AbstractEntityAdjunct must be specified");
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public <T> T getConfig(ConfigKey<T> key, T defaultValue) {
+        // FIXME What about inherited task in config?!
+        //              alex says: think that should work, no?
+        // FIXME What if someone calls getConfig on a task, before setting parent app?
+        //              alex says: not supported (throw exception, or return the task)
+        
+        // In case this entity class has overridden the given key (e.g. to set default), then retrieve this entity's key
+        // TODO If ask for a config value that's not in our configKeys, should we really continue with rest of method and return key.getDefaultValue?
+        //      e.g. SshBasedJavaAppSetup calls setAttribute(JMX_USER), which calls getConfig(JMX_USER)
+        //           but that example doesn't have a default...
+        ConfigKey<T> ownKey = adjunct!=null ? (ConfigKey<T>)elvis(adjunct.getAdjunctType().getConfigKey(key.getName()), key) : key;
+        
+        // Don't use groovy truth: if the set value is e.g. 0, then would ignore set value and return default!
+        if (ownKey instanceof ConfigKeySelfExtracting) {
+            if (((ConfigKeySelfExtracting<T>)ownKey).isSet(ownConfig)) {
+                // FIXME Should we support config from futures? How to get execution context before setEntity?
+                EntityLocal entity = adjunct.entity;
+                ExecutionContext exec = (entity != null) ? ((EntityInternal)entity).getExecutionContext() : null;
+                return ((ConfigKeySelfExtracting<T>)ownKey).extractValue(ownConfig, exec);
+            }
+        } else {
+            LOG.warn("Config key {} of {} is not a ConfigKeySelfExtracting; cannot retrieve value; returning default", ownKey, this);
+        }
+        return TypeCoercions.coerce((defaultValue != null) ? defaultValue : ownKey.getDefaultValue(), key.getTypeToken());
+    }
+    
+    @Override
+    public Maybe<Object> getConfigRaw(ConfigKey<?> key, boolean includeInherited) {
+        if (ownConfig.containsKey(key)) return Maybe.of(ownConfig.get(key));
+        return Maybe.absent();
+    }
+    
+    /** returns the config of this policy */
+    @Override
+    public Map<ConfigKey<?>,Object> getAllConfig() {
+        // Don't use ImmutableMap because valide for values to be null
+        return Collections.unmodifiableMap(Maps.newLinkedHashMap(ownConfig));
+    }
+
+    public Object setConfig(ConfigKey<?> key, Object v) {
+        Object val = coerceConfigVal(key, v);
+        if (key instanceof StructuredConfigKey) {
+            return ((StructuredConfigKey)key).applyValueToMap(val, ownConfig);
+        } else {
+            return ownConfig.put(key, val);
+        }
+    }
+    
+    public void addToLocalBag(Map<String, ?> vals) {
+        for (Map.Entry<String, ?> entry : vals.entrySet()) {
+            setConfig(ConfigKeys.newConfigKey(Object.class, entry.getKey()), entry.getValue());
+        }
+    }
+
+    public void removeFromLocalBag(String key) {
+        ownConfig.remove(key);
+    }
+
+    @Override
+    public ConfigMapImpl submap(Predicate<ConfigKey<?>> filter) {
+        ConfigMapImpl m = new ConfigMapImpl(adjunct);
+        for (Map.Entry<ConfigKey<?>,Object> entry: ownConfig.entrySet())
+            if (filter.apply(entry.getKey()))
+                m.ownConfig.put(entry.getKey(), entry.getValue());
+        return m;
+    }
+
+    @Override
+    public String toString() {
+        return super.toString()+"[own="+Sanitizer.sanitize(ownConfig)+"]";
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6eacb3c3/core/src/main/java/org/apache/brooklyn/core/policy/basic/GeneralPurposePolicy.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/policy/basic/GeneralPurposePolicy.java b/core/src/main/java/org/apache/brooklyn/core/policy/basic/GeneralPurposePolicy.java
new file mode 100644
index 0000000..9d140cd
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/core/policy/basic/GeneralPurposePolicy.java
@@ -0,0 +1,36 @@
+/*
+ * 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.apache.brooklyn.core.policy.basic;
+
+import java.util.Collections;
+import java.util.Map;
+
+/**
+ * @deprecated since 0.7.0; will be either deleted or moved to tests
+ */
+@Deprecated
+public class GeneralPurposePolicy extends AbstractPolicy {
+    public GeneralPurposePolicy() {
+        this(Collections.emptyMap());
+    }
+    public GeneralPurposePolicy(Map properties) {
+        super(properties);
+    }
+}
+

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6eacb3c3/core/src/main/java/org/apache/brooklyn/core/policy/basic/Policies.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/policy/basic/Policies.java b/core/src/main/java/org/apache/brooklyn/core/policy/basic/Policies.java
new file mode 100644
index 0000000..7ac8823
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/core/policy/basic/Policies.java
@@ -0,0 +1,73 @@
+/*
+ * 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.apache.brooklyn.core.policy.basic;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.entity.basic.EntityLocal;
+import org.apache.brooklyn.api.event.Sensor;
+import org.apache.brooklyn.api.event.SensorEvent;
+import org.apache.brooklyn.api.event.SensorEventListener;
+import org.apache.brooklyn.api.policy.Policy;
+import org.apache.brooklyn.core.policy.basic.AbstractPolicy;
+
+import groovy.lang.Closure;
+import brooklyn.entity.basic.Lifecycle;
+
+@SuppressWarnings({"rawtypes","unchecked"})
+public class Policies {
+
+    public static SensorEventListener listenerFromValueClosure(final Closure code) {
+        return new SensorEventListener() {
+            @Override
+            public void onEvent(SensorEvent event) {
+                code.call(event.getValue());
+            }
+        };
+    }
+    
+    public static <T> Policy newSingleSensorValuePolicy(final Sensor<T> sensor, final Closure code) {
+        return new AbstractPolicy() {
+            @Override
+            public void setEntity(EntityLocal entity) {
+                super.setEntity(entity);
+                entity.subscribe(entity, sensor, listenerFromValueClosure(code));
+            }
+        };
+    }
+    
+    public static <S,T> Policy newSingleSensorValuePolicy(final Entity remoteEntity, final Sensor<T> remoteSensor, 
+            final Closure code) {
+        return new AbstractPolicy() {
+            @Override
+            public void setEntity(EntityLocal entity) {
+                super.setEntity(entity);
+                entity.subscribe(remoteEntity, remoteSensor, listenerFromValueClosure(code));
+            }
+        };
+    }
+
+    public static Lifecycle getPolicyStatus(Policy p) {
+        if (p.isRunning()) return Lifecycle.RUNNING;
+        if (p.isDestroyed()) return Lifecycle.DESTROYED;
+        if (p.isSuspended()) return Lifecycle.STOPPED;
+        // TODO could policy be in an error state?
+        return Lifecycle.CREATED;        
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6eacb3c3/core/src/main/java/org/apache/brooklyn/core/policy/basic/PolicyDynamicType.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/policy/basic/PolicyDynamicType.java b/core/src/main/java/org/apache/brooklyn/core/policy/basic/PolicyDynamicType.java
new file mode 100644
index 0000000..315d03d
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/core/policy/basic/PolicyDynamicType.java
@@ -0,0 +1,44 @@
+/*
+ * 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.apache.brooklyn.core.policy.basic;
+
+import org.apache.brooklyn.api.policy.Policy;
+import org.apache.brooklyn.api.policy.PolicyType;
+
+import brooklyn.basic.BrooklynDynamicType;
+
+public class PolicyDynamicType extends BrooklynDynamicType<Policy, AbstractPolicy> {
+
+    public PolicyDynamicType(Class<? extends Policy> type) {
+        super(type);
+    }
+    
+    public PolicyDynamicType(AbstractPolicy policy) {
+        super(policy);
+    }
+    
+    public PolicyType getSnapshot() {
+        return (PolicyType) super.getSnapshot();
+    }
+
+    @Override
+    protected PolicyTypeSnapshot newSnapshot() {
+        return new PolicyTypeSnapshot(name, value(configKeys));
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6eacb3c3/core/src/main/java/org/apache/brooklyn/core/policy/basic/PolicyTypeSnapshot.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/policy/basic/PolicyTypeSnapshot.java b/core/src/main/java/org/apache/brooklyn/core/policy/basic/PolicyTypeSnapshot.java
new file mode 100644
index 0000000..0e655b6
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/core/policy/basic/PolicyTypeSnapshot.java
@@ -0,0 +1,40 @@
+/*
+ * 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.apache.brooklyn.core.policy.basic;
+
+import java.util.Map;
+
+import org.apache.brooklyn.api.policy.PolicyType;
+
+import brooklyn.basic.BrooklynTypeSnapshot;
+import brooklyn.config.ConfigKey;
+
+public class PolicyTypeSnapshot extends BrooklynTypeSnapshot implements PolicyType {
+    private static final long serialVersionUID = 4670930188951106009L;
+    
+    PolicyTypeSnapshot(String name, Map<String, ConfigKey<?>> configKeys) {
+        super(name, configKeys);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) return true;
+        return (obj instanceof PolicyTypeSnapshot) && super.equals(obj);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6eacb3c3/core/src/test/java/brooklyn/entity/EntityPreManagementTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/entity/EntityPreManagementTest.java b/core/src/test/java/brooklyn/entity/EntityPreManagementTest.java
index c29682e..3fc07ed 100644
--- a/core/src/test/java/brooklyn/entity/EntityPreManagementTest.java
+++ b/core/src/test/java/brooklyn/entity/EntityPreManagementTest.java
@@ -27,6 +27,7 @@ import org.apache.brooklyn.api.event.SensorEvent;
 import org.apache.brooklyn.api.event.SensorEventListener;
 import org.apache.brooklyn.api.management.EntityManager;
 import org.apache.brooklyn.api.management.ManagementContext;
+import org.apache.brooklyn.core.policy.basic.AbstractPolicy;
 import org.apache.brooklyn.test.TestUtils;
 import org.apache.brooklyn.test.entity.LocalManagementContextForTests;
 import org.apache.brooklyn.test.entity.TestApplication;
@@ -41,7 +42,6 @@ import org.testng.annotations.Test;
 import brooklyn.entity.basic.ApplicationBuilder;
 import brooklyn.entity.basic.Attributes;
 import brooklyn.entity.basic.Entities;
-import brooklyn.policy.basic.AbstractPolicy;
 
 
 @SuppressWarnings({"rawtypes","unchecked"})

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6eacb3c3/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 80ebfb7..35a2c74 100644
--- a/core/src/test/java/brooklyn/entity/basic/EntitySpecTest.java
+++ b/core/src/test/java/brooklyn/entity/basic/EntitySpecTest.java
@@ -27,6 +27,7 @@ import org.apache.brooklyn.api.policy.Enricher;
 import org.apache.brooklyn.api.policy.EnricherSpec;
 import org.apache.brooklyn.api.policy.Policy;
 import org.apache.brooklyn.api.policy.PolicySpec;
+import org.apache.brooklyn.core.policy.basic.AbstractPolicy;
 import org.apache.brooklyn.core.util.flags.SetFromFlag;
 import org.apache.brooklyn.test.entity.TestEntity;
 import org.apache.brooklyn.test.entity.TestEntityImpl;
@@ -41,7 +42,6 @@ import brooklyn.event.basic.BasicConfigKey;
 
 import org.apache.brooklyn.location.basic.SimulatedLocation;
 
-import brooklyn.policy.basic.AbstractPolicy;
 import brooklyn.test.Asserts;
 
 import com.google.common.collect.ImmutableSet;

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6eacb3c3/core/src/test/java/brooklyn/entity/basic/PolicyRegistrationTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/entity/basic/PolicyRegistrationTest.java b/core/src/test/java/brooklyn/entity/basic/PolicyRegistrationTest.java
index 63c6a71..2f21dd8 100644
--- a/core/src/test/java/brooklyn/entity/basic/PolicyRegistrationTest.java
+++ b/core/src/test/java/brooklyn/entity/basic/PolicyRegistrationTest.java
@@ -31,6 +31,7 @@ import org.apache.brooklyn.api.event.SensorEventListener;
 import org.apache.brooklyn.api.policy.EnricherSpec;
 import org.apache.brooklyn.api.policy.Policy;
 import org.apache.brooklyn.api.policy.PolicySpec;
+import org.apache.brooklyn.core.policy.basic.AbstractPolicy;
 import org.apache.brooklyn.test.TestUtils;
 import org.apache.brooklyn.test.entity.TestEntity;
 import org.apache.brooklyn.test.entity.TestEntityNoEnrichersImpl;
@@ -38,7 +39,6 @@ import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
 import brooklyn.entity.BrooklynAppUnitTestSupport;
-import brooklyn.policy.basic.AbstractPolicy;
 import brooklyn.util.collections.MutableMap;
 
 import com.google.common.collect.ImmutableList;

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6eacb3c3/core/src/test/java/brooklyn/entity/group/GroupPickUpEntitiesTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/entity/group/GroupPickUpEntitiesTest.java b/core/src/test/java/brooklyn/entity/group/GroupPickUpEntitiesTest.java
index 0ac1ef9..8b9fa5a 100644
--- a/core/src/test/java/brooklyn/entity/group/GroupPickUpEntitiesTest.java
+++ b/core/src/test/java/brooklyn/entity/group/GroupPickUpEntitiesTest.java
@@ -25,6 +25,7 @@ import org.apache.brooklyn.api.entity.proxying.EntitySpec;
 import org.apache.brooklyn.api.event.SensorEvent;
 import org.apache.brooklyn.api.event.SensorEventListener;
 import org.apache.brooklyn.api.policy.PolicySpec;
+import org.apache.brooklyn.core.policy.basic.AbstractPolicy;
 import org.apache.brooklyn.test.EntityTestUtils;
 import org.apache.brooklyn.test.entity.TestEntity;
 import org.testng.Assert;
@@ -36,7 +37,6 @@ import brooklyn.entity.basic.BasicGroup;
 import brooklyn.entity.basic.Entities;
 import brooklyn.entity.basic.EntityInternal;
 import brooklyn.entity.trait.Startable;
-import brooklyn.policy.basic.AbstractPolicy;
 import brooklyn.test.Asserts;
 import brooklyn.util.javalang.Boxing;
 

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6eacb3c3/core/src/test/java/brooklyn/entity/rebind/RebindCatalogItemTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/entity/rebind/RebindCatalogItemTest.java b/core/src/test/java/brooklyn/entity/rebind/RebindCatalogItemTest.java
index 92383fc..996c656 100644
--- a/core/src/test/java/brooklyn/entity/rebind/RebindCatalogItemTest.java
+++ b/core/src/test/java/brooklyn/entity/rebind/RebindCatalogItemTest.java
@@ -44,6 +44,7 @@ import org.apache.brooklyn.core.catalog.internal.BasicBrooklynCatalog;
 import org.apache.brooklyn.core.catalog.internal.CatalogDto;
 import org.apache.brooklyn.core.internal.BrooklynFeatureEnablement;
 import org.apache.brooklyn.core.management.internal.LocalManagementContext;
+import org.apache.brooklyn.core.policy.basic.AbstractPolicy;
 import org.apache.brooklyn.test.entity.TestEntity;
 
 import brooklyn.config.BrooklynProperties;
@@ -51,8 +52,6 @@ import brooklyn.config.BrooklynServerConfig;
 
 import org.apache.brooklyn.location.basic.LocalhostMachineProvisioningLocation;
 
-import brooklyn.policy.basic.AbstractPolicy;
-
 import com.google.common.base.Joiner;
 import com.google.common.base.Throwables;
 import com.google.common.collect.ImmutableList;

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6eacb3c3/core/src/test/java/brooklyn/entity/rebind/RebindFailuresTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/entity/rebind/RebindFailuresTest.java b/core/src/test/java/brooklyn/entity/rebind/RebindFailuresTest.java
index 674d77e..ac2d4c7 100644
--- a/core/src/test/java/brooklyn/entity/rebind/RebindFailuresTest.java
+++ b/core/src/test/java/brooklyn/entity/rebind/RebindFailuresTest.java
@@ -40,6 +40,7 @@ import org.apache.brooklyn.api.policy.EnricherSpec;
 import org.apache.brooklyn.api.policy.Policy;
 import org.apache.brooklyn.api.policy.PolicySpec;
 import org.apache.brooklyn.core.management.internal.LocalManagementContext;
+import org.apache.brooklyn.core.policy.basic.AbstractPolicy;
 import org.apache.brooklyn.core.util.flags.SetFromFlag;
 import org.apache.brooklyn.test.entity.LocalManagementContextForTests;
 import org.testng.Assert;
@@ -53,7 +54,6 @@ import brooklyn.entity.basic.EntityFunctions;
 import brooklyn.entity.basic.EntityPredicates;
 import brooklyn.entity.rebind.RebindEntityTest.MyEntity;
 import brooklyn.entity.rebind.RebindEntityTest.MyEntityImpl;
-import brooklyn.policy.basic.AbstractPolicy;
 import brooklyn.util.exceptions.Exceptions;
 import brooklyn.util.os.Os;
 

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6eacb3c3/core/src/test/java/brooklyn/entity/rebind/RebindPolicyTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/entity/rebind/RebindPolicyTest.java b/core/src/test/java/brooklyn/entity/rebind/RebindPolicyTest.java
index d74a6fc..84413c2 100644
--- a/core/src/test/java/brooklyn/entity/rebind/RebindPolicyTest.java
+++ b/core/src/test/java/brooklyn/entity/rebind/RebindPolicyTest.java
@@ -33,6 +33,7 @@ import org.apache.brooklyn.api.mementos.BrooklynMementoManifest;
 import org.apache.brooklyn.api.policy.EnricherSpec;
 import org.apache.brooklyn.api.policy.Policy;
 import org.apache.brooklyn.api.policy.PolicySpec;
+import org.apache.brooklyn.core.policy.basic.AbstractPolicy;
 import org.apache.brooklyn.core.util.flags.SetFromFlag;
 import org.apache.brooklyn.test.entity.TestApplication;
 import org.apache.brooklyn.test.entity.TestEntity;
@@ -47,7 +48,6 @@ import brooklyn.entity.rebind.RebindEnricherTest.MyEnricher;
 
 import org.apache.brooklyn.location.basic.Locations;
 
-import brooklyn.policy.basic.AbstractPolicy;
 import brooklyn.test.Asserts;
 import brooklyn.util.collections.MutableMap;
 import brooklyn.util.collections.MutableSet;

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6eacb3c3/core/src/test/java/brooklyn/policy/basic/BasicPolicyTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/policy/basic/BasicPolicyTest.java b/core/src/test/java/brooklyn/policy/basic/BasicPolicyTest.java
deleted file mode 100644
index 5ac58c9..0000000
--- a/core/src/test/java/brooklyn/policy/basic/BasicPolicyTest.java
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * 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 brooklyn.policy.basic;
-
-import static org.testng.Assert.assertEquals;
-
-import java.util.Map;
-
-import org.apache.brooklyn.api.policy.PolicySpec;
-import org.apache.brooklyn.core.util.flags.SetFromFlag;
-import org.testng.annotations.Test;
-
-import brooklyn.config.ConfigKey;
-import brooklyn.entity.BrooklynAppUnitTestSupport;
-import brooklyn.event.basic.BasicConfigKey;
-import brooklyn.util.collections.MutableSet;
-
-/**
- * Test that policy can be created and accessed, by construction and by spec
- */
-public class BasicPolicyTest extends BrooklynAppUnitTestSupport {
-    
-    public static class MyPolicy extends AbstractPolicy {
-        @SetFromFlag("intKey")
-        public static final BasicConfigKey<Integer> INT_KEY = new BasicConfigKey<Integer>(Integer.class, "bkey", "b key");
-        
-        @SetFromFlag("strKey")
-        public static final ConfigKey<String> STR_KEY = new BasicConfigKey<String>(String.class, "akey", "a key");
-        public static final ConfigKey<Integer> INT_KEY_WITH_DEFAULT = new BasicConfigKey<Integer>(Integer.class, "ckey", "c key", 1);
-        public static final ConfigKey<String> STR_KEY_WITH_DEFAULT = new BasicConfigKey<String>(String.class, "strKey", "str key", "str key default");
-        
-        MyPolicy(Map<?,?> flags) {
-            super(flags);
-        }
-        
-        public MyPolicy() {
-            super();
-        }
-    }
-    
-    @Test
-    public void testAddInstance() throws Exception {
-        MyPolicy policy = new MyPolicy();
-        policy.setDisplayName("Bob");
-        policy.config().set(MyPolicy.STR_KEY, "aval");
-        policy.config().set(MyPolicy.INT_KEY, 2);
-        app.addPolicy(policy);
-        
-        assertEquals(policy.getDisplayName(), "Bob");
-        assertEquals(policy.getConfig(MyPolicy.STR_KEY), "aval");
-        assertEquals(policy.getConfig(MyPolicy.INT_KEY), (Integer)2);
-    }
-    
-    @Test
-    public void testAddSpec() throws Exception {
-        MyPolicy policy = app.addPolicy(PolicySpec.create(MyPolicy.class)
-            .displayName("Bob")
-            .configure(MyPolicy.STR_KEY, "aval").configure(MyPolicy.INT_KEY, 2));
-        
-        assertEquals(policy.getDisplayName(), "Bob");
-        assertEquals(policy.getConfig(MyPolicy.STR_KEY), "aval");
-        assertEquals(policy.getConfig(MyPolicy.INT_KEY), (Integer)2);
-    }
-        
-    @Test
-    public void testTagsFromSpec() throws Exception {
-        MyPolicy policy = app.addPolicy(PolicySpec.create(MyPolicy.class).tag(99).uniqueTag("x"));
-
-        assertEquals(policy.tags().getTags(), MutableSet.of("x", 99));
-        assertEquals(policy.getUniqueTag(), "x");
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6eacb3c3/core/src/test/java/brooklyn/policy/basic/EnricherTypeTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/policy/basic/EnricherTypeTest.java b/core/src/test/java/brooklyn/policy/basic/EnricherTypeTest.java
deleted file mode 100644
index 14773f5..0000000
--- a/core/src/test/java/brooklyn/policy/basic/EnricherTypeTest.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * 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 brooklyn.policy.basic;
-
-import static org.testng.Assert.assertEquals;
-
-import org.apache.brooklyn.api.policy.EnricherType;
-import org.testng.annotations.AfterMethod;
-import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.Test;
-
-import brooklyn.enricher.basic.AbstractEnricher;
-import brooklyn.event.basic.BasicConfigKey;
-
-import com.google.common.collect.ImmutableSet;
-
-public class EnricherTypeTest {
-    private MyEnricher enricher;
-    
-    @BeforeMethod(alwaysRun=true)
-    public void setUp() throws Exception{
-        enricher = new MyEnricher();
-    }
-
-    @AfterMethod(alwaysRun=true)
-    public void tearDown() throws Exception {
-        // nothing to tear down; no management context not started
-    }
-    
-    @Test
-    public void testGetConfig() throws Exception {
-        EnricherType enricherType = enricher.getEnricherType();
-        assertEquals(enricherType.getConfigKeys(), ImmutableSet.of(MyEnricher.CONF1, MyEnricher.CONF2, AbstractEnricher.SUPPRESS_DUPLICATES));
-        assertEquals(enricherType.getName(), MyEnricher.class.getCanonicalName());
-        assertEquals(enricherType.getConfigKey("test.conf1"), MyEnricher.CONF1);
-        assertEquals(enricherType.getConfigKey("test.conf2"), MyEnricher.CONF2);
-    }
-    
-    public static class MyEnricher extends AbstractEnricher {
-        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);
-    }
-}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6eacb3c3/core/src/test/java/brooklyn/policy/basic/PolicyConfigTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/policy/basic/PolicyConfigTest.java b/core/src/test/java/brooklyn/policy/basic/PolicyConfigTest.java
deleted file mode 100644
index b68781d..0000000
--- a/core/src/test/java/brooklyn/policy/basic/PolicyConfigTest.java
+++ /dev/null
@@ -1,202 +0,0 @@
-/*
- * 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 brooklyn.policy.basic;
-
-import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertTrue;
-import static org.testng.Assert.fail;
-
-import java.util.concurrent.Callable;
-import java.util.concurrent.CountDownLatch;
-
-import org.apache.brooklyn.test.entity.TestEntity;
-import org.testng.annotations.Test;
-
-import brooklyn.entity.BrooklynAppUnitTestSupport;
-import brooklyn.event.basic.BasicConfigKey;
-import brooklyn.event.basic.DependentConfiguration;
-import brooklyn.policy.basic.BasicPolicyTest.MyPolicy;
-import brooklyn.util.collections.MutableMap;
-import brooklyn.util.exceptions.Exceptions;
-
-import com.google.common.util.concurrent.Callables;
-
-/**
- * Test that configuration properties are usable and inherited correctly.
- */
-public class PolicyConfigTest extends BrooklynAppUnitTestSupport {
-    private static final int EARLY_RETURN_GRACE = 10;
-
-    private BasicConfigKey<String> differentKey = new BasicConfigKey<String>(String.class, "differentkey", "diffval");
-
-    @Test
-    public void testConfigFlagsPassedInAtConstructionIsAvailable() throws Exception {
-        MyPolicy policy = new MyPolicy(MutableMap.builder()
-                .put("strKey", "aval")
-                .put("intKey", 2)
-                .build());
-        app.addPolicy(policy);
-        
-        assertEquals(policy.getConfig(MyPolicy.STR_KEY), "aval");
-        assertEquals(policy.getConfig(MyPolicy.INT_KEY), (Integer)2);
-        // this is set, because key name matches annotation on STR_KEY
-        assertEquals(policy.getConfig(MyPolicy.STR_KEY_WITH_DEFAULT), "aval");
-    }
-    
-    @Test
-    public void testUnknownConfigPassedInAtConstructionIsWarnedAndIgnored() throws Exception {
-        // TODO Also assert it's warned
-        MyPolicy policy = new MyPolicy(MutableMap.builder()
-                .put(differentKey, "aval")
-                .build());
-        app.addPolicy(policy);
-        
-        assertEquals(policy.getConfig(differentKey), null);
-        assertEquals(policy.getPolicyType().getConfigKey(differentKey.getName()), null);
-    }
-    
-    @Test
-    public void testConfigPassedInAtConstructionIsAvailable() throws Exception {
-        MyPolicy policy = new MyPolicy(MutableMap.builder()
-                .put(MyPolicy.STR_KEY, "aval")
-                .put(MyPolicy.INT_KEY, 2)
-                .build());
-        app.addPolicy(policy);
-        
-        assertEquals(policy.getConfig(MyPolicy.STR_KEY), "aval");
-        assertEquals(policy.getConfig(MyPolicy.INT_KEY), (Integer)2);
-        // this is not set (contrast with above)
-        assertEquals(policy.getConfig(MyPolicy.STR_KEY_WITH_DEFAULT), MyPolicy.STR_KEY_WITH_DEFAULT.getDefaultValue());
-    }
-    
-    @Test
-    public void testConfigSetToGroovyTruthFalseIsAvailable() throws Exception {
-        MyPolicy policy = new MyPolicy(MutableMap.builder()
-                .put(MyPolicy.INT_KEY_WITH_DEFAULT, 0)
-                .build());
-        app.addPolicy(policy);
-        
-        assertEquals(policy.getConfig(MyPolicy.INT_KEY_WITH_DEFAULT), (Integer)0);
-    }
-    
-    @Test
-    public void testConfigSetToNullIsAvailable() throws Exception {
-        MyPolicy policy = new MyPolicy(MutableMap.builder()
-                .put(MyPolicy.STR_KEY_WITH_DEFAULT, null)
-                .build());
-        app.addPolicy(policy);
-        
-        assertEquals(policy.getConfig(MyPolicy.STR_KEY_WITH_DEFAULT), null);
-    }
-    
-    @Test
-    public void testConfigCanBeSetOnPolicy() throws Exception {
-        MyPolicy policy = new MyPolicy();
-        policy.config().set(MyPolicy.STR_KEY, "aval");
-        policy.config().set(MyPolicy.INT_KEY, 2);
-        app.addPolicy(policy);
-        
-        assertEquals(policy.getConfig(MyPolicy.STR_KEY), "aval");
-        assertEquals(policy.getConfig(MyPolicy.INT_KEY), (Integer)2);
-    }
-    
-    @Test
-    public void testConfigSetterOverridesConstructorValue() throws Exception {
-        MyPolicy policy = new MyPolicy(MutableMap.builder()
-                .put(MyPolicy.STR_KEY, "aval")
-                .build());
-        policy.config().set(MyPolicy.STR_KEY, "diffval");
-        app.addPolicy(policy);
-        
-        assertEquals(policy.getConfig(MyPolicy.STR_KEY), "diffval");
-    }
-
-    @Test
-    public void testConfigCannotBeSetAfterApplicationIsStarted() throws Exception {
-        MyPolicy policy = new MyPolicy(MutableMap.builder()
-                .put(MyPolicy.STR_KEY, "origval")
-                .build());
-        app.addPolicy(policy);
-        
-        try {
-            policy.config().set(MyPolicy.STR_KEY,"newval");
-            fail();
-        } catch (UnsupportedOperationException e) {
-            // success
-        }
-        
-        assertEquals(policy.getConfig(MyPolicy.STR_KEY), "origval");
-    }
-    
-    @Test
-    public void testConfigReturnsDefaultValueIfNotSet() throws Exception {
-        MyPolicy policy = new MyPolicy();
-        app.addPolicy(policy);
-        
-        assertEquals(policy.getConfig(MyPolicy.STR_KEY_WITH_DEFAULT), "str key default");
-    }
-    
-    // FIXME Should we support this now?
-    @Test(enabled=false)
-    public void testGetFutureConfigWhenReady() throws Exception {
-        MyPolicy policy = new MyPolicy(MutableMap.builder()
-                .put(TestEntity.CONF_NAME, DependentConfiguration.whenDone(Callables.returning("aval")))
-                .build());
-        app.addPolicy(policy);
-        
-        assertEquals(policy.getConfig(TestEntity.CONF_NAME), "aval");
-    }
-    
-    // FIXME Should we support this now?
-    @Test(enabled=false)
-    public void testGetFutureConfigBlocksUntilReady() throws Exception {
-        final CountDownLatch latch = new CountDownLatch(1);
-        MyPolicy policy = new MyPolicy(MutableMap.builder()
-                .put(TestEntity.CONF_NAME, DependentConfiguration.whenDone(new Callable<String>() {
-                        public String call() {
-                            try {
-                                latch.await(); return "aval";
-                            } catch (InterruptedException e) {
-                                throw Exceptions.propagate(e);
-                            }
-                        }}))
-                .build());
-        app.addPolicy(policy);
-        
-        Thread t = new Thread(new Runnable() {
-                public void run() {
-                    try {
-                        Thread.sleep(10+EARLY_RETURN_GRACE); latch.countDown();
-                    } catch (InterruptedException e) {
-                        throw Exceptions.propagate(e);
-                    }
-                }});
-        try {
-            long starttime = System.currentTimeMillis();
-            t.start();
-            assertEquals(policy.getConfig(TestEntity.CONF_NAME), "aval");
-            long endtime = System.currentTimeMillis();
-            
-            assertTrue((endtime - starttime) >= 10, "starttime="+starttime+"; endtime="+endtime);
-            
-        } finally {
-            t.interrupt();
-        }
-    }
-}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6eacb3c3/core/src/test/java/brooklyn/policy/basic/PolicySubscriptionTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/policy/basic/PolicySubscriptionTest.java b/core/src/test/java/brooklyn/policy/basic/PolicySubscriptionTest.java
deleted file mode 100644
index e3b2dc8..0000000
--- a/core/src/test/java/brooklyn/policy/basic/PolicySubscriptionTest.java
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
- * 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 brooklyn.policy.basic;
-
-import static org.testng.Assert.assertEquals;
-
-import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.Test;
-
-import brooklyn.entity.BrooklynAppUnitTestSupport;
-
-import org.apache.brooklyn.api.entity.proxying.EntitySpec;
-import org.apache.brooklyn.api.management.SubscriptionHandle;
-import org.apache.brooklyn.entity.basic.RecordingSensorEventListener;
-import org.apache.brooklyn.test.entity.TestEntity;
-
-import brooklyn.event.basic.BasicSensorEvent;
-import org.apache.brooklyn.location.basic.SimulatedLocation;
-import brooklyn.test.Asserts;
-
-import com.google.common.collect.ImmutableList;
-
-public class PolicySubscriptionTest extends BrooklynAppUnitTestSupport {
-
-    // TODO Duplication between this and EntitySubscriptionTest
-    
-    private static final long SHORT_WAIT_MS = 100;
-    
-    private SimulatedLocation loc;
-    private TestEntity entity;
-    private TestEntity otherEntity;
-    private AbstractPolicy policy;
-    private RecordingSensorEventListener<Object> listener;
-    
-    @BeforeMethod(alwaysRun=true)
-    @Override
-    public void setUp() throws Exception {
-        super.setUp();
-        loc = app.newSimulatedLocation();
-        entity = app.createAndManageChild(EntitySpec.create(TestEntity.class));
-        otherEntity = app.createAndManageChild(EntitySpec.create(TestEntity.class));
-        listener = new RecordingSensorEventListener<>();
-        policy = new AbstractPolicy() {};
-        entity.addPolicy(policy);
-        app.start(ImmutableList.of(loc));
-    }
-
-    @Test
-    public void testSubscriptionReceivesEvents() throws Exception {
-        policy.subscribe(entity, TestEntity.SEQUENCE, listener);
-        policy.subscribe(entity, TestEntity.NAME, listener);
-        policy.subscribe(entity, TestEntity.MY_NOTIF, listener);
-        
-        otherEntity.setAttribute(TestEntity.SEQUENCE, 456);
-        entity.setAttribute(TestEntity.SEQUENCE, 123);
-        entity.setAttribute(TestEntity.NAME, "myname");
-        entity.emit(TestEntity.MY_NOTIF, 789);
-        
-        Asserts.succeedsEventually(new Runnable() {
-            @Override public void run() {
-                assertEquals(listener.getEvents(), ImmutableList.of(
-                        new BasicSensorEvent<Integer>(TestEntity.SEQUENCE, entity, 123),
-                        new BasicSensorEvent<String>(TestEntity.NAME, entity, "myname"),
-                        new BasicSensorEvent<Integer>(TestEntity.MY_NOTIF, entity, 789)));
-            }});
-    }
-    
-    @Test
-    public void testUnsubscribeRemovesAllSubscriptionsForThatEntity() throws Exception {
-        policy.subscribe(entity, TestEntity.SEQUENCE, listener);
-        policy.subscribe(entity, TestEntity.NAME, listener);
-        policy.subscribe(entity, TestEntity.MY_NOTIF, listener);
-        policy.subscribe(otherEntity, TestEntity.SEQUENCE, listener);
-        policy.unsubscribe(entity);
-        
-        entity.setAttribute(TestEntity.SEQUENCE, 123);
-        entity.setAttribute(TestEntity.NAME, "myname");
-        entity.emit(TestEntity.MY_NOTIF, 456);
-        otherEntity.setAttribute(TestEntity.SEQUENCE, 789);
-        
-        Thread.sleep(SHORT_WAIT_MS);
-        Asserts.succeedsEventually(new Runnable() {
-            @Override public void run() {
-                assertEquals(listener.getEvents(), ImmutableList.of(
-                        new BasicSensorEvent<Integer>(TestEntity.SEQUENCE, otherEntity, 789)));
-            }});
-    }
-    
-    @Test
-    public void testUnsubscribeUsingHandleStopsEvents() throws Exception {
-        SubscriptionHandle handle1 = policy.subscribe(entity, TestEntity.SEQUENCE, listener);
-        SubscriptionHandle handle2 = policy.subscribe(entity, TestEntity.NAME, listener);
-        SubscriptionHandle handle3 = policy.subscribe(otherEntity, TestEntity.SEQUENCE, listener);
-        
-        policy.unsubscribe(entity, handle2);
-        
-        entity.setAttribute(TestEntity.SEQUENCE, 123);
-        entity.setAttribute(TestEntity.NAME, "myname");
-        otherEntity.setAttribute(TestEntity.SEQUENCE, 456);
-        
-        Asserts.succeedsEventually(new Runnable() {
-            @Override public void run() {
-                assertEquals(listener.getEvents(), ImmutableList.of(
-                        new BasicSensorEvent<Integer>(TestEntity.SEQUENCE, entity, 123),
-                        new BasicSensorEvent<Integer>(TestEntity.SEQUENCE, otherEntity, 456)));
-            }});
-    }
-    
-}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6eacb3c3/core/src/test/java/brooklyn/policy/basic/PolicyTypeTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/policy/basic/PolicyTypeTest.java b/core/src/test/java/brooklyn/policy/basic/PolicyTypeTest.java
deleted file mode 100644
index cd9b6a7..0000000
--- a/core/src/test/java/brooklyn/policy/basic/PolicyTypeTest.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * 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 brooklyn.policy.basic;
-
-import static org.testng.Assert.assertEquals;
-
-import org.apache.brooklyn.api.policy.PolicyType;
-import org.testng.annotations.AfterMethod;
-import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.Test;
-
-import brooklyn.event.basic.BasicConfigKey;
-
-import com.google.common.collect.ImmutableSet;
-
-public class PolicyTypeTest {
-    private MyPolicy policy;
-    
-    @BeforeMethod(alwaysRun=true)
-    public void setUpTestEntity() throws Exception{
-        policy = new MyPolicy();
-    }
-
-    @AfterMethod(alwaysRun=true)
-    public void tearDown() throws Exception {
-        // nothing to tear down; no management context not started
-    }
-    
-    @Test
-    public void testGetConfig() throws Exception {
-        PolicyType policyType = policy.getPolicyType();
-        assertEquals(policyType.getConfigKeys(), ImmutableSet.of(MyPolicy.CONF1, MyPolicy.CONF2));
-        assertEquals(policyType.getName(), MyPolicy.class.getCanonicalName());
-        assertEquals(policyType.getConfigKey("test.conf1"), MyPolicy.CONF1);
-        assertEquals(policyType.getConfigKey("test.conf2"), MyPolicy.CONF2);
-    }
-    
-    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);
-    }
-}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6eacb3c3/core/src/test/java/brooklyn/test/policy/TestPolicy.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/test/policy/TestPolicy.java b/core/src/test/java/brooklyn/test/policy/TestPolicy.java
index 184eb4e..e30fd2c 100644
--- a/core/src/test/java/brooklyn/test/policy/TestPolicy.java
+++ b/core/src/test/java/brooklyn/test/policy/TestPolicy.java
@@ -22,12 +22,12 @@ import java.util.Collections;
 import java.util.Map;
 
 import org.apache.brooklyn.api.event.AttributeSensor;
+import org.apache.brooklyn.core.policy.basic.AbstractPolicy;
 import org.apache.brooklyn.core.util.flags.SetFromFlag;
 
 import brooklyn.config.ConfigKey;
 import brooklyn.entity.basic.ConfigKeys;
 import brooklyn.event.basic.BasicConfigKey;
-import brooklyn.policy.basic.AbstractPolicy;
 
 import com.google.common.reflect.TypeToken;
 

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6eacb3c3/core/src/test/java/org/apache/brooklyn/core/policy/basic/BasicPolicyTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/core/policy/basic/BasicPolicyTest.java b/core/src/test/java/org/apache/brooklyn/core/policy/basic/BasicPolicyTest.java
new file mode 100644
index 0000000..852e658
--- /dev/null
+++ b/core/src/test/java/org/apache/brooklyn/core/policy/basic/BasicPolicyTest.java
@@ -0,0 +1,90 @@
+/*
+ * 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.apache.brooklyn.core.policy.basic;
+
+import static org.testng.Assert.assertEquals;
+
+import java.util.Map;
+
+import org.apache.brooklyn.api.policy.PolicySpec;
+import org.apache.brooklyn.core.policy.basic.AbstractPolicy;
+import org.apache.brooklyn.core.util.flags.SetFromFlag;
+import org.testng.annotations.Test;
+
+import brooklyn.config.ConfigKey;
+import brooklyn.entity.BrooklynAppUnitTestSupport;
+import brooklyn.event.basic.BasicConfigKey;
+import brooklyn.util.collections.MutableSet;
+
+/**
+ * Test that policy can be created and accessed, by construction and by spec
+ */
+public class BasicPolicyTest extends BrooklynAppUnitTestSupport {
+    
+    public static class MyPolicy extends AbstractPolicy {
+        @SetFromFlag("intKey")
+        public static final BasicConfigKey<Integer> INT_KEY = new BasicConfigKey<Integer>(Integer.class, "bkey", "b key");
+        
+        @SetFromFlag("strKey")
+        public static final ConfigKey<String> STR_KEY = new BasicConfigKey<String>(String.class, "akey", "a key");
+        public static final ConfigKey<Integer> INT_KEY_WITH_DEFAULT = new BasicConfigKey<Integer>(Integer.class, "ckey", "c key", 1);
+        public static final ConfigKey<String> STR_KEY_WITH_DEFAULT = new BasicConfigKey<String>(String.class, "strKey", "str key", "str key default");
+        
+        MyPolicy(Map<?,?> flags) {
+            super(flags);
+        }
+        
+        public MyPolicy() {
+            super();
+        }
+    }
+    
+    @Test
+    public void testAddInstance() throws Exception {
+        MyPolicy policy = new MyPolicy();
+        policy.setDisplayName("Bob");
+        policy.config().set(MyPolicy.STR_KEY, "aval");
+        policy.config().set(MyPolicy.INT_KEY, 2);
+        app.addPolicy(policy);
+        
+        assertEquals(policy.getDisplayName(), "Bob");
+        assertEquals(policy.getConfig(MyPolicy.STR_KEY), "aval");
+        assertEquals(policy.getConfig(MyPolicy.INT_KEY), (Integer)2);
+    }
+    
+    @Test
+    public void testAddSpec() throws Exception {
+        MyPolicy policy = app.addPolicy(PolicySpec.create(MyPolicy.class)
+            .displayName("Bob")
+            .configure(MyPolicy.STR_KEY, "aval").configure(MyPolicy.INT_KEY, 2));
+        
+        assertEquals(policy.getDisplayName(), "Bob");
+        assertEquals(policy.getConfig(MyPolicy.STR_KEY), "aval");
+        assertEquals(policy.getConfig(MyPolicy.INT_KEY), (Integer)2);
+    }
+        
+    @Test
+    public void testTagsFromSpec() throws Exception {
+        MyPolicy policy = app.addPolicy(PolicySpec.create(MyPolicy.class).tag(99).uniqueTag("x"));
+
+        assertEquals(policy.tags().getTags(), MutableSet.of("x", 99));
+        assertEquals(policy.getUniqueTag(), "x");
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6eacb3c3/core/src/test/java/org/apache/brooklyn/core/policy/basic/EnricherTypeTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/core/policy/basic/EnricherTypeTest.java b/core/src/test/java/org/apache/brooklyn/core/policy/basic/EnricherTypeTest.java
new file mode 100644
index 0000000..380192d
--- /dev/null
+++ b/core/src/test/java/org/apache/brooklyn/core/policy/basic/EnricherTypeTest.java
@@ -0,0 +1,59 @@
+/*
+ * 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.apache.brooklyn.core.policy.basic;
+
+import static org.testng.Assert.assertEquals;
+
+import org.apache.brooklyn.api.policy.EnricherType;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import brooklyn.enricher.basic.AbstractEnricher;
+import brooklyn.event.basic.BasicConfigKey;
+
+import com.google.common.collect.ImmutableSet;
+
+public class EnricherTypeTest {
+    private MyEnricher enricher;
+    
+    @BeforeMethod(alwaysRun=true)
+    public void setUp() throws Exception{
+        enricher = new MyEnricher();
+    }
+
+    @AfterMethod(alwaysRun=true)
+    public void tearDown() throws Exception {
+        // nothing to tear down; no management context not started
+    }
+    
+    @Test
+    public void testGetConfig() throws Exception {
+        EnricherType enricherType = enricher.getEnricherType();
+        assertEquals(enricherType.getConfigKeys(), ImmutableSet.of(MyEnricher.CONF1, MyEnricher.CONF2, AbstractEnricher.SUPPRESS_DUPLICATES));
+        assertEquals(enricherType.getName(), MyEnricher.class.getCanonicalName());
+        assertEquals(enricherType.getConfigKey("test.conf1"), MyEnricher.CONF1);
+        assertEquals(enricherType.getConfigKey("test.conf2"), MyEnricher.CONF2);
+    }
+    
+    public static class MyEnricher extends AbstractEnricher {
+        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);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6eacb3c3/core/src/test/java/org/apache/brooklyn/core/policy/basic/PolicyConfigTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/core/policy/basic/PolicyConfigTest.java b/core/src/test/java/org/apache/brooklyn/core/policy/basic/PolicyConfigTest.java
new file mode 100644
index 0000000..295d621
--- /dev/null
+++ b/core/src/test/java/org/apache/brooklyn/core/policy/basic/PolicyConfigTest.java
@@ -0,0 +1,202 @@
+/*
+ * 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.apache.brooklyn.core.policy.basic;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.fail;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.CountDownLatch;
+
+import org.apache.brooklyn.core.policy.basic.BasicPolicyTest.MyPolicy;
+import org.apache.brooklyn.test.entity.TestEntity;
+import org.testng.annotations.Test;
+
+import brooklyn.entity.BrooklynAppUnitTestSupport;
+import brooklyn.event.basic.BasicConfigKey;
+import brooklyn.event.basic.DependentConfiguration;
+import brooklyn.util.collections.MutableMap;
+import brooklyn.util.exceptions.Exceptions;
+
+import com.google.common.util.concurrent.Callables;
+
+/**
+ * Test that configuration properties are usable and inherited correctly.
+ */
+public class PolicyConfigTest extends BrooklynAppUnitTestSupport {
+    private static final int EARLY_RETURN_GRACE = 10;
+
+    private BasicConfigKey<String> differentKey = new BasicConfigKey<String>(String.class, "differentkey", "diffval");
+
+    @Test
+    public void testConfigFlagsPassedInAtConstructionIsAvailable() throws Exception {
+        MyPolicy policy = new MyPolicy(MutableMap.builder()
+                .put("strKey", "aval")
+                .put("intKey", 2)
+                .build());
+        app.addPolicy(policy);
+        
+        assertEquals(policy.getConfig(MyPolicy.STR_KEY), "aval");
+        assertEquals(policy.getConfig(MyPolicy.INT_KEY), (Integer)2);
+        // this is set, because key name matches annotation on STR_KEY
+        assertEquals(policy.getConfig(MyPolicy.STR_KEY_WITH_DEFAULT), "aval");
+    }
+    
+    @Test
+    public void testUnknownConfigPassedInAtConstructionIsWarnedAndIgnored() throws Exception {
+        // TODO Also assert it's warned
+        MyPolicy policy = new MyPolicy(MutableMap.builder()
+                .put(differentKey, "aval")
+                .build());
+        app.addPolicy(policy);
+        
+        assertEquals(policy.getConfig(differentKey), null);
+        assertEquals(policy.getPolicyType().getConfigKey(differentKey.getName()), null);
+    }
+    
+    @Test
+    public void testConfigPassedInAtConstructionIsAvailable() throws Exception {
+        MyPolicy policy = new MyPolicy(MutableMap.builder()
+                .put(MyPolicy.STR_KEY, "aval")
+                .put(MyPolicy.INT_KEY, 2)
+                .build());
+        app.addPolicy(policy);
+        
+        assertEquals(policy.getConfig(MyPolicy.STR_KEY), "aval");
+        assertEquals(policy.getConfig(MyPolicy.INT_KEY), (Integer)2);
+        // this is not set (contrast with above)
+        assertEquals(policy.getConfig(MyPolicy.STR_KEY_WITH_DEFAULT), MyPolicy.STR_KEY_WITH_DEFAULT.getDefaultValue());
+    }
+    
+    @Test
+    public void testConfigSetToGroovyTruthFalseIsAvailable() throws Exception {
+        MyPolicy policy = new MyPolicy(MutableMap.builder()
+                .put(MyPolicy.INT_KEY_WITH_DEFAULT, 0)
+                .build());
+        app.addPolicy(policy);
+        
+        assertEquals(policy.getConfig(MyPolicy.INT_KEY_WITH_DEFAULT), (Integer)0);
+    }
+    
+    @Test
+    public void testConfigSetToNullIsAvailable() throws Exception {
+        MyPolicy policy = new MyPolicy(MutableMap.builder()
+                .put(MyPolicy.STR_KEY_WITH_DEFAULT, null)
+                .build());
+        app.addPolicy(policy);
+        
+        assertEquals(policy.getConfig(MyPolicy.STR_KEY_WITH_DEFAULT), null);
+    }
+    
+    @Test
+    public void testConfigCanBeSetOnPolicy() throws Exception {
+        MyPolicy policy = new MyPolicy();
+        policy.config().set(MyPolicy.STR_KEY, "aval");
+        policy.config().set(MyPolicy.INT_KEY, 2);
+        app.addPolicy(policy);
+        
+        assertEquals(policy.getConfig(MyPolicy.STR_KEY), "aval");
+        assertEquals(policy.getConfig(MyPolicy.INT_KEY), (Integer)2);
+    }
+    
+    @Test
+    public void testConfigSetterOverridesConstructorValue() throws Exception {
+        MyPolicy policy = new MyPolicy(MutableMap.builder()
+                .put(MyPolicy.STR_KEY, "aval")
+                .build());
+        policy.config().set(MyPolicy.STR_KEY, "diffval");
+        app.addPolicy(policy);
+        
+        assertEquals(policy.getConfig(MyPolicy.STR_KEY), "diffval");
+    }
+
+    @Test
+    public void testConfigCannotBeSetAfterApplicationIsStarted() throws Exception {
+        MyPolicy policy = new MyPolicy(MutableMap.builder()
+                .put(MyPolicy.STR_KEY, "origval")
+                .build());
+        app.addPolicy(policy);
+        
+        try {
+            policy.config().set(MyPolicy.STR_KEY,"newval");
+            fail();
+        } catch (UnsupportedOperationException e) {
+            // success
+        }
+        
+        assertEquals(policy.getConfig(MyPolicy.STR_KEY), "origval");
+    }
+    
+    @Test
+    public void testConfigReturnsDefaultValueIfNotSet() throws Exception {
+        MyPolicy policy = new MyPolicy();
+        app.addPolicy(policy);
+        
+        assertEquals(policy.getConfig(MyPolicy.STR_KEY_WITH_DEFAULT), "str key default");
+    }
+    
+    // FIXME Should we support this now?
+    @Test(enabled=false)
+    public void testGetFutureConfigWhenReady() throws Exception {
+        MyPolicy policy = new MyPolicy(MutableMap.builder()
+                .put(TestEntity.CONF_NAME, DependentConfiguration.whenDone(Callables.returning("aval")))
+                .build());
+        app.addPolicy(policy);
+        
+        assertEquals(policy.getConfig(TestEntity.CONF_NAME), "aval");
+    }
+    
+    // FIXME Should we support this now?
+    @Test(enabled=false)
+    public void testGetFutureConfigBlocksUntilReady() throws Exception {
+        final CountDownLatch latch = new CountDownLatch(1);
+        MyPolicy policy = new MyPolicy(MutableMap.builder()
+                .put(TestEntity.CONF_NAME, DependentConfiguration.whenDone(new Callable<String>() {
+                        public String call() {
+                            try {
+                                latch.await(); return "aval";
+                            } catch (InterruptedException e) {
+                                throw Exceptions.propagate(e);
+                            }
+                        }}))
+                .build());
+        app.addPolicy(policy);
+        
+        Thread t = new Thread(new Runnable() {
+                public void run() {
+                    try {
+                        Thread.sleep(10+EARLY_RETURN_GRACE); latch.countDown();
+                    } catch (InterruptedException e) {
+                        throw Exceptions.propagate(e);
+                    }
+                }});
+        try {
+            long starttime = System.currentTimeMillis();
+            t.start();
+            assertEquals(policy.getConfig(TestEntity.CONF_NAME), "aval");
+            long endtime = System.currentTimeMillis();
+            
+            assertTrue((endtime - starttime) >= 10, "starttime="+starttime+"; endtime="+endtime);
+            
+        } finally {
+            t.interrupt();
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6eacb3c3/core/src/test/java/org/apache/brooklyn/core/policy/basic/PolicySubscriptionTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/core/policy/basic/PolicySubscriptionTest.java b/core/src/test/java/org/apache/brooklyn/core/policy/basic/PolicySubscriptionTest.java
new file mode 100644
index 0000000..b775598
--- /dev/null
+++ b/core/src/test/java/org/apache/brooklyn/core/policy/basic/PolicySubscriptionTest.java
@@ -0,0 +1,128 @@
+/*
+ * 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.apache.brooklyn.core.policy.basic;
+
+import static org.testng.Assert.assertEquals;
+
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import brooklyn.entity.BrooklynAppUnitTestSupport;
+
+import org.apache.brooklyn.api.entity.proxying.EntitySpec;
+import org.apache.brooklyn.api.management.SubscriptionHandle;
+import org.apache.brooklyn.core.policy.basic.AbstractPolicy;
+import org.apache.brooklyn.entity.basic.RecordingSensorEventListener;
+import org.apache.brooklyn.test.entity.TestEntity;
+
+import brooklyn.event.basic.BasicSensorEvent;
+
+import org.apache.brooklyn.location.basic.SimulatedLocation;
+
+import brooklyn.test.Asserts;
+
+import com.google.common.collect.ImmutableList;
+
+public class PolicySubscriptionTest extends BrooklynAppUnitTestSupport {
+
+    // TODO Duplication between this and EntitySubscriptionTest
+    
+    private static final long SHORT_WAIT_MS = 100;
+    
+    private SimulatedLocation loc;
+    private TestEntity entity;
+    private TestEntity otherEntity;
+    private AbstractPolicy policy;
+    private RecordingSensorEventListener<Object> listener;
+    
+    @BeforeMethod(alwaysRun=true)
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        loc = app.newSimulatedLocation();
+        entity = app.createAndManageChild(EntitySpec.create(TestEntity.class));
+        otherEntity = app.createAndManageChild(EntitySpec.create(TestEntity.class));
+        listener = new RecordingSensorEventListener<>();
+        policy = new AbstractPolicy() {};
+        entity.addPolicy(policy);
+        app.start(ImmutableList.of(loc));
+    }
+
+    @Test
+    public void testSubscriptionReceivesEvents() throws Exception {
+        policy.subscribe(entity, TestEntity.SEQUENCE, listener);
+        policy.subscribe(entity, TestEntity.NAME, listener);
+        policy.subscribe(entity, TestEntity.MY_NOTIF, listener);
+        
+        otherEntity.setAttribute(TestEntity.SEQUENCE, 456);
+        entity.setAttribute(TestEntity.SEQUENCE, 123);
+        entity.setAttribute(TestEntity.NAME, "myname");
+        entity.emit(TestEntity.MY_NOTIF, 789);
+        
+        Asserts.succeedsEventually(new Runnable() {
+            @Override public void run() {
+                assertEquals(listener.getEvents(), ImmutableList.of(
+                        new BasicSensorEvent<Integer>(TestEntity.SEQUENCE, entity, 123),
+                        new BasicSensorEvent<String>(TestEntity.NAME, entity, "myname"),
+                        new BasicSensorEvent<Integer>(TestEntity.MY_NOTIF, entity, 789)));
+            }});
+    }
+    
+    @Test
+    public void testUnsubscribeRemovesAllSubscriptionsForThatEntity() throws Exception {
+        policy.subscribe(entity, TestEntity.SEQUENCE, listener);
+        policy.subscribe(entity, TestEntity.NAME, listener);
+        policy.subscribe(entity, TestEntity.MY_NOTIF, listener);
+        policy.subscribe(otherEntity, TestEntity.SEQUENCE, listener);
+        policy.unsubscribe(entity);
+        
+        entity.setAttribute(TestEntity.SEQUENCE, 123);
+        entity.setAttribute(TestEntity.NAME, "myname");
+        entity.emit(TestEntity.MY_NOTIF, 456);
+        otherEntity.setAttribute(TestEntity.SEQUENCE, 789);
+        
+        Thread.sleep(SHORT_WAIT_MS);
+        Asserts.succeedsEventually(new Runnable() {
+            @Override public void run() {
+                assertEquals(listener.getEvents(), ImmutableList.of(
+                        new BasicSensorEvent<Integer>(TestEntity.SEQUENCE, otherEntity, 789)));
+            }});
+    }
+    
+    @Test
+    public void testUnsubscribeUsingHandleStopsEvents() throws Exception {
+        SubscriptionHandle handle1 = policy.subscribe(entity, TestEntity.SEQUENCE, listener);
+        SubscriptionHandle handle2 = policy.subscribe(entity, TestEntity.NAME, listener);
+        SubscriptionHandle handle3 = policy.subscribe(otherEntity, TestEntity.SEQUENCE, listener);
+        
+        policy.unsubscribe(entity, handle2);
+        
+        entity.setAttribute(TestEntity.SEQUENCE, 123);
+        entity.setAttribute(TestEntity.NAME, "myname");
+        otherEntity.setAttribute(TestEntity.SEQUENCE, 456);
+        
+        Asserts.succeedsEventually(new Runnable() {
+            @Override public void run() {
+                assertEquals(listener.getEvents(), ImmutableList.of(
+                        new BasicSensorEvent<Integer>(TestEntity.SEQUENCE, entity, 123),
+                        new BasicSensorEvent<Integer>(TestEntity.SEQUENCE, otherEntity, 456)));
+            }});
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6eacb3c3/core/src/test/java/org/apache/brooklyn/core/policy/basic/PolicyTypeTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/core/policy/basic/PolicyTypeTest.java b/core/src/test/java/org/apache/brooklyn/core/policy/basic/PolicyTypeTest.java
new file mode 100644
index 0000000..40c4df3
--- /dev/null
+++ b/core/src/test/java/org/apache/brooklyn/core/policy/basic/PolicyTypeTest.java
@@ -0,0 +1,59 @@
+/*
+ * 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.apache.brooklyn.core.policy.basic;
+
+import static org.testng.Assert.assertEquals;
+
+import org.apache.brooklyn.api.policy.PolicyType;
+import org.apache.brooklyn.core.policy.basic.AbstractPolicy;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import brooklyn.event.basic.BasicConfigKey;
+
+import com.google.common.collect.ImmutableSet;
+
+public class PolicyTypeTest {
+    private MyPolicy policy;
+    
+    @BeforeMethod(alwaysRun=true)
+    public void setUpTestEntity() throws Exception{
+        policy = new MyPolicy();
+    }
+
+    @AfterMethod(alwaysRun=true)
+    public void tearDown() throws Exception {
+        // nothing to tear down; no management context not started
+    }
+    
+    @Test
+    public void testGetConfig() throws Exception {
+        PolicyType policyType = policy.getPolicyType();
+        assertEquals(policyType.getConfigKeys(), ImmutableSet.of(MyPolicy.CONF1, MyPolicy.CONF2));
+        assertEquals(policyType.getName(), MyPolicy.class.getCanonicalName());
+        assertEquals(policyType.getConfigKey("test.conf1"), MyPolicy.CONF1);
+        assertEquals(policyType.getConfigKey("test.conf2"), MyPolicy.CONF2);
+    }
+    
+    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);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6eacb3c3/locations/jclouds/src/main/java/brooklyn/policy/os/AdvertiseWinrmLoginPolicy.java
----------------------------------------------------------------------
diff --git a/locations/jclouds/src/main/java/brooklyn/policy/os/AdvertiseWinrmLoginPolicy.java b/locations/jclouds/src/main/java/brooklyn/policy/os/AdvertiseWinrmLoginPolicy.java
index 266f738..f5257e5 100644
--- a/locations/jclouds/src/main/java/brooklyn/policy/os/AdvertiseWinrmLoginPolicy.java
+++ b/locations/jclouds/src/main/java/brooklyn/policy/os/AdvertiseWinrmLoginPolicy.java
@@ -24,6 +24,7 @@ import org.apache.brooklyn.api.event.AttributeSensor;
 import org.apache.brooklyn.api.event.SensorEvent;
 import org.apache.brooklyn.api.event.SensorEventListener;
 import org.apache.brooklyn.api.location.Location;
+import org.apache.brooklyn.core.policy.basic.AbstractPolicy;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -32,8 +33,6 @@ import brooklyn.event.basic.Sensors;
 
 import org.apache.brooklyn.location.basic.WinRmMachineLocation;
 
-import brooklyn.policy.basic.AbstractPolicy;
-
 import com.google.common.annotations.Beta;
 
 /**

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6eacb3c3/locations/jclouds/src/main/java/brooklyn/policy/os/CreateUserPolicy.java
----------------------------------------------------------------------
diff --git a/locations/jclouds/src/main/java/brooklyn/policy/os/CreateUserPolicy.java b/locations/jclouds/src/main/java/brooklyn/policy/os/CreateUserPolicy.java
index 8327cf1..dd33058 100644
--- a/locations/jclouds/src/main/java/brooklyn/policy/os/CreateUserPolicy.java
+++ b/locations/jclouds/src/main/java/brooklyn/policy/os/CreateUserPolicy.java
@@ -26,6 +26,7 @@ import org.apache.brooklyn.api.event.AttributeSensor;
 import org.apache.brooklyn.api.event.SensorEvent;
 import org.apache.brooklyn.api.event.SensorEventListener;
 import org.apache.brooklyn.api.location.Location;
+import org.apache.brooklyn.core.policy.basic.AbstractPolicy;
 import org.apache.brooklyn.core.util.flags.SetFromFlag;
 import org.apache.brooklyn.core.util.internal.ssh.SshTool;
 import org.jclouds.compute.config.AdminAccessConfiguration;
@@ -43,7 +44,6 @@ import brooklyn.event.basic.Sensors;
 
 import org.apache.brooklyn.location.basic.SshMachineLocation;
 
-import brooklyn.policy.basic.AbstractPolicy;
 import brooklyn.util.text.Identifiers;
 
 import com.google.common.annotations.Beta;