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 2017/02/15 18:30:56 UTC

[03/28] brooklyn-server git commit: migrate config inheritance to new classes

migrate config inheritance to new classes

pioneers use of `readResolve` that actually works brilliantly out of the box due to xstream

also tidying `BasicConfigInheritance` and adding a placeholder (not used yet)
for resolving ancestor defaults

includes tests for config inheritance serialization


Project: http://git-wip-us.apache.org/repos/asf/brooklyn-server/repo
Commit: http://git-wip-us.apache.org/repos/asf/brooklyn-server/commit/53ae1c61
Tree: http://git-wip-us.apache.org/repos/asf/brooklyn-server/tree/53ae1c61
Diff: http://git-wip-us.apache.org/repos/asf/brooklyn-server/diff/53ae1c61

Branch: refs/heads/master
Commit: 53ae1c611c736ee572801f840785266690531da9
Parents: 9f8a3aa
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Thu Nov 24 10:56:46 2016 +0000
Committer: Alex Heneveld <al...@Alexs-MacBook-Pro.local>
Committed: Tue Dec 6 10:05:21 2016 +0000

----------------------------------------------------------------------
 .../core/config/BasicConfigInheritance.java     | 255 +++++++++++++++++--
 .../util/core/xstream/ClassRenamingMapper.java  |   4 +-
 .../rebind/RebindConfigInheritanceTest.java     | 145 +++++++++++
 .../core/mgmt/rebind/RebindOptions.java         |   6 +
 .../core/mgmt/rebind/RebindTestFixture.java     |   1 -
 ...RebindWithDeserializingClassRenamesTest.java |  35 ++-
 .../config-inheritance-basic-2016-10-wj5s8u9h73 |  50 ++++
 .../config-inheritance-basic-2016-11-kmpez5fznt |  46 ++++
 ...nfig-inheritance-prebasic-2016-07-toruf2wxg4 | 137 ++++++++++
 .../brooklyn/config/ConfigInheritance.java      |  29 ++-
 10 files changed, 661 insertions(+), 47 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/53ae1c61/core/src/main/java/org/apache/brooklyn/core/config/BasicConfigInheritance.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/config/BasicConfigInheritance.java b/core/src/main/java/org/apache/brooklyn/core/config/BasicConfigInheritance.java
index 51f2e7a..dd3b336 100644
--- a/core/src/main/java/org/apache/brooklyn/core/config/BasicConfigInheritance.java
+++ b/core/src/main/java/org/apache/brooklyn/core/config/BasicConfigInheritance.java
@@ -18,8 +18,12 @@
  */
 package org.apache.brooklyn.core.config;
 
+import java.lang.reflect.Field;
+import java.util.Arrays;
 import java.util.Map;
+import java.util.Objects;
 
+import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 
 import org.apache.brooklyn.config.ConfigInheritance;
@@ -28,51 +32,176 @@ import org.apache.brooklyn.config.ConfigInheritances.BasicConfigValueAtContainer
 import org.apache.brooklyn.config.ConfigKey;
 import org.apache.brooklyn.config.ConfigValueAtContainer;
 import org.apache.brooklyn.util.collections.CollectionMerger;
+import org.apache.brooklyn.util.exceptions.Exceptions;
 import org.apache.brooklyn.util.exceptions.ReferenceWithError;
 import org.apache.brooklyn.util.guava.Maybe;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
