You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by he...@apache.org on 2014/08/06 23:16:55 UTC

[03/11] git commit: add tags to BrooklynObject and to specs, with tests that they get set and can be queried for all BO subtypes; other cleanup to testing and deprection

add tags to BrooklynObject and to specs, with tests that they get set and can be queried for all BO subtypes; other cleanup to testing and deprection


Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/72b52e1d
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/72b52e1d
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/72b52e1d

Branch: refs/heads/master
Commit: 72b52e1d0db028caa654c6169ec829d0bed7af9e
Parents: 4bbf3b4
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Fri Aug 1 17:58:49 2014 -0400
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Tue Aug 5 10:40:41 2014 -0400

----------------------------------------------------------------------
 .../basic/AbstractBrooklynObjectSpec.java       |  20 ++
 .../java/brooklyn/basic/BrooklynObject.java     |  14 ++
 .../brooklyn/entity/proxying/EntitySpec.java    |   1 +
 .../main/java/brooklyn/policy/EnricherSpec.java |   5 +
 .../java/brooklyn/policy/EntityAdjunct.java     |  31 ++-
 .../main/java/brooklyn/policy/PolicySpec.java   |   5 +
 .../brooklyn/basic/AbstractBrooklynObject.java  |  53 +++++
 .../enricher/basic/AbstractEnricher.java        |   5 +-
 .../brooklyn/entity/basic/AbstractEntity.java   |  37 +---
 .../entity/proxying/InternalEntityFactory.java  |   3 +
 .../proxying/InternalLocationFactory.java       |   4 +-
 .../entity/proxying/InternalPolicyFactory.java  |   4 +
 .../rebind/BasicLocationRebindSupport.java      |   2 +-
 .../location/basic/AbstractLocation.java        |  49 ++--
 .../policy/basic/AbstractEntityAdjunct.java     |  26 ++-
 .../brooklyn/policy/basic/AbstractPolicy.java   |   8 +-
 .../enricher/basic/BasicEnricherTest.java       |  93 ++++++++
 .../enricher/basic/EnricherConfigTest.java      | 148 +++++++++++++
 .../brooklyn/entity/basic/EntitiesTest.java     |   6 +-
 .../location/basic/AbstractLocationTest.java    |   9 +-
 .../location/basic/LocationConfigTest.java      |   3 +
 .../brooklyn/policy/basic/BasicPolicyTest.java  |  89 ++++++++
 .../policy/basic/EnricherConfigTest.java        | 169 --------------
 .../policy/basic/PolicyConfigMapUsageTest.java  | 222 -------------------
 .../brooklyn/policy/basic/PolicyConfigTest.java | 202 +++++++++++++++++
 .../BrooklynAssemblyTemplateInstantiator.java   |   1 +
 .../java/brooklyn/util/guava/TypeTokens.java    |  19 ++
 27 files changed, 776 insertions(+), 452 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/72b52e1d/api/src/main/java/brooklyn/basic/AbstractBrooklynObjectSpec.java
----------------------------------------------------------------------
diff --git a/api/src/main/java/brooklyn/basic/AbstractBrooklynObjectSpec.java b/api/src/main/java/brooklyn/basic/AbstractBrooklynObjectSpec.java
index d61e016..dc6642f 100644
--- a/api/src/main/java/brooklyn/basic/AbstractBrooklynObjectSpec.java
+++ b/api/src/main/java/brooklyn/basic/AbstractBrooklynObjectSpec.java
@@ -20,10 +20,14 @@ package brooklyn.basic;
 
 import java.io.Serializable;
 import java.lang.reflect.Modifier;
+import java.util.Set;
 
+import brooklyn.util.collections.MutableSet;
 import brooklyn.util.exceptions.Exceptions;
 
 import com.google.common.base.Objects;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
 
 public abstract class AbstractBrooklynObjectSpec<T,K extends AbstractBrooklynObjectSpec<T,K>> implements Serializable {
 
@@ -31,6 +35,7 @@ public abstract class AbstractBrooklynObjectSpec<T,K extends AbstractBrooklynObj
     
     private final Class<T> type;
     private String displayName;
+    private Set<Object> tags = MutableSet.of();
 
     protected AbstractBrooklynObjectSpec(Class<T> type) {
         checkValidType(type);
@@ -54,6 +59,17 @@ public abstract class AbstractBrooklynObjectSpec<T,K extends AbstractBrooklynObj
         return self();
     }
     
+    public K tag(Object tag) {
+        tags.add(tag);
+        return self();
+    }
+
+    /** adds the given tags */
+    public K tags(Iterable<Object> tagsToAdd) {
+        Iterables.addAll(this.tags, tagsToAdd);
+        return self();
+    }
+
     /**
      * @return The type of the enricher
      */
@@ -68,6 +84,10 @@ public abstract class AbstractBrooklynObjectSpec<T,K extends AbstractBrooklynObj
         return displayName;
     }
 