+@SuppressWarnings("serial")
 public class BasicConfigInheritance implements ConfigInheritance {
 
+    private static final Logger log = LoggerFactory.getLogger(BasicConfigInheritance.class);
+    
     private static final long serialVersionUID = -5916548049057961051L;
 
     public static final String CONFLICT_RESOLUTION_STRATEGY_DEEP_MERGE = "deep_merge";
     public static final String CONFLICT_RESOLUTION_STRATEGY_OVERWRITE = "overwrite";
     
+    public static abstract class DelegatingConfigInheritance implements ConfigInheritance {
+        protected abstract ConfigInheritance getDelegate();
+        
+        @Override @Deprecated public InheritanceMode isInherited(ConfigKey<?> key, Object from, Object to) {
+            return getDelegate().isInherited(key, from, to);
+        }
+        @Override public <TContainer, TValue> boolean isReinheritable(ConfigValueAtContainer<TContainer, TValue> parent, ConfigInheritanceContext context) {
+            return getDelegate().isReinheritable(parent, context);
+        }
+        @Override public <TContainer, TValue> boolean considerParent(ConfigValueAtContainer<TContainer, TValue> local, ConfigValueAtContainer<TContainer, TValue> parent, ConfigInheritanceContext context) {
+            return getDelegate().considerParent(local, parent, context);
+        }
+        @Override
+        public <TContainer, TValue> ReferenceWithError<ConfigValueAtContainer<TContainer, TValue>> resolveWithParent(ConfigValueAtContainer<TContainer, TValue> local, ConfigValueAtContainer<TContainer, TValue> resolvedParent, ConfigInheritanceContext context) {
+            return getDelegate().resolveWithParent(local, resolvedParent, context);
+        }
+        @Override
+        public boolean equals(Object obj) {
+            return super.equals(obj) || getDelegate().equals(obj);
+        }
+    }
+
+    /*
+     * use of delegate is so that stateless classes can be defined to make the serialization nice,
+     * both the name and hiding the implementation detail (also making it easier for that detail to change);
+     * with aliased type names the field names here could even be aliases for the type names.
+     * (we could alternatively have an "alias-for-final-instance" mode in serialization,
+     * to optimize where we know that a java instance is final.)   
+     */
+    
+    private static class NotReinherited extends DelegatingConfigInheritance {
+        final transient BasicConfigInheritance delegate = new BasicConfigInheritance(false, CONFLICT_RESOLUTION_STRATEGY_OVERWRITE, false, true); 
+        @Override protected ConfigInheritance getDelegate() { return delegate; }
+    }
     /** Indicates that a config key value should not be passed down from a container where it is defined.
      * Unlike {@link #NEVER_INHERITED} these values can be passed down if set as anonymous keys at a container
      * (ie the container does not expect it) to a container which does expect it, but it will not be passed down further. 
      * If the inheritor also defines a value the parent's value is ignored irrespective 
      * (as in {@link #OVERWRITE}; see {@link #NOT_REINHERITED_ELSE_DEEP_MERGE} if merging is desired). */
-    public static BasicConfigInheritance NOT_REINHERITED = new BasicConfigInheritance(false,CONFLICT_RESOLUTION_STRATEGY_OVERWRITE,false);
+    public static ConfigInheritance NOT_REINHERITED = new NotReinherited();
+    
+    private static class NotReinheritedElseDeepMerge extends DelegatingConfigInheritance {
+        final transient BasicConfigInheritance delegate = new BasicConfigInheritance(false, CONFLICT_RESOLUTION_STRATEGY_DEEP_MERGE, false, true); 
+        @Override protected ConfigInheritance getDelegate() { return delegate; }
+    }
     /** As {@link #NOT_REINHERITED} but in cases where a value is inherited because a parent did not recognize it,
      * if the inheritor also defines a value the two values should be merged. */
-    public static BasicConfigInheritance NOT_REINHERITED_ELSE_DEEP_MERGE = new BasicConfigInheritance(false,CONFLICT_RESOLUTION_STRATEGY_DEEP_MERGE,false);
-    /** Indicates that a key's value should never be inherited, even if defined on a container that does not know the key.
-     * Most usages will prefer {@link #NOT_REINHERITED}. */
-    public static BasicConfigInheritance NEVER_INHERITED = new BasicConfigInheritance(false,CONFLICT_RESOLUTION_STRATEGY_OVERWRITE,true);
+    public static ConfigInheritance NOT_REINHERITED_ELSE_DEEP_MERGE = new NotReinheritedElseDeepMerge();
+    
+    private static class NeverInherited extends DelegatingConfigInheritance {
+        final transient BasicConfigInheritance delegate = new BasicConfigInheritance(false, CONFLICT_RESOLUTION_STRATEGY_OVERWRITE, true, false);
+        @Override protected ConfigInheritance getDelegate() { return delegate; }
+    }
+    /** Indicates that a key's value should never be inherited, even if inherited from a value set on a container that does not know the key.
+     * (Most usages will prefer {@link #NOT_REINHERITED}.) */
+    public static ConfigInheritance NEVER_INHERITED = new NeverInherited();
+    
+    private static class Overwrite extends DelegatingConfigInheritance {
+        final transient BasicConfigInheritance delegate = new BasicConfigInheritance(true, CONFLICT_RESOLUTION_STRATEGY_OVERWRITE, false, true);
+        @Override protected ConfigInheritance getDelegate() { return delegate; }
+    }
     /** Indicates that if a key has a value at both an ancestor and a descendant, the descendant and his descendants
      * will prefer the value at the descendant. */
-    public static BasicConfigInheritance OVERWRITE = new BasicConfigInheritance(true,CONFLICT_RESOLUTION_STRATEGY_OVERWRITE,false);
+    public static ConfigInheritance OVERWRITE = new Overwrite();
+    
+    private static class DeepMerge extends DelegatingConfigInheritance {
+        final transient BasicConfigInheritance delegate = new BasicConfigInheritance(true, CONFLICT_RESOLUTION_STRATEGY_DEEP_MERGE, false, true);
+        @Override protected ConfigInheritance getDelegate() { return delegate; }
+    }
     /** Indicates that if a key has a value at both an ancestor and a descendant, the descendant and his descendants
      * should attempt to merge the values. If the values are not mergable behaviour is undefined
      * (and often the descendant's value will simply overwrite). */
-    public static BasicConfigInheritance DEEP_MERGE = new BasicConfigInheritance(true,CONFLICT_RESOLUTION_STRATEGY_DEEP_MERGE,false);
+    public static ConfigInheritance DEEP_MERGE = new DeepMerge();
+
+    // support conversion from these legacy fields
+    @SuppressWarnings("deprecation")
+    private static void registerReplacements() {
+        ConfigInheritance.Legacy.registerReplacement(ConfigInheritance.DEEP_MERGE, DEEP_MERGE); 
+        ConfigInheritance.Legacy.registerReplacement(ConfigInheritance.ALWAYS, OVERWRITE); 
+        ConfigInheritance.Legacy.registerReplacement(ConfigInheritance.NONE, NOT_REINHERITED); 
+    }
+    static { registerReplacements(); }
     
-    /** reinheritable? true/false; if false, children/descendants/inheritors will never see it; default true */
+    /** whether a value on a key defined locally should be inheritable by descendants.
+     * if false at a point where a key is defined, 
+     * children/descendants/inheritors will not be able to see its value, whether explicit or default.
+     * default true:  things are normally reinherited.
+     * <p>
+     * note that this only takes effect where a key is defined locally.
+     * if a key is not defined at an ancestor, a descendant setting this value false will not prevent it 
+     * from inheriting values from ancestors.
+     * <p> 
+     * typical use case for setting this is false is where a key is consumed and descendants should not
+     * "reconsume" it.  for example setting files to install on a VM need only be applied once,
+     * and if it has <b>runtime management</b> hierarchy descendants which also understand that field they
+     * should not install the same files. 
+     * (there is normally no reason to set this false in the context of <b>type hierarchy</b> inheritance because
+     * an ancestor type cannot "consume" a value.) */
     protected final boolean isReinherited;
-    /** conflict-resolution-strategy? in {@link BasicConfigInheritance} supported values are
+    
+    /** a symbol indicating a conflict-resolution-strategy understood by the implementation.
+     * in {@link BasicConfigInheritance} supported values are
      * {@link #CONFLICT_RESOLUTION_STRATEGY_DEEP_MERGE} and {@link #CONFLICT_RESOLUTION_STRATEGY_OVERWRITE}.
-     * subclasses may pass null if they provide a custom implementaton of {@link #resolveWithParentCustomStrategy(ConfigValueAtContainer, ConfigValueAtContainer, org.apache.brooklyn.config.ConfigInheritance.ConfigInheritanceContext)} */
+     * subclasses may pass null or a different string if they provide a custom implementaton 
+     * of {@link #resolveWithParentCustomStrategy(ConfigValueAtContainer, ConfigValueAtContainer, org.apache.brooklyn.config.ConfigInheritance.ConfigInheritanceContext)} */
     @Nullable protected final String conflictResolutionStrategy;
-    /** use-local-default-value? true/false; if true, overwrite above means "always ignore (even if null)"; default false
-     * whereas merge means "use local default (if non-null)" */
-    protected final boolean useLocalDefaultValue;
+    
+    /** @deprecated since 0.10.0 when this was introduced, now renamed {@link #localDefaultResolvesWithAncestorValue} */
+    @Deprecated protected final Boolean useLocalDefaultValue;
+    /** whether a local default value should be considered for resolution in the presence of an ancestor value.
+     * can use true with overwrite to mean don't inherit, or true with merge to mean local default merged on top of inherited
+     * (but be careful here, if local default is null in a merge it will delete ancestor values).
+     * <p>
+     * in most cases this is false, meaning a default value is ignored if the parent has a value.
+     * <p>
+     * null should not be used. a boxed object is taken (as opposed to a primitive boolean) only in order to support migration.    
+     */
+    @Nonnull
+    protected final Boolean localDefaultResolvesWithAncestorValue;
 
-    protected BasicConfigInheritance(boolean isReinherited, @Nullable String conflictResolutionStrategy, boolean useLocalDefaultValue) {
+    /** whether a default set in an ancestor container's key definition will be considered as the
+     * local default value at descendants who don't define any other value (nothing set locally and local default is null);
+     * <p>
+     * if true (now the usual behaviour), if an ancestor defines a default and a descendant doesn't, the ancestor's value will be taken as a default.
+     * if it is also the case that localDefaultResolvesWithAncestorValue is true at the <i>ancestor</i> then a descendant who
+     * defines a local default value (with this field true) will have its conflict resolution strategy
+     * applied with the ancestor's default value.
+     * <p>
+     * if this is false, ancestor defaults are completely ignored; prior to 0.10.0 this was the normal behaviour,
+     * but it caused surprises where default values in parameters did not take effect.
+     * <p>
+     * null should not be used. a boxed object is taken (as opposed to a primitive boolean) only in order to support migration.    
+     */
+    @Nonnull
+    protected final Boolean ancestorDefaultInheritable;
+    
+    /* TODO
+     * - document key definition inference vs explicitness (conflict resolution is inferred from nearest descendant explicit key; whereas other values don't apply if no explicit key)
+     * - ancestor default value inheritance -- https://issues.apache.org/jira/browse/BROOKLYN-267
+     * - immediate config evaluation
+     */
+
+    @Deprecated /** @deprecated since 0.10.0 use four-arg constructor */
+    protected BasicConfigInheritance(boolean isReinherited, @Nullable String conflictResolutionStrategy, boolean localDefaultResolvesWithAncestorValue) {
+        this(isReinherited, conflictResolutionStrategy, localDefaultResolvesWithAncestorValue, true);
+    }
+    
+    protected BasicConfigInheritance(boolean isReinherited, @Nullable String conflictResolutionStrategy, boolean localDefaultResolvesWithAncestorValue, boolean ancestorDefaultInheritable) {
         super();
         this.isReinherited = isReinherited;
         this.conflictResolutionStrategy = conflictResolutionStrategy;
-        this.useLocalDefaultValue = useLocalDefaultValue;
+        this.useLocalDefaultValue = null;
+        this.localDefaultResolvesWithAncestorValue = localDefaultResolvesWithAncestorValue;
+        this.ancestorDefaultInheritable = ancestorDefaultInheritable;
     }
 
     @Override @Deprecated
@@ -82,9 +211,17 @@ public class BasicConfigInheritance implements ConfigInheritance {
     
     protected <TContainer, TValue> void checkInheritanceContext(ConfigValueAtContainer<TContainer, TValue> local, ConfigInheritanceContext context) {
         ConfigInheritance rightInheritance = ConfigInheritances.findInheritance(local, context, this);
-        if (!equals(rightInheritance)) 
+        if (!isSameRootInstanceAs(rightInheritance)) {
             throw new IllegalStateException("Low level inheritance computation error: caller should invoke on "+rightInheritance+" "
                 + "(the inheritance at "+local+"), not "+this);
+        }
+    }
+
+    private boolean isSameRootInstanceAs(ConfigInheritance other) {
+        if (other==null) return false;
+        if (this==other) return true;
+        if (other instanceof DelegatingConfigInheritance) return isSameRootInstanceAs( ((DelegatingConfigInheritance)other).getDelegate() );
+        return false;
     }
 
     @Override
@@ -102,7 +239,7 @@ public class BasicConfigInheritance implements ConfigInheritance {
         if (parent==null) return false;
         if (CONFLICT_RESOLUTION_STRATEGY_OVERWRITE.equals(conflictResolutionStrategy)) {
             // overwrite means ignore if there's an explicit value, or we're using the local default
-            return !local.isValueExplicitlySet() && !getUseLocalDefaultValue();
+            return !local.isValueExplicitlySet() && !getLocalDefaultResolvesWithAncestorValue();
         }
         return true;
     }
@@ -115,12 +252,12 @@ public class BasicConfigInheritance implements ConfigInheritance {
         
         checkInheritanceContext(local, context);
         
-        if (!parent.isValueExplicitlySet() && !getUseLocalDefaultValue()) 
+        if (!parent.isValueExplicitlySet() && !getLocalDefaultResolvesWithAncestorValue()) 
             return ReferenceWithError.newInstanceWithoutError(new BasicConfigValueAtContainer<TContainer,TValue>(local));
         
         // parent explicitly set (or we might have to merge defaults), 
         // and by the contract of this method we can assume reinheritable
-        if (!local.isValueExplicitlySet() && !getUseLocalDefaultValue())
+        if (!local.isValueExplicitlySet() && !getLocalDefaultResolvesWithAncestorValue())
             return ReferenceWithError.newInstanceWithoutError(new BasicConfigValueAtContainer<TContainer,TValue>(parent));
 
         // both explicitly set or defaults applicable, and not overwriting; it should be merge, or something from child
@@ -170,12 +307,88 @@ public class BasicConfigInheritance implements ConfigInheritance {
         return conflictResolutionStrategy;
     }
     
+    @Deprecated /** @deprecated since 0.10.0 when it was introduced, prefer {@link #getLocalDefaultResolvesWithAncestorValue()} */
     public boolean getUseLocalDefaultValue() {
-        return useLocalDefaultValue;
+        return getLocalDefaultResolvesWithAncestorValue();
+    }
+
+    /** see {@link #localDefaultResolvesWithAncestorValue} */
+    public boolean getLocalDefaultResolvesWithAncestorValue() {
+        if (localDefaultResolvesWithAncestorValue==null) {
+            // in case some legacy path is using an improperly deserialized object
+            log.warn("Encountered legacy "+this+" with null localDefaultResolvesWithAncestorValue; transforming", new Throwable("stack trace for legacy "+this));
+            readResolve();
+        }
+        return localDefaultResolvesWithAncestorValue;
+    }
+    
+    public boolean getAncestorDefaultInheritable() {
+        if (ancestorDefaultInheritable==null) {
+            log.warn("Encountered legacy "+this+" with null ancestorDefaultInheritable; transforming", new Throwable("stack trace for legacy "+this));
+            readResolve();
+        }
+        return ancestorDefaultInheritable;
     }
 
+    // standard deserialization method
+    private ConfigInheritance readResolve() {
+        try {
+            if (useLocalDefaultValue!=null) {
+                // move away from useLocalDefaultValue to localDefaultResolvesWithAncestorValue
+                
+                Field fNew = getClass().getDeclaredField("localDefaultResolvesWithAncestorValue");
+                fNew.setAccessible(true);
+                Field fOld = getClass().getDeclaredField("useLocalDefaultValue");
+                fOld.setAccessible(true);
+                
+                if (fNew.get(this)==null) {
+                    fNew.set(this, useLocalDefaultValue);
+                } else {
+                    if (!fNew.get(this).equals(useLocalDefaultValue)) {
+                        throw new IllegalStateException("Incompatible values detected for "+fOld+" ("+fOld.get(this)+") and "+fNew+" ("+fNew.get(this)+")");
+                    }
+                }
+                fOld.set(this, null);
+            }
+            
+            if (ancestorDefaultInheritable==null) {
+                Field f = getClass().getDeclaredField("ancestorDefaultInheritable");
+                f.setAccessible(true);
+                f.set(this, true);
+            }
+            
+        } catch (Exception e) {
+            throw Exceptions.propagate(e);
+        }
+        
+        for (ConfigInheritance knownMode: Arrays.asList(
+                NOT_REINHERITED, NOT_REINHERITED_ELSE_DEEP_MERGE, NEVER_INHERITED, OVERWRITE, DEEP_MERGE)) {
+            if (equals(knownMode)) return knownMode;
+        }
+        if (equals(new BasicConfigInheritance(false, CONFLICT_RESOLUTION_STRATEGY_OVERWRITE, true, true))) {
+            // ignore the ancestor flag for this mode
+            return NEVER_INHERITED;
+        }
+        
+        return this;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj==null) return false;
+        if (obj instanceof DelegatingConfigInheritance) return equals( ((DelegatingConfigInheritance)obj).getDelegate() );
+        if (obj.getClass().equals(BasicConfigInheritance.class)) {
+            BasicConfigInheritance b = (BasicConfigInheritance)obj;
+            return Objects.equals(conflictResolutionStrategy, b.conflictResolutionStrategy) &&
+                Objects.equals(isReinherited, b.isReinherited) &&
+                Objects.equals(getLocalDefaultResolvesWithAncestorValue(), b.getLocalDefaultResolvesWithAncestorValue()) &&
+                Objects.equals(getAncestorDefaultInheritable(), b.getAncestorDefaultInheritable());
+        }
+        return false;
+    }
+    
     @Override
     public String toString() {
-        return super.toString()+"[reinherit="+isReinherited()+"; strategy="+getConflictResolutionStrategy()+"; useLocal="+getUseLocalDefaultValue()+"]";
+        return super.toString()+"[reinherit="+isReinherited()+"; strategy="+getConflictResolutionStrategy()+"; localDefaultResolvesWithAncestor="+localDefaultResolvesWithAncestorValue+"]";
     }
 }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/53ae1c61/core/src/main/java/org/apache/brooklyn/util/core/xstream/ClassRenamingMapper.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/util/core/xstream/ClassRenamingMapper.java b/core/src/main/java/org/apache/brooklyn/util/core/xstream/ClassRenamingMapper.java
index aae4a6e..a30fdfb 100644
--- a/core/src/main/java/org/apache/brooklyn/util/core/xstream/ClassRenamingMapper.java
+++ b/core/src/main/java/org/apache/brooklyn/util/core/xstream/ClassRenamingMapper.java
@@ -22,11 +22,11 @@ import static com.google.common.base.Preconditions.checkNotNull;
 
 import java.util.Map;
 
+import org.apache.brooklyn.util.guava.Maybe;
 import org.apache.brooklyn.util.javalang.Reflections;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import com.google.common.base.Optional;
 import com.thoughtworks.xstream.mapper.Mapper;
 import com.thoughtworks.xstream.mapper.MapperWrapper;
 
@@ -42,7 +42,7 @@ public class ClassRenamingMapper extends MapperWrapper {
     
     @Override
     public Class<?> realClass(String elementName) {
-        Optional<String> elementNameOpt = Reflections.tryFindMappedName(nameToType, elementName);
+        Maybe<String> elementNameOpt = Reflections.findMappedNameMaybe(nameToType, elementName);
         if (elementNameOpt.isPresent()) {
             LOG.debug("Mapping class '"+elementName+"' to '"+elementNameOpt.get()+"'");
             elementName = elementNameOpt.get();

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/53ae1c61/core/src/test/java/org/apache/brooklyn/core/mgmt/rebind/RebindConfigInheritanceTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/core/mgmt/rebind/RebindConfigInheritanceTest.java b/core/src/test/java/org/apache/brooklyn/core/mgmt/rebind/RebindConfigInheritanceTest.java
new file mode 100644
index 0000000..2eeef35
--- /dev/null
+++ b/core/src/test/java/org/apache/brooklyn/core/mgmt/rebind/RebindConfigInheritanceTest.java
@@ -0,0 +1,145 @@
+/*
+ * 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.mgmt.rebind;
+
+import java.io.File;
+import java.io.FileReader;
+
+import org.apache.brooklyn.api.entity.Application;
+import org.apache.brooklyn.api.entity.EntitySpec;
+import org.apache.brooklyn.config.ConfigInheritance;
+import org.apache.brooklyn.config.ConfigKey;
+import org.apache.brooklyn.core.config.BasicConfigInheritance;
+import org.apache.brooklyn.core.config.ConfigKeys;
+import org.apache.brooklyn.core.config.ConfigKeys.InheritanceContext;
+import org.apache.brooklyn.core.config.ConfigPredicates;
+import org.apache.brooklyn.core.entity.EntityAsserts;
+import org.apache.brooklyn.core.test.entity.TestEntity;
+import org.apache.brooklyn.test.Asserts;
+import org.apache.brooklyn.util.os.Os;
+import org.apache.brooklyn.util.stream.Streams;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Charsets;
+import com.google.common.base.Joiner;
+import com.google.common.collect.Iterables;
+import com.google.common.io.Files;
+
+public class RebindConfigInheritanceTest extends RebindTestFixtureWithApp {
+
+    private static final Logger log = LoggerFactory.getLogger(RebindConfigInheritanceTest.class);
+
+    ConfigKey<String> key1 = ConfigKeys.builder(String.class, "key1").runtimeInheritance(BasicConfigInheritance.NEVER_INHERITED).build();
+    String origMemento, newMemento;
+    Application rebindedApp;
+    
+    @Override
+    @BeforeMethod(alwaysRun=true)
+    public void setUp() throws Exception {
+        super.setUp();
+    }
+        
+    protected void doReadConfigInheritance(String label, String entityId) throws Exception {
+        String mementoFilename = "config-inheritance-"+label+"-"+entityId;
+        origMemento = Streams.readFullyString(getClass().getResourceAsStream(mementoFilename));
+        
+        File persistedEntityFile = new File(mementoDir, Os.mergePaths("entities", entityId));
+        Files.write(origMemento.getBytes(), persistedEntityFile);
+        
+        // we'll make assertions on what we've loaded
+        rebind();
+        rebindedApp = (Application) newManagementContext.lookup(entityId);
+        
+        // we'll also make assertions on the contents written out
+        RebindTestUtils.waitForPersisted(mgmt());
+        newMemento = Joiner.on("\n").join(Files.readLines(persistedEntityFile, Charsets.UTF_8));
+    }
+    
+    /** also see {@link RebindWithDeserializingClassRenamesTest} */
+    @Test
+    public void testPreBasicConfigInheritance_2016_07() throws Exception {
+        doReadConfigInheritance("prebasic-2016-07", "toruf2wxg4");
+        
+        ConfigKey<?> k = Iterables.getOnlyElement( rebindedApp.config().findKeys(ConfigPredicates.nameEqualTo("my.config.inheritanceMerged")) );
+        
+        Asserts.assertStringContains(origMemento, "<parentInheritance class=\"org.apache.brooklyn.config.ConfigInheritance$Merged\"/>");
+        Asserts.assertStringDoesNotContain(origMemento, BasicConfigInheritance.DEEP_MERGE.getClass().getName());
+
+        // should now convert it to BasicConfigInheritance.DEEP_MERGE
+        Asserts.assertStringDoesNotContain(newMemento, "ConfigInheritance$Merged");
+        Asserts.assertStringDoesNotContain(newMemento, "ConfigInheritance$Legacy$Merged");
+        Asserts.assertStringContains(newMemento, BasicConfigInheritance.DEEP_MERGE.getClass().getName());
+        
+        ConfigInheritance inh = k.getInheritanceByContext(InheritanceContext.RUNTIME_MANAGEMENT);
+        Assert.assertEquals(inh, BasicConfigInheritance.DEEP_MERGE);
+    }
+    
+    @Test
+    public void testBasicConfigInheritance_2016_10() throws Exception {
+        doReadConfigInheritance("basic-2016-10", "wj5s8u9h73");
+
+        checkNewAppNonInheritingKey1(rebindedApp);
+        
+        Asserts.assertStringContains(origMemento, "isReinherited");
+        Asserts.assertStringDoesNotContain(origMemento, "NEVER_INHERITED");
+        Asserts.assertStringDoesNotContain(origMemento, "NeverInherited");
+        
+        // should write it out as NeverInherited
+        Asserts.assertStringDoesNotContain(newMemento, "isReinherited");
+        Asserts.assertStringContains(newMemento, "NeverInherited");
+    }
+
+    @Test
+    public void testReadConfigInheritance_2016_11() throws Exception {
+        doReadConfigInheritance("basic-2016-11", "kmpez5fznt");
+        checkNewAppNonInheritingKey1(rebindedApp);
+        
+        String origMementoWithoutLicenseHeader = origMemento.substring(origMemento.indexOf("<entity>"));
+        Asserts.assertEquals(origMementoWithoutLicenseHeader, newMemento);
+    }
+    
+    @Test
+    public void testBasicConfigInheritanceProgrammatic() throws Exception {
+        origApp.config().set(key1, "1");
+        
+        RebindOptions opts = RebindOptions.create();
+        opts.keepSameInstance = true;
+        
+        rebind(opts);
+        
+        String entityFile = Streams.readFully(new FileReader(new File(opts.mementoDir, "entities/"+origApp.getApplicationId())));
+        log.info("persisted file with config inheritance programmatic:\n"+entityFile);
+        
+        checkNewAppNonInheritingKey1(newApp);
+    }
+
+    protected void checkNewAppNonInheritingKey1(Application app) {
+        // check key
+        EntityAsserts.assertConfigEquals(app, key1, "1");
+        
+        // check not inherited
+        TestEntity entity = app.addChild(EntitySpec.create(TestEntity.class));
+        EntityAsserts.assertConfigEquals(entity, key1, null);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/53ae1c61/core/src/test/java/org/apache/brooklyn/core/mgmt/rebind/RebindOptions.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/core/mgmt/rebind/RebindOptions.java b/core/src/test/java/org/apache/brooklyn/core/mgmt/rebind/RebindOptions.java
index bfa65d4..2c7514b 100644
--- a/core/src/test/java/org/apache/brooklyn/core/mgmt/rebind/RebindOptions.java
+++ b/core/src/test/java/org/apache/brooklyn/core/mgmt/rebind/RebindOptions.java
@@ -37,6 +37,10 @@ import com.google.common.collect.Iterables;
  * See {@link RebindTestFixture#rebind(RebindOptions)} and {@link RebindTestUtils#rebind(RebindOptions)}.
  */
 public class RebindOptions {
+    /** whether to keep the same instance, rather than make a copy; 
+     * if true, this {@link RebindOptions} may be changed in-place when passed to {@link RebindTestUtils#rebind(RebindOptions)} */
+    public boolean keepSameInstance;
+    
     public boolean checkSerializable;
     public boolean terminateOrigManagementContext;
     public RebindExceptionHandler exceptionHandler;
@@ -55,6 +59,8 @@ public class RebindOptions {
         return new RebindOptions();
     }
     public static RebindOptions create(RebindOptions options) {
+        if (options.keepSameInstance) return options;
+        
         RebindOptions result = create();
         result.checkSerializable(options.checkSerializable);
         result.terminateOrigManagementContext(options.terminateOrigManagementContext);

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/53ae1c61/core/src/test/java/org/apache/brooklyn/core/mgmt/rebind/RebindTestFixture.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/core/mgmt/rebind/RebindTestFixture.java b/core/src/test/java/org/apache/brooklyn/core/mgmt/rebind/RebindTestFixture.java
index ec8aae9..41d51c2 100644
--- a/core/src/test/java/org/apache/brooklyn/core/mgmt/rebind/RebindTestFixture.java
+++ b/core/src/test/java/org/apache/brooklyn/core/mgmt/rebind/RebindTestFixture.java
@@ -29,7 +29,6 @@ import java.util.concurrent.Callable;
 import org.apache.brooklyn.api.catalog.BrooklynCatalog;
 import org.apache.brooklyn.api.catalog.CatalogItem;
 import org.apache.brooklyn.api.entity.Application;
-import org.apache.brooklyn.api.entity.Entity;
 import org.apache.brooklyn.api.mgmt.ManagementContext;
 import org.apache.brooklyn.api.mgmt.Task;
 import org.apache.brooklyn.api.mgmt.ha.HighAvailabilityMode;

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/53ae1c61/core/src/test/java/org/apache/brooklyn/core/mgmt/rebind/RebindWithDeserializingClassRenamesTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/core/mgmt/rebind/RebindWithDeserializingClassRenamesTest.java b/core/src/test/java/org/apache/brooklyn/core/mgmt/rebind/RebindWithDeserializingClassRenamesTest.java
index c5e48c9..a66c7d9 100644
--- a/core/src/test/java/org/apache/brooklyn/core/mgmt/rebind/RebindWithDeserializingClassRenamesTest.java
+++ b/core/src/test/java/org/apache/brooklyn/core/mgmt/rebind/RebindWithDeserializingClassRenamesTest.java
@@ -19,18 +19,17 @@
 package org.apache.brooklyn.core.mgmt.rebind;
 
 import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertFalse;
-import static org.testng.Assert.assertTrue;
 
 import java.io.File;
-import java.lang.reflect.Method;
 import java.util.Map;
 
 import org.apache.brooklyn.config.ConfigInheritance;
 import org.apache.brooklyn.config.ConfigKey;
+import org.apache.brooklyn.core.config.BasicConfigInheritance;
 import org.apache.brooklyn.core.config.ConfigKeys.InheritanceContext;
 import org.apache.brooklyn.core.config.ConfigPredicates;
 import org.apache.brooklyn.core.entity.EntityInternal;
+import org.apache.brooklyn.test.Asserts;
 import org.apache.brooklyn.util.core.ResourceUtils;
 import org.apache.brooklyn.util.os.Os;
 import org.testng.annotations.Test;
@@ -43,13 +42,13 @@ import com.google.common.io.Files;
 
 public class RebindWithDeserializingClassRenamesTest extends RebindTestFixtureWithApp {
 
+    /** also see {@link RebindConfigInheritanceTest} */
     @Test
-    @SuppressWarnings("deprecation")
     public void testRebindWillRenameLegacyConfigInheritance() throws Exception {
         Map<String, String> expectedTransforms = ImmutableMap.<String, String>builder()
-                .put("org.apache.brooklyn.config.ConfigInheritance$None", "org.apache.brooklyn.config.ConfigInheritance$Legacy$None")
-                .put("org.apache.brooklyn.config.ConfigInheritance$Always", "org.apache.brooklyn.config.ConfigInheritance$Legacy$Always")
-                .put("org.apache.brooklyn.config.ConfigInheritance$Merged", "org.apache.brooklyn.config.ConfigInheritance$Legacy$Merged")
+                .put("org.apache.brooklyn.config.ConfigInheritance$None", BasicConfigInheritance.NOT_REINHERITED.getClass().getName())
+                .put("org.apache.brooklyn.config.ConfigInheritance$Always", BasicConfigInheritance.OVERWRITE.getClass().getName())
+                .put("org.apache.brooklyn.config.ConfigInheritance$Merged", BasicConfigInheritance.DEEP_MERGE.getClass().getName())
                 .build();
         
         String entityId = "toruf2wxg4";
@@ -58,8 +57,8 @@ public class RebindWithDeserializingClassRenamesTest extends RebindTestFixtureWi
 
         // Orig state contains the old names (and not the new names)
         for (Map.Entry<String, String> entry : expectedTransforms.entrySet()) {
-            assertTrue(persistedEntity.contains(entry.getKey()));
-            assertFalse(persistedEntity.contains(entry.getValue()));
+            Asserts.assertStringContains(persistedEntity, entry.getKey());
+            Asserts.assertStringDoesNotContain(persistedEntity, entry.getValue());
         }
         
         File persistedEntityFile = new File(mementoDir, Os.mergePaths("entities", entityId));
@@ -71,8 +70,8 @@ public class RebindWithDeserializingClassRenamesTest extends RebindTestFixtureWi
         RebindTestUtils.waitForPersisted(mgmt());
         String newPersistedEntity = Joiner.on("\n").join(Files.readLines(persistedEntityFile, Charsets.UTF_8));
         for (Map.Entry<String, String> entry : expectedTransforms.entrySet()) {
-            assertFalse(newPersistedEntity.contains(entry.getKey()));
-            assertTrue(newPersistedEntity.contains(entry.getValue()));
+            Asserts.assertStringDoesNotContain(newPersistedEntity, entry.getKey());
+            Asserts.assertStringContains(newPersistedEntity, entry.getValue());
         }
 
         // Check the config keys are as expected 
@@ -82,17 +81,13 @@ public class RebindWithDeserializingClassRenamesTest extends RebindTestFixtureWi
         ConfigKey<?> keyWithInheritanceAlways = Iterables.find(config.keySet(), ConfigPredicates.nameEqualTo("my.config.inheritanceAlways"));
         ConfigKey<?> keyWithInheritanceMerged = Iterables.find(config.keySet(), ConfigPredicates.nameEqualTo("my.config.inheritanceMerged"));
         
-        assertLegacyConfigRuntimInheritanceMode(keyWithInheritanceNone, ConfigInheritance.InheritanceMode.NONE);
-        assertLegacyConfigRuntimInheritanceMode(keyWithInheritanceAlways, ConfigInheritance.InheritanceMode.IF_NO_EXPLICIT_VALUE);
-        assertLegacyConfigRuntimInheritanceMode(keyWithInheritanceMerged, ConfigInheritance.InheritanceMode.DEEP_MERGE);
+        assertConfigRuntimeInheritanceMode(keyWithInheritanceNone, BasicConfigInheritance.NOT_REINHERITED);
+        assertConfigRuntimeInheritanceMode(keyWithInheritanceAlways, BasicConfigInheritance.OVERWRITE);
+        assertConfigRuntimeInheritanceMode(keyWithInheritanceMerged, BasicConfigInheritance.DEEP_MERGE);
     }
 
-    @SuppressWarnings("deprecation")
-    private void assertLegacyConfigRuntimInheritanceMode(ConfigKey<?> key, ConfigInheritance.InheritanceMode expected) throws Exception {
+    private void assertConfigRuntimeInheritanceMode(ConfigKey<?> key, ConfigInheritance expected) throws Exception {
         ConfigInheritance val = key.getInheritanceByContext().get(InheritanceContext.RUNTIME_MANAGEMENT);
-        Method method = val.getClass().getDeclaredMethod("getMode");
-        method.setAccessible(true);
-        ConfigInheritance.InheritanceMode mode = (ConfigInheritance.InheritanceMode) method.invoke(val);
-        assertEquals(mode, expected);
+        assertEquals(val, expected);
     }
 }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/53ae1c61/core/src/test/resources/org/apache/brooklyn/core/mgmt/rebind/config-inheritance-basic-2016-10-wj5s8u9h73
----------------------------------------------------------------------
diff --git a/core/src/test/resources/org/apache/brooklyn/core/mgmt/rebind/config-inheritance-basic-2016-10-wj5s8u9h73 b/core/src/test/resources/org/apache/brooklyn/core/mgmt/rebind/config-inheritance-basic-2016-10-wj5s8u9h73
new file mode 100644
index 0000000..d1bc6ca
--- /dev/null
+++ b/core/src/test/resources/org/apache/brooklyn/core/mgmt/rebind/config-inheritance-basic-2016-10-wj5s8u9h73
@@ -0,0 +1,50 @@
+<!--
+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.
+-->
+
+<entity>
+  <brooklynVersion>0.10.0-SNAPSHOT</brooklynVersion>
+  <type>org.apache.brooklyn.core.test.entity.TestApplicationNoEnrichersImpl</type>
+  <id>wj5s8u9h73</id>
+  <displayName>TestApplicationNoEnrichersImpl:wj5s</displayName>
+  <config>
+    <key1>1</key1>
+  </config>
+  <attributes>
+    <entity.id>wj5s8u9h73</entity.id>
+    <application.id>wj5s8u9h73</application.id>
+    <catalog.id>
+      <null/>
+    </catalog.id>
+  </attributes>
+  <configKeys>
+    <key1>
+      <configKey>
+        <name>key1</name>
+        <type>java.lang.String</type>
+        <reconfigurable>false</reconfigurable>
+        <runtimeInheritance class="org.apache.brooklyn.core.config.BasicConfigInheritance">
+          <isReinherited>false</isReinherited>
+          <conflictResolutionStrategy>overwrite</conflictResolutionStrategy>
+          <useLocalDefaultValue>true</useLocalDefaultValue>
+        </runtimeInheritance>
+        <constraint class="com.google.common.base.Predicates$ObjectPredicate">ALWAYS_TRUE</constraint>
+      </configKey>
+    </key1>
+  </configKeys>
+</entity>

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/53ae1c61/core/src/test/resources/org/apache/brooklyn/core/mgmt/rebind/config-inheritance-basic-2016-11-kmpez5fznt
----------------------------------------------------------------------
diff --git a/core/src/test/resources/org/apache/brooklyn/core/mgmt/rebind/config-inheritance-basic-2016-11-kmpez5fznt b/core/src/test/resources/org/apache/brooklyn/core/mgmt/rebind/config-inheritance-basic-2016-11-kmpez5fznt
new file mode 100644
index 0000000..cf635ea
--- /dev/null
+++ b/core/src/test/resources/org/apache/brooklyn/core/mgmt/rebind/config-inheritance-basic-2016-11-kmpez5fznt
@@ -0,0 +1,46 @@
+<!--
+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.
+-->
+
+<entity>
+  <brooklynVersion>0.10.0-SNAPSHOT</brooklynVersion>
+  <type>org.apache.brooklyn.core.test.entity.TestApplicationNoEnrichersImpl</type>
+  <id>kmpez5fznt</id>
+  <displayName>TestApplicationNoEnrichersImpl:kmpe</displayName>
+  <config>
+    <key1>1</key1>
+  </config>
+  <attributes>
+    <entity.id>kmpez5fznt</entity.id>
+    <application.id>kmpez5fznt</application.id>
+    <catalog.id>
+      <null/>
+    </catalog.id>
+  </attributes>
+  <configKeys>
+    <key1>
+      <configKey>
+        <name>key1</name>
+        <type>java.lang.String</type>
+        <reconfigurable>false</reconfigurable>
+        <runtimeInheritance class="org.apache.brooklyn.core.config.BasicConfigInheritance$NeverInherited"/>
+        <constraint class="com.google.common.base.Predicates$ObjectPredicate">ALWAYS_TRUE</constraint>
+      </configKey>
+    </key1>
+  </configKeys>
+</entity>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/53ae1c61/core/src/test/resources/org/apache/brooklyn/core/mgmt/rebind/config-inheritance-prebasic-2016-07-toruf2wxg4
----------------------------------------------------------------------
diff --git a/core/src/test/resources/org/apache/brooklyn/core/mgmt/rebind/config-inheritance-prebasic-2016-07-toruf2wxg4 b/core/src/test/resources/org/apache/brooklyn/core/mgmt/rebind/config-inheritance-prebasic-2016-07-toruf2wxg4
new file mode 100644
index 0000000..7a59b4e
--- /dev/null
+++ b/core/src/test/resources/org/apache/brooklyn/core/mgmt/rebind/config-inheritance-prebasic-2016-07-toruf2wxg4
@@ -0,0 +1,137 @@
+<!--
+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.
+-->
+
+<entity>
+  <brooklynVersion>0.10.0-20160908.1322</brooklynVersion>
+  <type>org.apache.brooklyn.entity.stock.BasicApplicationImpl</type>
+  <id>toruf2wxg4</id>
+  <displayName>Application (toruf2wxg4)</displayName>
+  <tags>
+    <org.apache.brooklyn.core.mgmt.BrooklynTags_-NamedStringTag>
+      <kind>yaml_spec</kind>
+      <contents>services:
+- type: org.apache.brooklyn.entity.stock.BasicApplication</contents>
+    </org.apache.brooklyn.core.mgmt.BrooklynTags_-NamedStringTag>
+  </tags>
+  <config>
+    <my.config.inheritanceNone>myval</my.config.inheritanceNone>
+    <my.config.inheritanceMerged>myval</my.config.inheritanceMerged>
+    <my.config.inheritanceAlways>myval</my.config.inheritanceAlways>
+  </config>
+  <attributes>
+    <service.notUp.indicators>
+      <MutableMap/>
+    </service.notUp.indicators>
+    <entity.id>toruf2wxg4</entity.id>
+    <application.id>toruf2wxg4</application.id>
+    <catalog.id>
+      <null/>
+    </catalog.id>
+    <service.isUp type="boolean">true</service.isUp>
+    <service.problems>
+      <MutableMap/>
+    </service.problems>
+    <service.state type="org.apache.brooklyn.core.entity.lifecycle.Lifecycle">RUNNING</service.state>
+    <service.state.expected>
+      <org.apache.brooklyn.core.entity.lifecycle.Lifecycle_-Transition>
+        <state>RUNNING</state>
+        <timestampUtc>1476100554627</timestampUtc>
+      </org.apache.brooklyn.core.entity.lifecycle.Lifecycle_-Transition>
+    </service.state.expected>
+  </attributes>
+
+  <configKeys>
+    <!-- Hand-crafted config, for testing -->
+    <my.config.inheritanceNone>
+      <configKey>
+        <name>my.config.inheritanceNone</name>
+        <type>java.lang.String</type>
+        <reconfigurable>false</reconfigurable>
+        <parentInheritance class="org.apache.brooklyn.config.ConfigInheritance$None"/>
+      </configKey>
+    </my.config.inheritanceNone>
+    <my.config.inheritanceMerged>
+      <configKey>
+        <name>my.config.inheritanceMerged</name>
+        <type>java.lang.String</type>
+        <reconfigurable>false</reconfigurable>
+        <parentInheritance class="org.apache.brooklyn.config.ConfigInheritance$Merged"/>
+      </configKey>
+    </my.config.inheritanceMerged>
+    <my.config.inheritanceAlways>
+      <configKey>
+        <name>my.config.inheritanceAlways</name>
+        <type>java.lang.String</type>
+        <reconfigurable>false</reconfigurable>
+        <parentInheritance class="org.apache.brooklyn.config.ConfigInheritance$Always"/>
+      </configKey>
+    </my.config.inheritanceAlways>
+  </configKeys>
+  
+  <attributeKeys>
+    <service.notUp.indicators>
+      <attributeSensor>
+        <typeToken class="org.apache.brooklyn.core.entity.Attributes$1" resolves-to="com.google.common.reflect.TypeToken$SimpleTypeToken">
+          <runtimeType class="com.google.common.reflect.Types$ParameterizedTypeImpl">
+            <argumentsList>
+              <java-class>java.lang.String</java-class>
+              <java-class>java.lang.Object</java-class>
+            </argumentsList>
+            <rawType>java.util.Map</rawType>
+          </runtimeType>
+        </typeToken>
+        <name>service.notUp.indicators</name>
+        <description>A map of namespaced indicators that the service is not up</description>
+        <persistence>REQUIRED</persistence>
+      </attributeSensor>
+    </service.notUp.indicators>
+    <service.problems>
+      <attributeSensor>
+        <typeToken class="org.apache.brooklyn.core.entity.Attributes$3" resolves-to="com.google.common.reflect.TypeToken$SimpleTypeToken">
+          <runtimeType class="com.google.common.reflect.Types$ParameterizedTypeImpl">
+            <argumentsList>
+              <java-class>java.lang.String</java-class>
+              <java-class>java.lang.Object</java-class>
+            </argumentsList>
+            <rawType>java.util.Map</rawType>
+          </runtimeType>
+        </typeToken>
+        <name>service.problems</name>
+        <description>A map of namespaced indicators of problems with a service</description>
+        <persistence>REQUIRED</persistence>
+      </attributeSensor>
+    </service.problems>
+    <service.state>
+      <attributeSensor>
+        <type>org.apache.brooklyn.core.entity.lifecycle.Lifecycle</type>
+        <name>service.state</name>
+        <description>Actual lifecycle state of the service</description>
+        <persistence>REQUIRED</persistence>
+      </attributeSensor>
+    </service.state>
+    <service.state.expected>
+      <attributeSensor>
+        <type>org.apache.brooklyn.core.entity.lifecycle.Lifecycle$Transition</type>
+        <name>service.state.expected</name>
+        <description>Last controlled change to service state, indicating what the expected state should be</description>
+        <persistence>REQUIRED</persistence>
+      </attributeSensor>
+    </service.state.expected>
+  </attributeKeys>
+</entity>

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/53ae1c61/utils/common/src/main/java/org/apache/brooklyn/config/ConfigInheritance.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/org/apache/brooklyn/config/ConfigInheritance.java b/utils/common/src/main/java/org/apache/brooklyn/config/ConfigInheritance.java
index 5b96a7b..3100c8c 100644
--- a/utils/common/src/main/java/org/apache/brooklyn/config/ConfigInheritance.java
+++ b/utils/common/src/main/java/org/apache/brooklyn/config/ConfigInheritance.java
@@ -26,6 +26,7 @@ import javax.annotation.Nullable;
 
 import org.apache.brooklyn.config.ConfigInheritances.BasicConfigValueAtContainer;
 import org.apache.brooklyn.util.collections.CollectionMerger;
+import org.apache.brooklyn.util.collections.MutableMap;
 import org.apache.brooklyn.util.exceptions.ReferenceWithError;
 import org.apache.brooklyn.util.guava.Maybe;
 import org.apache.brooklyn.util.text.Strings;
@@ -47,11 +48,11 @@ public interface ConfigInheritance extends Serializable {
         DEEP_MERGE
     }
     
-    /** @deprecated since 0.10.0 see implementations of this interface */ @Deprecated
+    /** @deprecated since 0.10.0 see implementations of this interface (look for NOT_REINHERITED, or possibly NEVER_REINHERITED) */ @Deprecated
     public static final ConfigInheritance NONE = new Legacy.None();
-    /** @deprecated since 0.10.0 see implementations of this interface */ @Deprecated
+    /** @deprecated since 0.10.0 see implementations of this interface (look for OVERWRITE) */ @Deprecated
     public static final ConfigInheritance ALWAYS = new Legacy.Always();
-    /** @deprecated since 0.10.0 see implementations of this interface */ @Deprecated
+    /** @deprecated since 0.10.0 see implementations of this interface (look for the same name, DEEP_MERGE) */ @Deprecated
     public static final ConfigInheritance DEEP_MERGE = new Legacy.Merged();
     
     /** @deprecated since 0.10.0 more complex inheritance conditions now require other methods */
@@ -133,6 +134,16 @@ public interface ConfigInheritance extends Serializable {
                 throw new IllegalArgumentException("Invalid config-inheritance '"+val+"' (legal values are none, always or merge)");
             }
         }
+        private static Map<ConfigInheritance,ConfigInheritance> REPLACEMENTS = MutableMap.of();
+        /** used to assist in migration to new classes */
+        public static void registerReplacement(ConfigInheritance old, ConfigInheritance replacement) {
+            REPLACEMENTS.put(old, replacement);
+        }
+        private static ConfigInheritance orReplacement(ConfigInheritance orig) {
+            ConfigInheritance repl = REPLACEMENTS.get(orig);
+            if (repl!=null) return repl;
+            return orig;
+        }
         private abstract static class LegacyAbstractConversion implements ConfigInheritance {
 
             @Override
@@ -200,18 +211,30 @@ public interface ConfigInheritance extends Serializable {
             public InheritanceMode getMode() {
                 return InheritanceMode.IF_NO_EXPLICIT_VALUE;
             }
+            @SuppressWarnings("unused") // standard deserialization method
+            private ConfigInheritance readResolve() {
+                return orReplacement(ConfigInheritance.ALWAYS);
+            }
         }
         private static class None extends LegacyAbstractConversion {
             @Override
             public InheritanceMode getMode() {
                 return InheritanceMode.NONE;
             }
+            @SuppressWarnings("unused") // standard deserialization method
+            private ConfigInheritance readResolve() {
+                return orReplacement(ConfigInheritance.NONE);
+            }
         }
         private static class Merged extends LegacyAbstractConversion {
             @Override
             public InheritanceMode getMode() {
                 return InheritanceMode.DEEP_MERGE;
             }
+            @SuppressWarnings("unused") // standard deserialization method
+            private ConfigInheritance readResolve() {
+                return orReplacement(ConfigInheritance.DEEP_MERGE);
+            }
         }
     }