+    public final Set<Object> getTags() {
+        return ImmutableSet.copyOf(tags);
+    }
+
     // TODO Duplicates method in BasicEntityTypeRegistry and InternalEntityFactory.isNewStyleEntity
     protected final void checkIsNewStyleImplementation(Class<?> implClazz) {
         try {

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/72b52e1d/api/src/main/java/brooklyn/basic/BrooklynObject.java
----------------------------------------------------------------------
diff --git a/api/src/main/java/brooklyn/basic/BrooklynObject.java b/api/src/main/java/brooklyn/basic/BrooklynObject.java
index 9e22f96..dd26528 100644
--- a/api/src/main/java/brooklyn/basic/BrooklynObject.java
+++ b/api/src/main/java/brooklyn/basic/BrooklynObject.java
@@ -18,14 +18,28 @@
  */
 package brooklyn.basic;
 
+import java.util.Set;
+
+import javax.annotation.Nonnull;
+
 import brooklyn.entity.trait.Identifiable;
 
 /**
  * Super-type of entity, location, policy and enricher.
  */
 public interface BrooklynObject extends Identifiable {
+    
     /**
      * A display name; recommended to be a concise single-line description.
      */
     String getDisplayName();
+    
+    /**
+     * A set of tags associated to this adjunct.
+     */
+    @Nonnull Set<Object> getTags();
+
+    /** whether the given object is contained as a tag */
+    boolean containsTag(Object tag);
+    
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/72b52e1d/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 daaa6c8..7410266 100644
--- a/api/src/main/java/brooklyn/entity/proxying/EntitySpec.java
+++ b/api/src/main/java/brooklyn/entity/proxying/EntitySpec.java
@@ -106,6 +106,7 @@ public class EntitySpec<T extends Entity> extends AbstractBrooklynObjectSpec<T,E
     public static <T extends Entity> EntitySpec<T> create(EntitySpec<T> spec) {
         EntitySpec<T> result = create(spec.getType())
                 .displayName(spec.getDisplayName())
+                .tags(spec.getTags())
                 .additionalInterfaces(spec.getAdditionalInterfaces())
                 .configure(spec.getConfig())
                 .configure(spec.getFlags())

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/72b52e1d/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
index 43f1a85..f2cfdd4 100644
--- a/api/src/main/java/brooklyn/policy/EnricherSpec.java
+++ b/api/src/main/java/brooklyn/policy/EnricherSpec.java
@@ -84,6 +84,11 @@ public class EnricherSpec<T extends Enricher> extends AbstractBrooklynObjectSpec
         checkIsNewStyleImplementation(type);
     }
     
+    public EnricherSpec<T> uniqueTag(String uniqueTag) {
+        flags.put("uniqueTag", uniqueTag);
+        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");

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/72b52e1d/api/src/main/java/brooklyn/policy/EntityAdjunct.java
----------------------------------------------------------------------
diff --git a/api/src/main/java/brooklyn/policy/EntityAdjunct.java b/api/src/main/java/brooklyn/policy/EntityAdjunct.java
index fa05a2a..97acc08 100644
--- a/api/src/main/java/brooklyn/policy/EntityAdjunct.java
+++ b/api/src/main/java/brooklyn/policy/EntityAdjunct.java
@@ -18,15 +18,20 @@
  */
 package brooklyn.policy;
 
+import java.util.Set;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
 import brooklyn.basic.BrooklynObject;
 
 /**
- * EntityAdjuncts are supplementary logic that can be attached to Entities, providing sensor enrichment
- * or enabling policy
+ * EntityAdjuncts are supplementary logic that can be attached to Entities, 
+ * such as providing sensor enrichment or event-driven policy behavior
  */
 public interface EntityAdjunct extends BrooklynObject {
     /**
-     * A unique id for this adjunct
+     * A unique id for this adjunct, typically created by the system with no meaning
      */
     @Override
     String getId();
@@ -43,7 +48,25 @@ public interface EntityAdjunct extends BrooklynObject {
     boolean isDestroyed();
     
     /**
-     * Whether the adjunct is available
+     * Whether the adjunct is available/active
      */
     boolean isRunning();
+    
+    /**
+     * An optional tag used to identify adjuncts with a specific purpose, typically created by the caller.
+     * This is used to prevent multiple instances with the same purpose from being created,
+     * and to access and customize adjuncts so created.
+     * <p>
+     * This will be included in the call to {@link #getTags()}.
+     */
+    @Nullable String getUniqueTag();
+    
+    /**
+     * A set of tags associated to this adjunct.
+     * <p>
+     * This will include {@link #getUniqueTag()} if that is set.
+     */
+    @Override
+    @Nonnull Set<Object> getTags();
+
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/72b52e1d/api/src/main/java/brooklyn/policy/PolicySpec.java
----------------------------------------------------------------------
diff --git a/api/src/main/java/brooklyn/policy/PolicySpec.java b/api/src/main/java/brooklyn/policy/PolicySpec.java
index ccce6e0..13206cf 100644
--- a/api/src/main/java/brooklyn/policy/PolicySpec.java
+++ b/api/src/main/java/brooklyn/policy/PolicySpec.java
@@ -84,6 +84,11 @@ public class PolicySpec<T extends Policy> extends AbstractBrooklynObjectSpec<T,P
         checkIsNewStyleImplementation(type);
     }
     
+    public PolicySpec<T> uniqueTag(String uniqueTag) {
+        flags.put("uniqueTag", uniqueTag);
+        return this;
+    }
+
     public PolicySpec<T> configure(Map<?,?> val) {
         for (Map.Entry<?, ?> entry: val.entrySet()) {
             if (entry.getKey()==null) throw new NullPointerException("Null key not permitted");

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/72b52e1d/core/src/main/java/brooklyn/basic/AbstractBrooklynObject.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/basic/AbstractBrooklynObject.java b/core/src/main/java/brooklyn/basic/AbstractBrooklynObject.java
index b64db51..e977fec 100644
--- a/core/src/main/java/brooklyn/basic/AbstractBrooklynObject.java
+++ b/core/src/main/java/brooklyn/basic/AbstractBrooklynObject.java
@@ -18,16 +18,69 @@
  */
 package brooklyn.basic;
 
+import java.util.Set;
+
 import brooklyn.util.flags.SetFromFlag;
 import brooklyn.util.text.Identifiers;
 
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Sets;
+
 public abstract class AbstractBrooklynObject implements BrooklynObjectInternal {
 
     @SetFromFlag(value="id")
     private String id = Identifiers.makeRandomId(8);
     
+    /** subclasses should synchronize on this for all access */
+    @SetFromFlag(value="tags")
+    protected Set<Object> tags = Sets.newLinkedHashSet();
+    
     @Override
     public String getId() {
         return id;
     }
+    
+    @Override
+    public Set<Object> getTags() {
+        synchronized (tags) {
+            return ImmutableSet.copyOf(tags);
+        }
+    }
+
+    public boolean addTag(Object tag) {
+        boolean result;
+        synchronized (tags) {
+            result = tags.add(tag);
+        }
+        onTagsChanged();
+        return result;
+    }    
+
+    public boolean addTags(Iterable<Object> tags) {
+        boolean result;
+        synchronized (tags) {
+            result = Iterables.addAll(this.tags, tags);
+        }
+        onTagsChanged();
+        return result;
+    }    
+
+    public boolean removeTag(Object tag) {
+        boolean result;
+        synchronized (tags) {
+            result = tags.remove(tag);
+        }
+        onTagsChanged();
+        return result;
+    }    
+
+    public boolean containsTag(Object tag) {
+        synchronized (tags) {
+            return tags.contains(tag);
+        }
+    }    
+
+    protected abstract void onTagsChanged();
+    
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/72b52e1d/core/src/main/java/brooklyn/enricher/basic/AbstractEnricher.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/enricher/basic/AbstractEnricher.java b/core/src/main/java/brooklyn/enricher/basic/AbstractEnricher.java
index 6368866..df6a370 100644
--- a/core/src/main/java/brooklyn/enricher/basic/AbstractEnricher.java
+++ b/core/src/main/java/brooklyn/enricher/basic/AbstractEnricher.java
@@ -41,7 +41,7 @@ public abstract class AbstractEnricher extends AbstractEntityAdjunct implements
         this(Maps.newLinkedHashMap());
     }
     
-    public AbstractEnricher(Map flags) {
+    public AbstractEnricher(Map<?,?> flags) {
         super(flags);
         enricherType = new EnricherTypeImpl(getAdjunctType());
         
@@ -62,9 +62,10 @@ public abstract class AbstractEnricher extends AbstractEntityAdjunct implements
 
     @Override
     protected void onChanged() {
-        // TODO Could add EnricherChangeListener, similar to EntityChangeListener; should we do that?
+        // currently changes simply trigger re-persistence; there is no intermediate listener as we do for EntityChangeListener
         if (getManagementContext() != null) {
             getManagementContext().getRebindManager().getChangeListener().onChanged(this);
         }
     }
+    
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/72b52e1d/core/src/main/java/brooklyn/entity/basic/AbstractEntity.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/basic/AbstractEntity.java b/core/src/main/java/brooklyn/entity/basic/AbstractEntity.java
index 9ebb62f..19bbea1 100644
--- a/core/src/main/java/brooklyn/entity/basic/AbstractEntity.java
+++ b/core/src/main/java/brooklyn/entity/basic/AbstractEntity.java
@@ -34,7 +34,6 @@ import brooklyn.basic.AbstractBrooklynObject;
 import brooklyn.config.ConfigKey;
 import brooklyn.config.ConfigKey.HasConfigKey;
 import brooklyn.enricher.basic.AbstractEnricher;
-import brooklyn.enricher.basic.Aggregator;
 import brooklyn.entity.Application;
 import brooklyn.entity.Effector;
 import brooklyn.entity.Entity;
@@ -71,7 +70,6 @@ import brooklyn.management.internal.SubscriptionTracker;
 import brooklyn.mementos.EntityMemento;
 import brooklyn.policy.Enricher;
 import brooklyn.policy.EnricherSpec;
-import brooklyn.policy.EntityAdjunct;
 import brooklyn.policy.Policy;
 import brooklyn.policy.PolicySpec;
 import brooklyn.policy.basic.AbstractEntityAdjunct;
@@ -91,7 +89,6 @@ import com.google.common.base.Objects;
 import com.google.common.base.Objects.ToStringHelper;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
@@ -175,7 +172,6 @@ public abstract class AbstractEntity extends AbstractBrooklynObject implements E
     Map<String,Object> presentationAttributes = Maps.newLinkedHashMap();
     Collection<AbstractPolicy> policies = Lists.newCopyOnWriteArrayList();
     Collection<AbstractEnricher> enrichers = Lists.newCopyOnWriteArrayList();
-    Set<Object> tags = Sets.newLinkedHashSet();
 
     // FIXME we do not currently support changing parents, but to implement a cluster that can shrink we need to support at least
     // orphaning (i.e. removing ownership). This flag notes if the entity has previously had a parent, and if an attempt is made to
@@ -1330,38 +1326,9 @@ public abstract class AbstractEntity extends AbstractBrooklynObject implements E
     }
 
     @Override
-    public Set<Object> getTags() {
-        synchronized (tags) {
-            return ImmutableSet.copyOf(tags);
-        }
-    }
-
-    @Override
-    public boolean addTag(Object tag) {
-        boolean result;
-        synchronized (tags) {
-            result = tags.add(tag);
-        }
+    protected void onTagsChanged() {
         getManagementSupport().getEntityChangeListener().onTagsChanged();
-        return result;
-    }    
-
-    @Override
-    public boolean removeTag(Object tag) {
-        boolean result;
-        synchronized (tags) {
-            result = tags.remove(tag);
-        }
-        getManagementSupport().getEntityChangeListener().onTagsChanged();
-        return result;
-    }    
-
-    @Override
-    public boolean containsTag(Object tag) {
-        synchronized (tags) {
-            return tags.contains(tag);
-        }
-    }    
+    }
     
     @Override
     protected void finalize() throws Throwable {

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/72b52e1d/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 b5d7c0a..9cc05c3 100644
--- a/core/src/main/java/brooklyn/entity/proxying/InternalEntityFactory.java
+++ b/core/src/main/java/brooklyn/entity/proxying/InternalEntityFactory.java
@@ -169,6 +169,7 @@ public class InternalEntityFactory extends InternalFactory {
         return entity;
     }
     
+    @SuppressWarnings("deprecation")
     protected <T extends Entity> T createEntityAndDescendantsUninitialized(EntitySpec<T> spec, Map<String,Entity> entitiesByEntityId, Map<String,EntitySpec<?>> specsByEntityId) {
         if (spec.getFlags().containsKey("parent") || spec.getFlags().containsKey("owner")) {
             throw new IllegalArgumentException("Spec's flags must not contain parent or owner; use spec.parent() instead for "+spec);
@@ -230,6 +231,7 @@ public class InternalEntityFactory extends InternalFactory {
             if (spec.getDisplayName()!=null)
                 ((AbstractEntity)entity).setDisplayName(spec.getDisplayName());
             
+            ((AbstractEntity)entity).addTags(spec.getTags());
             ((AbstractEntity)entity).configure(MutableMap.copyOf(spec.getFlags()));
             
             for (Map.Entry<ConfigKey<?>, Object> entry : spec.getConfig().entrySet()) {
@@ -320,6 +322,7 @@ public class InternalEntityFactory extends InternalFactory {
      * but that behaviour is deprecated.
      */
     public <T extends Entity> T constructEntity(Class<? extends T> clazz, EntitySpec<T> spec) {
+        @SuppressWarnings("deprecation")
         T entity = constructEntityImpl(clazz, spec.getFlags(), spec.getId());
         if (((AbstractEntity)entity).getProxy() == null) ((AbstractEntity)entity).setProxy(createEntityProxy(spec, entity));
         return entity;

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/72b52e1d/core/src/main/java/brooklyn/entity/proxying/InternalLocationFactory.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/proxying/InternalLocationFactory.java b/core/src/main/java/brooklyn/entity/proxying/InternalLocationFactory.java
index 804b5d5..6a7b599 100644
--- a/core/src/main/java/brooklyn/entity/proxying/InternalLocationFactory.java
+++ b/core/src/main/java/brooklyn/entity/proxying/InternalLocationFactory.java
@@ -95,7 +95,9 @@ public class InternalLocationFactory extends InternalFactory {
             managementContext.prePreManage(loc);
 
             if (spec.getDisplayName()!=null)
-                ((AbstractLocation)loc).setName(spec.getDisplayName());
+                ((AbstractLocation)loc).setDisplayName(spec.getDisplayName());
+            
+            ((AbstractLocation)loc).addTags(spec.getTags());
             
             if (isNewStyleLocation(clazz)) {
                 ((AbstractLocation)loc).setManagementContext(managementContext);

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/72b52e1d/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 ebf13dd..76dd312 100644
--- a/core/src/main/java/brooklyn/entity/proxying/InternalPolicyFactory.java
+++ b/core/src/main/java/brooklyn/entity/proxying/InternalPolicyFactory.java
@@ -94,6 +94,8 @@ public class InternalPolicyFactory extends InternalFactory {
             if (spec.getDisplayName()!=null)
                 ((AbstractPolicy)pol).setDisplayName(spec.getDisplayName());
             
+            ((AbstractPolicy)pol).addTags(spec.getTags());
+            
             if (isNewStylePolicy(clazz)) {
                 ((AbstractPolicy)pol).setManagementContext(managementContext);
                 Map<String, Object> config = ConfigBag.newInstance().putAll(spec.getFlags()).putAll(spec.getConfig()).getAllConfig();
@@ -129,6 +131,8 @@ public class InternalPolicyFactory extends InternalFactory {
             if (spec.getDisplayName()!=null)
                 ((AbstractEnricher)enricher).setDisplayName(spec.getDisplayName());
             
+            ((AbstractEnricher)enricher).addTags(spec.getTags());
+            
             if (isNewStyleEnricher(clazz)) {
                 ((AbstractEnricher)enricher).setManagementContext(managementContext);
                 Map<String, Object> config = ConfigBag.newInstance().putAll(spec.getFlags()).putAll(spec.getConfig()).getAllConfig();

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/72b52e1d/core/src/main/java/brooklyn/entity/rebind/BasicLocationRebindSupport.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/rebind/BasicLocationRebindSupport.java b/core/src/main/java/brooklyn/entity/rebind/BasicLocationRebindSupport.java
index 7e930ef..157ccca 100644
--- a/core/src/main/java/brooklyn/entity/rebind/BasicLocationRebindSupport.java
+++ b/core/src/main/java/brooklyn/entity/rebind/BasicLocationRebindSupport.java
@@ -69,7 +69,7 @@ public class BasicLocationRebindSupport implements RebindSupport<LocationMemento
     	
     	// Note that the flags have been set in the constructor
         // FIXME Relies on location.getLocalConfigBag being mutable (to modify the location's own config)
-        location.setName(memento.getDisplayName());
+        location.setDisplayName(memento.getDisplayName());
         
         location.getLocalConfigBag().putAll(memento.getLocationConfig()).markAll(
                 Sets.difference(memento.getLocationConfig().keySet(), memento.getLocationConfigUnused())).

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/72b52e1d/core/src/main/java/brooklyn/location/basic/AbstractLocation.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/location/basic/AbstractLocation.java b/core/src/main/java/brooklyn/location/basic/AbstractLocation.java
index 651a46b..e517d10 100644
--- a/core/src/main/java/brooklyn/location/basic/AbstractLocation.java
+++ b/core/src/main/java/brooklyn/location/basic/AbstractLocation.java
@@ -57,6 +57,7 @@ import brooklyn.util.collections.SetFromLiveMap;
 import brooklyn.util.config.ConfigBag;
 import brooklyn.util.flags.FlagUtils;
 import brooklyn.util.flags.TypeCoercions;
+import brooklyn.util.guava.TypeTokens;
 import brooklyn.util.stream.Streams;
 
 import com.google.common.base.Objects;
@@ -78,6 +79,8 @@ import com.google.common.collect.Sets;
  */
 public abstract class AbstractLocation extends AbstractBrooklynObject implements LocationInternal, HasHostGeoInfo, Configurable {
     
+    private static final long serialVersionUID = -7495805474138619830L;
+
     /** @deprecated since 0.7.0 shouldn't be public */
     @Deprecated
     public static final Logger LOG = LoggerFactory.getLogger(AbstractLocation.class);
@@ -138,7 +141,8 @@ public abstract class AbstractLocation extends AbstractBrooklynObject implements
      * <li>abbreviatedName
      * </ul>
      */
-    public AbstractLocation(Map properties) {
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    public AbstractLocation(Map<?,?> properties) {
         inConstruction = true;
         _legacyConstruction = !InternalFactory.FactoryConstructionTracker.isConstructing();
         if (!_legacyConstruction && properties!=null && !properties.isEmpty()) {
@@ -233,8 +237,8 @@ public abstract class AbstractLocation extends AbstractBrooklynObject implements
      * in this class, so initializers in subclasses will not have run when this overridden 
      * method is invoked.) If you require fields to be initialized you must do that in 
      * this method with a guard (as in FixedListMachineProvisioningLocation).
-     */ 
-    public void configure(Map properties) {
+     */
+    public void configure(Map<?,?> properties) {
         assertNotYetManaged();
         
         boolean firstTime = !configured.getAndSet(true);
@@ -272,7 +276,7 @@ public abstract class AbstractLocation extends AbstractBrooklynObject implements
             if (rawCodes instanceof CharSequence) {
                 codes = ImmutableSet.copyOf(Splitter.on(",").trimResults().split((CharSequence)rawCodes));
             } else {
-                codes = TypeCoercions.coerce(rawCodes, Set.class);
+                codes = TypeCoercions.coerce(rawCodes, TypeTokens.setOf(String.class));
             }
             configBag.put(LocationConfigKeys.ISO_3166, codes);
         }
@@ -280,7 +284,7 @@ public abstract class AbstractLocation extends AbstractBrooklynObject implements
 
     // TODO ensure no callers rely on 'remove' semantics, and don't remove;
     // or perhaps better use a config bag so we know what is used v unused
-    private static Object removeIfPossible(Map map, Object key) {
+    private static Object removeIfPossible(Map<?,?> map, Object key) {
         try {
             return map.remove(key);
         } catch (Exception e) {
@@ -385,6 +389,8 @@ public abstract class AbstractLocation extends AbstractBrooklynObject implements
             parent.set(newParent);
             ((AbstractLocation)parent.get()).addChild(this); // FIXME Nasty cast
         }
+        
+        onChanged();
     }
 
     @Override
@@ -399,6 +405,7 @@ public abstract class AbstractLocation extends AbstractBrooklynObject implements
         
         // In case this entity class has overridden the given key (e.g. to set default), then retrieve this entity's key
         // TODO when locations become entities, the duplication of this compared to EntityConfigMap.getConfig will disappear.
+        @SuppressWarnings("unchecked")
         ConfigKey<T> ownKey = (ConfigKey<T>) elvis(entityType.getConfigKey(key.getName()), key);
 
         return ownKey.getDefaultValue();
@@ -442,18 +449,15 @@ public abstract class AbstractLocation extends AbstractBrooklynObject implements
     
     @Override
     public <T> T setConfig(ConfigKey<T> key, T value) {
-        return configBag.put(key, value);
-    }
-
-    /** @since 0.6.0 (?) - use getDisplayName */
-    public void setName(String newName) {
-        setDisplayName(newName);
-        displayNameAutoGenerated = false;
+        T result = configBag.put(key, value);
+        onChanged();
+        return result;
     }
 
     public void setDisplayName(String newName) {
         name.set(newName);
         displayNameAutoGenerated = false;
+        onChanged();
     }
 
     @Override
@@ -487,6 +491,7 @@ public abstract class AbstractLocation extends AbstractBrooklynObject implements
         return child;
     }
     
+    @SuppressWarnings("deprecation")
     public void addChild(Location child) {
     	// Previously, setParent delegated to addChildLocation and we sometimes ended up with
     	// duplicate entries here. Instead this now uses a similar scheme to 
@@ -508,7 +513,10 @@ public abstract class AbstractLocation extends AbstractBrooklynObject implements
         }
         
         if (isManaged()) {
-            Locations.manage(child, managementContext);
+            if (!managementContext.getLocationManager().isManaged(child)) {
+                // this deprecated call should be replaced with an internal interface call?
+                managementContext.getLocationManager().manage(child);
+            }
         } else if (managementContext != null) {
             if (((LocalLocationManager)managementContext.getLocationManager()).getLocationEvenIfPreManaged(child.getId()) == null) {
                 ((ManagementContextInternal)managementContext).prePreManage(child);
@@ -517,6 +525,8 @@ public abstract class AbstractLocation extends AbstractBrooklynObject implements
 
         children.add(child);
         child.setParent(this);
+        
+        onChanged();
     }
     
     public boolean removeChild(Location child) {
@@ -534,9 +544,22 @@ public abstract class AbstractLocation extends AbstractBrooklynObject implements
                 managementContext.getLocationManager().unmanage(child);
             }
         }
+        onChanged();
         return removed;
     }
 
+    @Override
+    protected void onTagsChanged() {
+        onChanged();
+    }
+
+    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);
+        }
+    }
+
     /** Default String representation is simplified name of class, together with selected fields. */
     @Override
     public String toString() {

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/72b52e1d/core/src/main/java/brooklyn/policy/basic/AbstractEntityAdjunct.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/policy/basic/AbstractEntityAdjunct.java b/core/src/main/java/brooklyn/policy/basic/AbstractEntityAdjunct.java
index 9ca0e91..a53d9a5 100644
--- a/core/src/main/java/brooklyn/policy/basic/AbstractEntityAdjunct.java
+++ b/core/src/main/java/brooklyn/policy/basic/AbstractEntityAdjunct.java
@@ -26,6 +26,7 @@ import java.util.Collection;
 import java.util.Collections;
 import java.util.Iterator;
 import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 import org.slf4j.Logger;
@@ -59,6 +60,7 @@ import brooklyn.util.flags.TypeCoercions;
 import com.google.common.annotations.Beta;
 import com.google.common.base.Objects;
 import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Maps;
 
 
@@ -97,6 +99,9 @@ public abstract class AbstractEntityAdjunct extends AbstractBrooklynObject imple
     protected transient SubscriptionTracker _subscriptionTracker;
     
     private AtomicBoolean destroyed = new AtomicBoolean(false);
+    
+    @SetFromFlag(value="uniqueTag")
+    protected String uniqueTag;
 
     public AbstractEntityAdjunct() {
         this(Collections.emptyMap());
@@ -262,6 +267,11 @@ public abstract class AbstractEntityAdjunct extends AbstractBrooklynObject imple
         throw new UnsupportedOperationException("reconfiguring "+key+" unsupported for "+this);
     }
     
+    @Override
+    protected void onTagsChanged() {
+        onChanged();
+    }
+    
     protected abstract void onChanged();
     
     protected AdjunctType getAdjunctType() {
@@ -397,11 +407,25 @@ public abstract class AbstractEntityAdjunct extends AbstractBrooklynObject imple
     public boolean isRunning() {
         return !isDestroyed();
     }
+
+    @Override
+    public String getUniqueTag() {
+        return uniqueTag;
+    }
+    
+    @Override
+    public Set<Object> getTags() {
+        if (getUniqueTag()==null) return super.getTags();
+        synchronized (tags) {
+            return ImmutableSet.builder().addAll(tags).add(getUniqueTag()).build();
+        }
+    }
     
     @Override
     public String toString() {
-        return Objects.toStringHelper(getClass())
+        return Objects.toStringHelper(getClass()).omitNullValues()
                 .add("name", name)
+                .add("uniqueTag", uniqueTag)
                 .add("running", isRunning())
                 .add("id", getId())
                 .toString();

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/72b52e1d/core/src/main/java/brooklyn/policy/basic/AbstractPolicy.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/policy/basic/AbstractPolicy.java b/core/src/main/java/brooklyn/policy/basic/AbstractPolicy.java
index 70fa44b..79269c6 100644
--- a/core/src/main/java/brooklyn/policy/basic/AbstractPolicy.java
+++ b/core/src/main/java/brooklyn/policy/basic/AbstractPolicy.java
@@ -54,7 +54,7 @@ public abstract class AbstractPolicy extends AbstractEntityAdjunct implements Po
         this(Collections.emptyMap());
     }
     
-    public AbstractPolicy(Map flags) {
+    public AbstractPolicy(Map<?,?> flags) {
         super(flags);
         policyType = new PolicyTypeImpl(getAdjunctType());
         
@@ -80,6 +80,10 @@ public abstract class AbstractPolicy extends AbstractEntityAdjunct implements Po
 
     @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();
     }
 
@@ -96,7 +100,7 @@ public abstract class AbstractPolicy extends AbstractEntityAdjunct implements Po
 
     @Override
     protected void onChanged() {
-        // TODO Could add PolicyChangeListener, similar to EntityChangeListener; should we do that?
+        // currently changes simply trigger re-persistence; there is no intermediate listener as we do for EntityChangeListener
         if (getManagementContext() != null) {
             getManagementContext().getRebindManager().getChangeListener().onChanged(this);
         }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/72b52e1d/core/src/test/java/brooklyn/enricher/basic/BasicEnricherTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/enricher/basic/BasicEnricherTest.java b/core/src/test/java/brooklyn/enricher/basic/BasicEnricherTest.java
new file mode 100644
index 0000000..9d0fcf2
--- /dev/null
+++ b/core/src/test/java/brooklyn/enricher/basic/BasicEnricherTest.java
@@ -0,0 +1,93 @@
+/*
+ * 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.enricher.basic;
+
+import static org.testng.Assert.assertEquals;
+
+import java.util.Map;
+
+import org.testng.annotations.Test;
+
+import brooklyn.config.ConfigKey;
+import brooklyn.enricher.basic.AbstractEnricher;
+import brooklyn.entity.BrooklynAppUnitTestSupport;
+import brooklyn.event.basic.BasicConfigKey;
+import brooklyn.policy.EnricherSpec;
+import brooklyn.util.collections.MutableSet;
+import brooklyn.util.flags.SetFromFlag;
+
+/**
+ * Test that enricher can be created and accessed, by construction and by spec
+ */
+public class BasicEnricherTest extends BrooklynAppUnitTestSupport {
+    
+    // TODO These tests are a copy of BasicPolicyTest, which is a code smell.
+    // However, the src/main/java code does not contain as much duplication.
+    
+    public static class MyEnricher extends AbstractEnricher {
+        @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");
+        
+        MyEnricher(Map<?,?> flags) {
+            super(flags);
+        }
+        
+        public MyEnricher() {
+            super();
+        }
+    }
+    
+    @Test
+    public void testAddInstance() throws Exception {
+        MyEnricher enricher = new MyEnricher();
+        enricher.setDisplayName("Bob");
+        enricher.setConfig(MyEnricher.STR_KEY, "aval");
+        enricher.setConfig(MyEnricher.INT_KEY, 2);
+        app.addEnricher(enricher);
+        
+        assertEquals(enricher.getDisplayName(), "Bob");
+        assertEquals(enricher.getConfig(MyEnricher.STR_KEY), "aval");
+        assertEquals(enricher.getConfig(MyEnricher.INT_KEY), (Integer)2);
+    }
+    
+    @Test
+    public void testAddSpec() throws Exception {
+        MyEnricher enricher = app.addEnricher(EnricherSpec.create(MyEnricher.class)
+            .displayName("Bob")
+            .configure(MyEnricher.STR_KEY, "aval").configure(MyEnricher.INT_KEY, 2));
+        
+        assertEquals(enricher.getDisplayName(), "Bob");
+        assertEquals(enricher.getConfig(MyEnricher.STR_KEY), "aval");
+        assertEquals(enricher.getConfig(MyEnricher.INT_KEY), (Integer)2);
+    }
+        
+    @Test
+    public void testTagsFromSpec() throws Exception {
+        MyEnricher enricher = app.addEnricher(EnricherSpec.create(MyEnricher.class).tag(99).uniqueTag("x"));
+
+        assertEquals(enricher.getTags(), MutableSet.of("x", 99));
+        assertEquals(enricher.getUniqueTag(), "x");
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/72b52e1d/core/src/test/java/brooklyn/enricher/basic/EnricherConfigTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/enricher/basic/EnricherConfigTest.java b/core/src/test/java/brooklyn/enricher/basic/EnricherConfigTest.java
new file mode 100644
index 0000000..03677d9
--- /dev/null
+++ b/core/src/test/java/brooklyn/enricher/basic/EnricherConfigTest.java
@@ -0,0 +1,148 @@
+/*
+ * 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.enricher.basic;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.fail;
+
+import org.testng.annotations.Test;
+
+import brooklyn.enricher.basic.BasicEnricherTest.MyEnricher;
+import brooklyn.entity.BrooklynAppUnitTestSupport;
+import brooklyn.event.basic.BasicConfigKey;
+import brooklyn.util.collections.MutableMap;
+
+/**
+ * Test that configuration properties are usable and inherited correctly.
+ */
+public class EnricherConfigTest extends BrooklynAppUnitTestSupport {
+    
+    // TODO These tests are a copy of PolicyConfigTest, which is a code smell.
+    // However, the src/main/java code does not contain as much duplication.
+    
+    private BasicConfigKey<String> differentKey = new BasicConfigKey<String>(String.class, "differentkey", "diffval");
+
+    @Test
+    public void testConfigFlagsPassedInAtConstructionIsAvailable() throws Exception {
+        MyEnricher enricher = new MyEnricher(MutableMap.builder()
+                .put("strKey", "aval")
+                .put("intKey", 2)
+                .build());
+        app.addEnricher(enricher);
+        
+        assertEquals(enricher.getConfig(MyEnricher.STR_KEY), "aval");
+        assertEquals(enricher.getConfig(MyEnricher.INT_KEY), (Integer)2);
+        // this is set, because key name matches annotation on STR_KEY
+        assertEquals(enricher.getConfig(MyEnricher.STR_KEY_WITH_DEFAULT), "aval");
+    }
+    
+    @Test
+    public void testUnknownConfigPassedInAtConstructionIsWarnedAndIgnored() throws Exception {
+        // TODO Also assert it's warned
+        MyEnricher enricher = new MyEnricher(MutableMap.builder()
+                .put(differentKey, "aval")
+                .build());
+        app.addEnricher(enricher);
+        
+        assertEquals(enricher.getConfig(differentKey), null);
+        assertEquals(enricher.getEnricherType().getConfigKey(differentKey.getName()), null);
+    }
+    
+    @Test
+    public void testConfigPassedInAtConstructionIsAvailable() throws Exception {
+        MyEnricher enricher = new MyEnricher(MutableMap.builder()
+                .put(MyEnricher.STR_KEY, "aval")
+                .put(MyEnricher.INT_KEY, 2)
+                .build());
+        app.addEnricher(enricher);
+        
+        assertEquals(enricher.getConfig(MyEnricher.STR_KEY), "aval");
+        assertEquals(enricher.getConfig(MyEnricher.INT_KEY), (Integer)2);
+        // this is not set (contrast with above)
+        assertEquals(enricher.getConfig(MyEnricher.STR_KEY_WITH_DEFAULT), MyEnricher.STR_KEY_WITH_DEFAULT.getDefaultValue());
+    }
+    
+    @Test
+    public void testConfigSetToGroovyTruthFalseIsAvailable() throws Exception {
+        MyEnricher enricher = new MyEnricher(MutableMap.builder()
+                .put(MyEnricher.INT_KEY_WITH_DEFAULT, 0)
+                .build());
+        app.addEnricher(enricher);
+        
+        assertEquals(enricher.getConfig(MyEnricher.INT_KEY_WITH_DEFAULT), (Integer)0);
+    }
+    
+    @Test
+    public void testConfigSetToNullIsAvailable() throws Exception {
+        MyEnricher enricher = new MyEnricher(MutableMap.builder()
+                .put(MyEnricher.STR_KEY_WITH_DEFAULT, null)
+                .build());
+        app.addEnricher(enricher);
+        
+        assertEquals(enricher.getConfig(MyEnricher.STR_KEY_WITH_DEFAULT), null);
+    }
+    
+    @Test
+    public void testConfigCanBeSetOnEnricher() throws Exception {
+        MyEnricher enricher = new MyEnricher();
+        enricher.setConfig(MyEnricher.STR_KEY, "aval");
+        enricher.setConfig(MyEnricher.INT_KEY, 2);
+        app.addEnricher(enricher);
+        
+        assertEquals(enricher.getConfig(MyEnricher.STR_KEY), "aval");
+        assertEquals(enricher.getConfig(MyEnricher.INT_KEY), (Integer)2);
+    }
+    
+    @Test
+    public void testConfigSetterOverridesConstructorValue() throws Exception {
+        MyEnricher enricher = new MyEnricher(MutableMap.builder()
+                .put(MyEnricher.STR_KEY, "aval")
+                .build());
+        enricher.setConfig(MyEnricher.STR_KEY, "diffval");
+        app.addEnricher(enricher);
+        
+        assertEquals(enricher.getConfig(MyEnricher.STR_KEY), "diffval");
+    }
+
+    @Test
+    public void testConfigCannotBeSetAfterApplicationIsStarted() throws Exception {
+        MyEnricher enricher = new MyEnricher(MutableMap.builder()
+                .put(MyEnricher.STR_KEY, "origval")
+                .build());
+        app.addEnricher(enricher);
+        
+        try {
+            enricher.setConfig(MyEnricher.STR_KEY,"newval");
+            fail();
+        } catch (UnsupportedOperationException e) {
+            // success
+        }
+        
+        assertEquals(enricher.getConfig(MyEnricher.STR_KEY), "origval");
+    }
+    
+    @Test
+    public void testConfigReturnsDefaultValueIfNotSet() throws Exception {
+        MyEnricher enricher = new MyEnricher();
+        app.addEnricher(enricher);
+        
+        assertEquals(enricher.getConfig(MyEnricher.STR_KEY_WITH_DEFAULT), "str key default");
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/72b52e1d/core/src/test/java/brooklyn/entity/basic/EntitiesTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/entity/basic/EntitiesTest.java b/core/src/test/java/brooklyn/entity/basic/EntitiesTest.java
index 8acdf72..e9b3399 100644
--- a/core/src/test/java/brooklyn/entity/basic/EntitiesTest.java
+++ b/core/src/test/java/brooklyn/entity/basic/EntitiesTest.java
@@ -107,14 +107,17 @@ public class EntitiesTest extends BrooklynAppUnitTestSupport {
     @Test
     public void testCreateGetContainsAndRemoveTags() throws Exception {
         entity = app.createAndManageChild(EntitySpec.create(TestEntity.class)
+            .tag(2)
             .addInitializer(EntityInitializers.addingTags("foo")));
         
         entity.addTag(app);
         
+        Assert.assertTrue(entity.containsTag(app));
         Assert.assertTrue(entity.containsTag("foo"));
+        Assert.assertTrue(entity.containsTag(2));
         Assert.assertFalse(entity.containsTag("bar"));
         
-        Assert.assertEquals(entity.getTags(), MutableSet.of(app, "foo"));
+        Assert.assertEquals(entity.getTags(), MutableSet.of(app, "foo", 2));
         
         entity.removeTag("foo");
         Assert.assertFalse(entity.containsTag("foo"));
@@ -122,6 +125,7 @@ public class EntitiesTest extends BrooklynAppUnitTestSupport {
         Assert.assertTrue(entity.containsTag(entity.getParent()));
         Assert.assertFalse(entity.containsTag(entity));
         
+        entity.removeTag(2);
         Assert.assertEquals(entity.getTags(), MutableSet.of(app));
     }
     

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/72b52e1d/core/src/test/java/brooklyn/location/basic/AbstractLocationTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/location/basic/AbstractLocationTest.java b/core/src/test/java/brooklyn/location/basic/AbstractLocationTest.java
index 4993486..97fb633 100644
--- a/core/src/test/java/brooklyn/location/basic/AbstractLocationTest.java
+++ b/core/src/test/java/brooklyn/location/basic/AbstractLocationTest.java
@@ -36,6 +36,7 @@ import brooklyn.location.LocationSpec;
 import brooklyn.management.ManagementContext;
 import brooklyn.test.entity.LocalManagementContextForTests;
 import brooklyn.util.collections.MutableMap;
+import brooklyn.util.collections.MutableSet;
 import brooklyn.util.flags.SetFromFlag;
 
 import com.google.common.collect.ImmutableList;
@@ -172,5 +173,11 @@ public class AbstractLocationTest {
         ConcreteLocation loc = createConcrete();
         assertEquals(loc.myfield, "mydefault");
     }
-    
+
+    @Test
+    public void testLocationTags() throws Exception {
+        LocationInternal loc = mgmt.getLocationManager().createLocation(LocationSpec.create(ConcreteLocation.class).tag("x"));
+        assertEquals(loc.getTags(), MutableSet.of("x"));
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/72b52e1d/core/src/test/java/brooklyn/location/basic/LocationConfigTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/location/basic/LocationConfigTest.java b/core/src/test/java/brooklyn/location/basic/LocationConfigTest.java
index 3db4743..69f18a1 100644
--- a/core/src/test/java/brooklyn/location/basic/LocationConfigTest.java
+++ b/core/src/test/java/brooklyn/location/basic/LocationConfigTest.java
@@ -176,6 +176,7 @@ public class LocationConfigTest {
         assertEquals(subloc.getConfig(MyLocation.MY_CONFIG_WITH_DEFAULT), "mysubdefault");
     }
     
+    @SuppressWarnings("serial")
     public static class MyLocation extends AbstractLocation {
         public static final ConfigKey<String> MY_CONFIG = ConfigKeys.newStringConfigKey("mylocation.myconfig");
 
@@ -185,6 +186,7 @@ public class LocationConfigTest {
         public static final ConfigKey<String> MY_CONFIG_WITH_DEFAULT = ConfigKeys.newStringConfigKey("mylocation.myconfigwithdefault", "", "mydefault");
     }
     
+    @SuppressWarnings("serial")
     public static class MyChildLocation extends AbstractLocation {
         public static final ConfigKey<String> MY_CHILD_CONFIG = ConfigKeys.newStringConfigKey("mychildlocation.myconfig");
 
@@ -192,6 +194,7 @@ public class LocationConfigTest {
         public static final ConfigKey<String> MY_CHILD_CONFIG_WITH_FLAGNAME = ConfigKeys.newStringConfigKey("mychildlocation.myconfigwithflagname");
     }
     
+    @SuppressWarnings("serial")
     public static class MySubLocation extends MyLocation {
         public static final ConfigKey<String> MY_CONFIG_WITH_DEFAULT = ConfigKeys.newConfigKeyWithDefault(MyLocation.MY_CONFIG_WITH_DEFAULT, "mysubdefault");
     }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/72b52e1d/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
new file mode 100644
index 0000000..1072bc8
--- /dev/null
+++ b/core/src/test/java/brooklyn/policy/basic/BasicPolicyTest.java
@@ -0,0 +1,89 @@
+/*
+ * 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.testng.annotations.Test;
+
+import brooklyn.config.ConfigKey;
+import brooklyn.entity.BrooklynAppUnitTestSupport;
+import brooklyn.event.basic.BasicConfigKey;
+import brooklyn.policy.PolicySpec;
+import brooklyn.util.collections.MutableSet;
+import brooklyn.util.flags.SetFromFlag;
+
+/**
+ * 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.setConfig(MyPolicy.STR_KEY, "aval");
+        policy.setConfig(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.getTags(), MutableSet.of("x", 99));
+        assertEquals(policy.getUniqueTag(), "x");
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/72b52e1d/core/src/test/java/brooklyn/policy/basic/EnricherConfigTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/policy/basic/EnricherConfigTest.java b/core/src/test/java/brooklyn/policy/basic/EnricherConfigTest.java
deleted file mode 100644
index c8dced8..0000000
--- a/core/src/test/java/brooklyn/policy/basic/EnricherConfigTest.java
+++ /dev/null
@@ -1,169 +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.fail;
-
-import java.util.Map;
-
-import org.testng.annotations.Test;
-
-import brooklyn.config.ConfigKey;
-import brooklyn.enricher.basic.AbstractEnricher;
-import brooklyn.entity.BrooklynAppUnitTestSupport;
-import brooklyn.event.basic.BasicConfigKey;
-import brooklyn.util.collections.MutableMap;
-import brooklyn.util.flags.SetFromFlag;
-
-/**
- * Test that configuration properties are usable and inherited correctly.
- */
-public class EnricherConfigTest extends BrooklynAppUnitTestSupport {
-    
-    // TODO These tests are a copy of PolicyConfigMapUsageTest, which is a code smell.
-    // However, the src/main/java code does not contain as much duplication.
-    
-    public static class MyEnricher extends AbstractEnricher {
-        @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");
-        
-        MyEnricher(Map flags) {
-            super(flags);
-        }
-        
-        MyEnricher() {
-            super();
-        }
-    }
-    
-    private BasicConfigKey<String> differentKey = new BasicConfigKey<String>(String.class, "differentkey", "diffval");
-
-    @Test
-    public void testConfigFlagsPassedInAtConstructionIsAvailable() throws Exception {
-        MyEnricher enricher = new MyEnricher(MutableMap.builder()
-                .put("strKey", "aval")
-                .put("intKey", 2)
-                .build());
-        app.addEnricher(enricher);
-        
-        assertEquals(enricher.getConfig(MyEnricher.STR_KEY), "aval");
-        assertEquals(enricher.getConfig(MyEnricher.INT_KEY), (Integer)2);
-        // this is set, because key name matches annotation on STR_KEY
-        assertEquals(enricher.getConfig(MyEnricher.STR_KEY_WITH_DEFAULT), "aval");
-    }
-    
-    @Test
-    public void testUnknownConfigPassedInAtConstructionIsWarnedAndIgnored() throws Exception {
-        // TODO Also assert it's warned
-        MyEnricher enricher = new MyEnricher(MutableMap.builder()
-                .put(differentKey, "aval")
-                .build());
-        app.addEnricher(enricher);
-        
-        assertEquals(enricher.getConfig(differentKey), null);
-        assertEquals(enricher.getEnricherType().getConfigKey(differentKey.getName()), null);
-    }
-    
-    @Test
-    public void testConfigPassedInAtConstructionIsAvailable() throws Exception {
-        MyEnricher enricher = new MyEnricher(MutableMap.builder()
-                .put(MyEnricher.STR_KEY, "aval")
-                .put(MyEnricher.INT_KEY, 2)
-                .build());
-        app.addEnricher(enricher);
-        
-        assertEquals(enricher.getConfig(MyEnricher.STR_KEY), "aval");
-        assertEquals(enricher.getConfig(MyEnricher.INT_KEY), (Integer)2);
-        // this is not set (contrast with above)
-        assertEquals(enricher.getConfig(MyEnricher.STR_KEY_WITH_DEFAULT), MyEnricher.STR_KEY_WITH_DEFAULT.getDefaultValue());
-    }
-    
-    @Test
-    public void testConfigSetToGroovyTruthFalseIsAvailable() throws Exception {
-        MyEnricher enricher = new MyEnricher(MutableMap.builder()
-                .put(MyEnricher.INT_KEY_WITH_DEFAULT, 0)
-                .build());
-        app.addEnricher(enricher);
-        
-        assertEquals(enricher.getConfig(MyEnricher.INT_KEY_WITH_DEFAULT), (Integer)0);
-    }
-    
-    @Test
-    public void testConfigSetToNullIsAvailable() throws Exception {
-        MyEnricher enricher = new MyEnricher(MutableMap.builder()
-                .put(MyEnricher.STR_KEY_WITH_DEFAULT, null)
-                .build());
-        app.addEnricher(enricher);
-        
-        assertEquals(enricher.getConfig(MyEnricher.STR_KEY_WITH_DEFAULT), null);
-    }
-    
-    @Test
-    public void testConfigCanBeSetOnEnricher() throws Exception {
-        MyEnricher enricher = new MyEnricher();
-        enricher.setConfig(MyEnricher.STR_KEY, "aval");
-        enricher.setConfig(MyEnricher.INT_KEY, 2);
-        app.addEnricher(enricher);
-        
-        assertEquals(enricher.getConfig(MyEnricher.STR_KEY), "aval");
-        assertEquals(enricher.getConfig(MyEnricher.INT_KEY), (Integer)2);
-    }
-    
-    @Test
-    public void testConfigSetterOverridesConstructorValue() throws Exception {
-        MyEnricher enricher = new MyEnricher(MutableMap.builder()
-                .put(MyEnricher.STR_KEY, "aval")
-                .build());
-        enricher.setConfig(MyEnricher.STR_KEY, "diffval");
-        app.addEnricher(enricher);
-        
-        assertEquals(enricher.getConfig(MyEnricher.STR_KEY), "diffval");
-    }
-
-    @Test
-    public void testConfigCannotBeSetAfterApplicationIsStarted() throws Exception {
-        MyEnricher enricher = new MyEnricher(MutableMap.builder()
-                .put(MyEnricher.STR_KEY, "origval")
-                .build());
-        app.addEnricher(enricher);
-        
-        try {
-            enricher.setConfig(MyEnricher.STR_KEY,"newval");
-            fail();
-        } catch (UnsupportedOperationException e) {
-            // success
-        }
-        
-        assertEquals(enricher.getConfig(MyEnricher.STR_KEY), "origval");
-    }
-    
-    @Test
-    public void testConfigReturnsDefaultValueIfNotSet() throws Exception {
-        MyEnricher enricher = new MyEnricher();
-        app.addEnricher(enricher);
-        
-        assertEquals(enricher.getConfig(MyEnricher.STR_KEY_WITH_DEFAULT), "str key default");
-    }
-}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/72b52e1d/core/src/test/java/brooklyn/policy/basic/PolicyConfigMapUsageTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/policy/basic/PolicyConfigMapUsageTest.java b/core/src/test/java/brooklyn/policy/basic/PolicyConfigMapUsageTest.java
deleted file mode 100644
index 05611d2..0000000
--- a/core/src/test/java/brooklyn/policy/basic/PolicyConfigMapUsageTest.java
+++ /dev/null
@@ -1,222 +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.Map;
-import java.util.concurrent.Callable;
-import java.util.concurrent.CountDownLatch;
-
-import org.testng.annotations.Test;
-
-import brooklyn.config.ConfigKey;
-import brooklyn.entity.BrooklynAppUnitTestSupport;
-import brooklyn.event.basic.BasicConfigKey;
-import brooklyn.event.basic.DependentConfiguration;
-import brooklyn.test.entity.TestEntity;
-import brooklyn.util.collections.MutableMap;
-import brooklyn.util.exceptions.Exceptions;
-import brooklyn.util.flags.SetFromFlag;
-
-import com.google.common.util.concurrent.Callables;
-
-/**
- * Test that configuration properties are usable and inherited correctly.
- */
-public class PolicyConfigMapUsageTest extends BrooklynAppUnitTestSupport {
-    private static final int EARLY_RETURN_GRACE = 10;
-
-    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);
-        }
-        
-        MyPolicy() {
-            super();
-        }
-    }
-    
-    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.setConfig(MyPolicy.STR_KEY, "aval");
-        policy.setConfig(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.setConfig(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.setConfig(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/72b52e1d/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
new file mode 100644
index 0000000..502881b
--- /dev/null
+++ b/core/src/test/java/brooklyn/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 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.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.test.entity.TestEntity;
+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.setConfig(MyPolicy.STR_KEY, "aval");
+        policy.setConfig(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.setConfig(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.setConfig(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/72b52e1d/usage/camp/src/main/java/io/brooklyn/camp/brooklyn/spi/creation/BrooklynAssemblyTemplateInstantiator.java
----------------------------------------------------------------------
diff --git a/usage/camp/src/main/java/io/brooklyn/camp/brooklyn/spi/creation/BrooklynAssemblyTemplateInstantiator.java b/usage/camp/src/main/java/io/brooklyn/camp/brooklyn/spi/creation/BrooklynAssemblyTemplateInstantiator.java
index 2457812..53e31e0 100644
--- a/usage/camp/src/main/java/io/brooklyn/camp/brooklyn/spi/creation/BrooklynAssemblyTemplateInstantiator.java
+++ b/usage/camp/src/main/java/io/brooklyn/camp/brooklyn/spi/creation/BrooklynAssemblyTemplateInstantiator.java
@@ -142,6 +142,7 @@ public class BrooklynAssemblyTemplateInstantiator implements AssemblyTemplateSpe
             // if promoted, apply the transformations done to the app
             // (normally this will be done by the resolveSpec call above)
             if (app.getDisplayName()==null) app.displayName(oldApp.getDisplayName());
+            app.tags(oldApp.getTags());
             app.locations(oldApp.getLocations());
         }
         

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/72b52e1d/utils/common/src/main/java/brooklyn/util/guava/TypeTokens.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/brooklyn/util/guava/TypeTokens.java b/utils/common/src/main/java/brooklyn/util/guava/TypeTokens.java
index ca69a97..d16d2df 100644
--- a/utils/common/src/main/java/brooklyn/util/guava/TypeTokens.java
+++ b/utils/common/src/main/java/brooklyn/util/guava/TypeTokens.java
@@ -18,6 +18,10 @@
  */
 package brooklyn.util.guava;
 
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
 import javax.annotation.Nullable;
 
 import com.google.common.reflect.TypeToken;
@@ -68,5 +72,20 @@ public class TypeTokens {
     public static <T> Class<T> getRawRawType(TypeToken<T> token) {
         return (Class)token.getRawType();
     }
+
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    public static <T> TypeToken<Set<T>> setOf(Class<T> type) {
+        return (TypeToken) TypeToken.of(Set.class);
+    }
+    
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    public static <K,V> TypeToken<Map<K,V>> mapOf(Class<K> key, Class<V> value) {
+        return (TypeToken) TypeToken.of(Map.class);
+    }
+    
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    public static <T> TypeToken<List<T>> listOf(Class<T> type) {
+        return (TypeToken) TypeToken.of(List.class);
+    }
     
 }