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 2015/12/23 12:06:41 UTC

[18/71] [abbrv] incubator-brooklyn git commit: Merge commit 'e430723' into reorg2

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/018a0e15/brooklyn-server/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlLocationTest.java
----------------------------------------------------------------------
diff --cc brooklyn-server/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlLocationTest.java
index 0000000,291a06a..47925e5
mode 000000,100644..100644
--- a/brooklyn-server/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlLocationTest.java
+++ b/brooklyn-server/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlLocationTest.java
@@@ -1,0 -1,252 +1,252 @@@
+ /*
+  * 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.camp.brooklyn.catalog;
+ 
+ import static org.testng.Assert.assertEquals;
+ import static org.testng.Assert.assertNull;
+ 
+ import java.util.Collection;
+ import java.util.List;
+ 
+ import org.apache.brooklyn.api.entity.Entity;
+ import org.apache.brooklyn.api.location.Location;
+ import org.apache.brooklyn.api.location.LocationDefinition;
+ import org.apache.brooklyn.api.location.LocationSpec;
+ import org.apache.brooklyn.api.typereg.OsgiBundleWithUrl;
+ import org.apache.brooklyn.api.typereg.RegisteredType;
+ import org.apache.brooklyn.camp.brooklyn.AbstractYamlTest;
+ import org.apache.brooklyn.core.config.BasicConfigKey;
+ import org.apache.brooklyn.core.mgmt.osgi.OsgiStandaloneTest;
+ import org.apache.brooklyn.core.typereg.RegisteredTypePredicates;
+ import org.apache.brooklyn.core.typereg.RegisteredTypes;
+ import org.apache.brooklyn.location.localhost.LocalhostMachineProvisioningLocation;
+ import org.apache.brooklyn.test.support.TestResourceUnavailableException;
+ import org.apache.brooklyn.util.text.StringFunctions;
+ import org.testng.Assert;
+ import org.testng.annotations.AfterMethod;
+ import org.testng.annotations.Test;
+ 
+ import com.google.common.collect.ImmutableList;
+ import com.google.common.collect.Iterables;
+ import com.google.common.collect.Lists;
+ 
+ public class CatalogYamlLocationTest extends AbstractYamlTest {
+     private static final String LOCALHOST_LOCATION_SPEC = "localhost";
+     private static final String LOCALHOST_LOCATION_TYPE = LocalhostMachineProvisioningLocation.class.getName();
+     private static final String SIMPLE_LOCATION_TYPE = "org.apache.brooklyn.test.osgi.entities.SimpleLocation";
+ 
+     @AfterMethod
+     public void tearDown() {
 -        for (RegisteredType ci : mgmt().getTypeRegistry().getAll(RegisteredTypePredicates.IS_LOCATION)) {
++        for (RegisteredType ci : mgmt().getTypeRegistry().getMatching(RegisteredTypePredicates.IS_LOCATION)) {
+             mgmt().getCatalog().deleteCatalogItem(ci.getSymbolicName(), ci.getVersion());
+         }
+     }
+     
+     @Test
+     public void testAddCatalogItem() throws Exception {
+         assertEquals(countCatalogLocations(), 0);
+ 
+         String symbolicName = "my.catalog.location.id.load";
+         addCatalogLocation(symbolicName, LOCALHOST_LOCATION_TYPE, null);
+         assertAdded(symbolicName, LOCALHOST_LOCATION_TYPE);
+         removeAndAssert(symbolicName);
+     }
+ 
+     @Test
+     public void testAddCatalogItemOsgi() throws Exception {
+         assertEquals(countCatalogLocations(), 0);
+ 
+         String symbolicName = "my.catalog.location.id.load";
+         addCatalogLocation(symbolicName, SIMPLE_LOCATION_TYPE, getOsgiLibraries());
+         assertAdded(symbolicName, SIMPLE_LOCATION_TYPE);
+         assertOsgi(symbolicName);
+         removeAndAssert(symbolicName);
+     }
+ 
+     @Test
+     public void testAddCatalogItemTopLevelItemSyntax() throws Exception {
+         assertEquals(countCatalogLocations(), 0);
+ 
+         String symbolicName = "my.catalog.location.id.load";
+         addCatalogLocationTopLevelItemSyntax(symbolicName, LOCALHOST_LOCATION_TYPE, null);
+         assertAdded(symbolicName, LOCALHOST_LOCATION_TYPE);
+         removeAndAssert(symbolicName);
+     }
+ 
+     @Test
+     public void testAddCatalogItemOsgiTopLevelItemSyntax() throws Exception {
+         assertEquals(countCatalogLocations(), 0);
+ 
+         String symbolicName = "my.catalog.location.id.load";
+         addCatalogLocationTopLevelItemSyntax(symbolicName, SIMPLE_LOCATION_TYPE, getOsgiLibraries());
+         assertAdded(symbolicName, SIMPLE_LOCATION_TYPE);
+         assertOsgi(symbolicName);
+         removeAndAssert(symbolicName);
+     }
+ 
+     private void assertOsgi(String symbolicName) {
+         RegisteredType item = mgmt().getTypeRegistry().get(symbolicName, TEST_VERSION);
+         Collection<OsgiBundleWithUrl> libs = item.getLibraries();
+         assertEquals(libs.size(), 1);
+         assertEquals(Iterables.getOnlyElement(libs).getUrl(), Iterables.getOnlyElement(getOsgiLibraries()));
+     }
+ 
+     @SuppressWarnings({ "rawtypes" })
+     private void assertAdded(String symbolicName, String expectedJavaType) {
+         RegisteredType item = mgmt().getTypeRegistry().get(symbolicName, TEST_VERSION);
+         assertEquals(item.getSymbolicName(), symbolicName);
+         Assert.assertTrue(RegisteredTypes.isSubtypeOf(item, Location.class), "Expected Location, not "+item.getSuperTypes());
+         assertEquals(countCatalogLocations(), 1);
+ 
+         // Item added to catalog should automatically be available in location registry
+         LocationDefinition def = mgmt().getLocationRegistry().getDefinedLocationByName(symbolicName);
+         assertEquals(def.getId(), symbolicName);
+         assertEquals(def.getName(), symbolicName);
+         
+         LocationSpec spec = (LocationSpec) mgmt().getTypeRegistry().createSpec(item, null, LocationSpec.class);
+         assertEquals(spec.getType().getName(), expectedJavaType);
+     }
+     
+     private void removeAndAssert(String symbolicName) {
+         // Deleting item: should be gone from catalog, and from location registry
+         deleteCatalogEntity(symbolicName);
+ 
+         assertEquals(countCatalogLocations(), 0);
+         assertNull(mgmt().getLocationRegistry().getDefinedLocationByName(symbolicName));
+     }
+ 
+     @Test
+     public void testLaunchApplicationReferencingLocationClass() throws Exception {
+         String symbolicName = "my.catalog.location.id.launch";
+         addCatalogLocation(symbolicName, LOCALHOST_LOCATION_TYPE, null);
+         runLaunchApplicationReferencingLocation(symbolicName, LOCALHOST_LOCATION_TYPE);
+ 
+         deleteCatalogEntity(symbolicName);
+     }
+ 
+     @Test
+     public void testLaunchApplicationReferencingLocationSpec() throws Exception {
+         String symbolicName = "my.catalog.location.id.launch";
+         addCatalogLocation(symbolicName, LOCALHOST_LOCATION_SPEC, null);
+         runLaunchApplicationReferencingLocation(symbolicName, LOCALHOST_LOCATION_TYPE);
+ 
+         deleteCatalogEntity(symbolicName);
+     }
+ 
+     @Test
+     public void testLaunchApplicationReferencingLocationClassTopLevelItemSyntax() throws Exception {
+         String symbolicName = "my.catalog.location.id.launch";
+         addCatalogLocationTopLevelItemSyntax(symbolicName, LOCALHOST_LOCATION_TYPE, null);
+         runLaunchApplicationReferencingLocation(symbolicName, LOCALHOST_LOCATION_TYPE);
+ 
+         deleteCatalogEntity(symbolicName);
+     }
+ 
+     @Test
+     public void testLaunchApplicationReferencingLocationSpecTopLevelSyntax() throws Exception {
+         String symbolicName = "my.catalog.location.id.launch";
+         addCatalogLocationTopLevelItemSyntax(symbolicName, LOCALHOST_LOCATION_SPEC, null);
+         runLaunchApplicationReferencingLocation(symbolicName, LOCALHOST_LOCATION_TYPE);
+ 
+         deleteCatalogEntity(symbolicName);
+     }
+ 
+     @Test
+     public void testLaunchApplicationReferencingOsgiLocation() throws Exception {
+         String symbolicName = "my.catalog.location.id.launch";
+         addCatalogLocation(symbolicName, SIMPLE_LOCATION_TYPE, getOsgiLibraries());
+         runLaunchApplicationReferencingLocation(symbolicName, SIMPLE_LOCATION_TYPE);
+         
+         deleteCatalogEntity(symbolicName);
+     }
+     
+     protected void runLaunchApplicationReferencingLocation(String locTypeInYaml, String locType) throws Exception {
+         Entity app = createAndStartApplication(
+             "name: simple-app-yaml",
+             "location: ",
+             "  "+locTypeInYaml+":",
+             "    config2: config2 override",
+             "    config3: config3",
+             "services: ",
+             "  - type: org.apache.brooklyn.entity.stock.BasicStartable");
+ 
+         Entity simpleEntity = Iterables.getOnlyElement(app.getChildren());
+         Location location = Iterables.getOnlyElement(simpleEntity.getLocations());
+         assertEquals(location.getClass().getName(), locType);
+         assertEquals(location.getConfig(new BasicConfigKey<String>(String.class, "config1")), "config1");
+         assertEquals(location.getConfig(new BasicConfigKey<String>(String.class, "config2")), "config2 override");
+         assertEquals(location.getConfig(new BasicConfigKey<String>(String.class, "config3")), "config3");
+     }
+ 
+     private List<String> getOsgiLibraries() {
+         TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_PATH);
+         return ImmutableList.of(OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_URL);
+     }
+     
+     private void addCatalogLocation(String symbolicName, String locationType, List<String> libraries) {
+         ImmutableList.Builder<String> yaml = ImmutableList.<String>builder().add(
+                 "brooklyn.catalog:",
+                 "  id: " + symbolicName,
+                 "  name: My Catalog Location",
+                 "  description: My description",
+                 "  version: " + TEST_VERSION);
+         if (libraries!=null && libraries.size() > 0) {
+             yaml.add("  libraries:")
+                 .addAll(Lists.transform(libraries, StringFunctions.prepend("  - url: ")));
+         }
+         yaml.add(
+                 "  item.type: location",
+                 "  item:",
+                 "    type: " + locationType,
+                 "    brooklyn.config:",
+                 "      config1: config1",
+                 "      config2: config2");
+         
+         
+         addCatalogItems(yaml.build());
+     }
+ 
+     private void addCatalogLocationTopLevelItemSyntax(String symbolicName, String locationType, List<String> libraries) {
+         ImmutableList.Builder<String> yaml = ImmutableList.<String>builder().add(
+                 "brooklyn.catalog:",
+                 "  id: " + symbolicName,
+                 "  name: My Catalog Location",
+                 "  description: My description",
+                 "  version: " + TEST_VERSION);
+         if (libraries!=null && libraries.size() > 0) {
+             yaml.add("  libraries:")
+                 .addAll(Lists.transform(libraries, StringFunctions.prepend("  - url: ")));
+         }
+         yaml.add(
+                 "",
+                 "brooklyn.locations:",
+                 "- type: " + locationType,
+                 "  brooklyn.config:",
+                 "    config1: config1",
+                 "    config2: config2");
+         
+         
+         addCatalogItems(yaml.build());
+     }
+ 
+     private int countCatalogLocations() {
 -        return Iterables.size(mgmt().getTypeRegistry().getAll(RegisteredTypePredicates.IS_LOCATION));
++        return Iterables.size(mgmt().getTypeRegistry().getMatching(RegisteredTypePredicates.IS_LOCATION));
+     }
+ 
+ }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/018a0e15/brooklyn-server/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlVersioningTest.java
----------------------------------------------------------------------
diff --cc brooklyn-server/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlVersioningTest.java
index 0000000,f92f3a4..440c114
mode 000000,100644..100644
--- a/brooklyn-server/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlVersioningTest.java
+++ b/brooklyn-server/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/catalog/CatalogYamlVersioningTest.java
@@@ -1,0 -1,269 +1,269 @@@
+ /*
+  * 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.camp.brooklyn.catalog;
+ 
+ import static org.testng.Assert.assertEquals;
+ import static org.testng.Assert.assertFalse;
+ import static org.testng.Assert.assertTrue;
+ import static org.testng.Assert.fail;
+ 
+ import org.apache.brooklyn.api.entity.Entity;
+ import org.apache.brooklyn.api.typereg.BrooklynTypeRegistry;
+ import org.apache.brooklyn.api.typereg.RegisteredType;
+ import org.apache.brooklyn.camp.brooklyn.AbstractYamlTest;
+ import org.apache.brooklyn.core.catalog.internal.BasicBrooklynCatalog;
+ import org.apache.brooklyn.core.config.ConfigKeys;
+ import org.apache.brooklyn.core.typereg.RegisteredTypePredicates;
+ import org.apache.brooklyn.core.typereg.RegisteredTypes;
+ import org.apache.brooklyn.entity.stock.BasicApplication;
+ import org.apache.brooklyn.entity.stock.BasicEntity;
+ import org.testng.Assert;
+ import org.testng.annotations.BeforeMethod;
+ import org.testng.annotations.Test;
+ 
+ import com.google.common.base.Predicates;
+ import com.google.common.collect.Iterables;
+ 
+ public class CatalogYamlVersioningTest extends AbstractYamlTest {
+     
+     private BrooklynTypeRegistry types;
+     
+     @BeforeMethod(alwaysRun = true)
+     public void setUp() {
+         super.setUp();
+         types = mgmt().getTypeRegistry();
+     }
+ 
+     @Test
+     public void testAddItem() {
+         String symbolicName = "sampleId";
+         String version = "0.1.0";
+         addCatalogEntity(symbolicName, version);
+         assertSingleCatalogItem(symbolicName, version);
+     }
+ 
+     @Test
+     public void testAddUnversionedItem() {
+         String symbolicName = "sampleId";
+         addCatalogEntity(symbolicName, null);
+         assertSingleCatalogItem(symbolicName, BasicBrooklynCatalog.NO_VERSION);
+     }
+ 
+     @Test
+     public void testAddSameVersionFailsWhenIconIsDifferent() {
+         String symbolicName = "sampleId";
+         String version = "0.1.0";
+         addCatalogEntity(symbolicName, version);
+         addCatalogEntity(symbolicName, version);
+         try {
+             addCatalogEntity(symbolicName, version, BasicEntity.class.getName(), "classpath:/another/icon.png");
+             fail("Expected to fail");
+         } catch (IllegalStateException e) {
+             assertEquals(e.getMessage(), "Updating existing catalog entries is forbidden: " + symbolicName + ":" + version + ". Use forceUpdate argument to override.");
+         }
+     }
+     
+     @Test
+     public void testAddSameVersionForce() {
+         String symbolicName = "sampleId";
+         String version = "0.1.0";
+         addCatalogEntity(symbolicName, version);
+         forceCatalogUpdate();
+         String expectedType = "org.apache.brooklyn.entity.stock.BasicApplication";
+         addCatalogEntity(symbolicName, version, expectedType);
+         RegisteredType item = types.get(symbolicName, version);
+         String yaml = RegisteredTypes.getImplementationDataStringForSpec(item);
+         assertTrue(yaml.contains(expectedType), "Version not updated:\n"+yaml);
+     }
+     
+     @Test
+     public void testGetLatest() {
+         String symbolicName = "sampleId";
+         String v1 = "0.1.0";
+         String v2 = "0.2.0";
+         addCatalogEntity(symbolicName, v1);
+         addCatalogEntity(symbolicName, v2);
+         RegisteredType item = types.get(symbolicName, BasicBrooklynCatalog.DEFAULT_VERSION);
+         assertEquals(item.getVersion(), v2);
+     }
+     
+     @Test
+     public void testGetLatestStable() {
+         String symbolicName = "sampleId";
+         String v1 = "0.1.0";
+         String v2 = "0.2.0-SNAPSHOT";
+         addCatalogEntity(symbolicName, v1);
+         addCatalogEntity(symbolicName, v2);
+         RegisteredType item = types.get(symbolicName, BasicBrooklynCatalog.DEFAULT_VERSION);
+         assertEquals(item.getVersion(), v1);
+     }
+ 
+     @Test
+     public void testDelete() {
+         String symbolicName = "sampleId";
+         String version = "0.1.0";
+         addCatalogEntity(symbolicName, version);
+         
+         Iterable<RegisteredType> matches;
 -        matches = types.getAll(RegisteredTypePredicates.symbolicName(Predicates.equalTo(symbolicName)));
++        matches = types.getMatching(RegisteredTypePredicates.symbolicName(Predicates.equalTo(symbolicName)));
+         assertTrue(matches.iterator().hasNext());
+         
+         mgmt().getCatalog().deleteCatalogItem(symbolicName, version);
 -        matches = types.getAll(RegisteredTypePredicates.symbolicName(Predicates.equalTo(symbolicName)));
++        matches = types.getMatching(RegisteredTypePredicates.symbolicName(Predicates.equalTo(symbolicName)));
+         assertFalse(matches.iterator().hasNext());
+     }
+     
+     @Test
+     public void testDeleteDefault() {
+         String symbolicName = "sampleId";
+         addCatalogEntity(symbolicName, null);
+ 
+         Iterable<RegisteredType> matches;
 -        matches = types.getAll(RegisteredTypePredicates.symbolicName(Predicates.equalTo(symbolicName)));
++        matches = types.getMatching(RegisteredTypePredicates.symbolicName(Predicates.equalTo(symbolicName)));
+         assertTrue(matches.iterator().hasNext());
+         
+         mgmt().getCatalog().deleteCatalogItem(symbolicName, BasicBrooklynCatalog.NO_VERSION);
 -        matches = types.getAll(RegisteredTypePredicates.symbolicName(Predicates.equalTo(symbolicName)));
++        matches = types.getMatching(RegisteredTypePredicates.symbolicName(Predicates.equalTo(symbolicName)));
+         assertFalse(matches.iterator().hasNext());
+     }
+     
+     @Test
+     public void testList() {
+         String symbolicName = "sampleId";
+         String v1 = "0.1.0";
+         String v2 = "0.2.0-SNAPSHOT";
+         addCatalogEntity(symbolicName, v1);
+         addCatalogEntity(symbolicName, v2);
 -        Iterable<RegisteredType> items = types.getAll(RegisteredTypePredicates.symbolicName(Predicates.equalTo(symbolicName)));
++        Iterable<RegisteredType> items = types.getMatching(RegisteredTypePredicates.symbolicName(Predicates.equalTo(symbolicName)));
+         assertEquals(Iterables.size(items), 2);
+     }
+     
+     @Test
+     public void testVersionedReference() throws Exception {
+         String symbolicName = "sampleId";
+         String parentName = "parentId";
+         String v1 = "0.1.0";
+         String v2 = "0.2.0";
+         String expectedType = BasicApplication.class.getName();
+ 
+         addCatalogEntity(symbolicName, v1, expectedType);
+         addCatalogEntity(symbolicName, v2);
+         addCatalogEntity(parentName, v1, symbolicName + ":" + v1);
+ 
+         Entity app = createAndStartApplication(
+                 "services:",
+                 "- type: " + parentName + ":" + v1);
+ 
+         assertEquals(app.getEntityType().getName(), expectedType);
+     }
+ 
+     @Test
+     public void testUnversionedReference() throws Exception {
+         String symbolicName = "sampleId";
+         String parentName = "parentId";
+         String v1 = "0.1.0";
+         String v2 = "0.2.0";
+         String expectedType = BasicApplication.class.getName();
+ 
+         addCatalogEntity(symbolicName, v1);
+         addCatalogEntity(symbolicName, v2, expectedType);
+         addCatalogEntity(parentName, v1, symbolicName);
+ 
+         Entity app = createAndStartApplication(
+                 "services:",
+                 "- type: " + parentName + ":" + v1);
+ 
+         assertEquals(app.getEntityType().getName(), expectedType);
+     }
+ 
+     private void doTestVersionedReferenceJustAdded(boolean isVersionImplicitSyntax) throws Exception {
+         addCatalogItems(            "brooklyn.catalog:",
+             "  version: 0.9",
+             "  items:",
+             "  - id: referrent",
+             "    item:",
+             "      type: "+BasicEntity.class.getName(),
+             "  - id: referrent",
+             "    version: 1.1",
+             "    item:",
+             "      type: "+BasicEntity.class.getName(),
+             "      brooklyn.config: { foo: bar }",
+             "  - id: referrer",
+             "    version: 1.0",
+             "    item:",
+             (isVersionImplicitSyntax ? 
+                 "      type: referrent:1.1" :
+                 "      type: referrent\n" +
+                 "      version: 1.1"));
+         
 -        Iterable<RegisteredType> items = types.getAll(RegisteredTypePredicates.symbolicName(Predicates.equalTo("referrer")));
++        Iterable<RegisteredType> items = types.getMatching(RegisteredTypePredicates.symbolicName(Predicates.equalTo("referrer")));
+         Assert.assertEquals(Iterables.size(items), 1, "Wrong number of: "+items);
+         RegisteredType item = Iterables.getOnlyElement(items);
+         Assert.assertEquals(item.getVersion(), "1.0");
+         
+         Entity app = createAndStartApplication(
+             "services:",
+             (isVersionImplicitSyntax ? 
+                 "- type: referrer:1.0" :
+                 "- type: referrer\n" +
+                 "  version: 1.0") );
+         Entity child = Iterables.getOnlyElement(app.getChildren());
+         Assert.assertTrue(child instanceof BasicEntity, "Wrong child: "+child);
+         Assert.assertEquals(child.getConfig(ConfigKeys.newStringConfigKey("foo")), "bar");
+     }
+ 
+     @Test
+     public void testVersionedReferenceJustAddedExplicitVersion() throws Exception {
+         doTestVersionedReferenceJustAdded(false);
+     }
+     
+     @Test
+     public void testVersionedReferenceJustAddedImplicitVersionSyntax() throws Exception {
+         doTestVersionedReferenceJustAdded(true);
+     }
+     
+     private void assertSingleCatalogItem(String symbolicName, String version) {
 -        Iterable<RegisteredType> items = types.getAll(RegisteredTypePredicates.symbolicName(Predicates.equalTo(symbolicName)));
++        Iterable<RegisteredType> items = types.getMatching(RegisteredTypePredicates.symbolicName(Predicates.equalTo(symbolicName)));
+         RegisteredType item = Iterables.getOnlyElement(items);
+         assertEquals(item.getSymbolicName(), symbolicName);
+         assertEquals(item.getVersion(), version);
+     }
+     
+     private void addCatalogEntity(String symbolicName, String version) {
+         addCatalogEntity(symbolicName, version, BasicEntity.class.getName());
+     }
+ 
+     private void addCatalogEntity(String symbolicName, String version, String type) {
+         addCatalogEntity(symbolicName, version, type, "classpath://path/to/myicon.jpg");
+     }
+     
+     private void addCatalogEntity(String symbolicName, String version, String type, String iconUrl) {
+         addCatalogItems(
+             "brooklyn.catalog:",
+             "  id: " + symbolicName,
+             "  name: My Catalog App",
+             "  description: My description",
+             "  icon_url: "+iconUrl,
+             (version != null ? "  version: " + version : ""),
+             "",
+             "services:",
+             "- type: " + type);
+     }
+ 
+ }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/018a0e15/brooklyn-server/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/test/lite/CampYamlLiteTest.java
----------------------------------------------------------------------
diff --cc brooklyn-server/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/test/lite/CampYamlLiteTest.java
index 0000000,d8e4b1d..9cd6bc5
mode 000000,100644..100644
--- a/brooklyn-server/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/test/lite/CampYamlLiteTest.java
+++ b/brooklyn-server/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/test/lite/CampYamlLiteTest.java
@@@ -1,0 -1,259 +1,261 @@@
+ /*
+  * 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.camp.brooklyn.test.lite;
+ 
+ import static org.testng.Assert.assertEquals;
+ import static org.testng.Assert.assertNotNull;
+ 
+ import java.io.IOException;
+ import java.io.InputStreamReader;
+ import java.io.Reader;
+ import java.util.Collection;
+ import java.util.List;
+ import java.util.Map;
+ 
+ import org.apache.brooklyn.api.catalog.CatalogItem;
+ import org.apache.brooklyn.api.catalog.CatalogItem.CatalogBundle;
+ import org.apache.brooklyn.api.entity.Entity;
+ import org.apache.brooklyn.api.entity.EntitySpec;
+ import org.apache.brooklyn.api.mgmt.Task;
+ import org.apache.brooklyn.api.typereg.OsgiBundleWithUrl;
+ import org.apache.brooklyn.api.typereg.RegisteredType;
+ import org.apache.brooklyn.camp.spi.Assembly;
+ import org.apache.brooklyn.camp.spi.AssemblyTemplate;
+ import org.apache.brooklyn.camp.spi.pdp.PdpYamlTest;
+ import org.apache.brooklyn.camp.test.mock.web.MockWebPlatform;
+ import org.apache.brooklyn.core.catalog.CatalogPredicates;
+ import org.apache.brooklyn.core.catalog.internal.BasicBrooklynCatalog;
+ import org.apache.brooklyn.core.catalog.internal.CatalogDto;
+ import org.apache.brooklyn.core.catalog.internal.CatalogUtils;
+ import org.apache.brooklyn.core.config.ConfigKeys;
+ import org.apache.brooklyn.core.effector.AddChildrenEffector;
+ import org.apache.brooklyn.core.effector.Effectors;
+ import org.apache.brooklyn.core.entity.Entities;
+ import org.apache.brooklyn.core.mgmt.internal.LocalManagementContext;
+ import org.apache.brooklyn.core.mgmt.osgi.OsgiStandaloneTest;
+ import org.apache.brooklyn.core.test.entity.LocalManagementContextForTests;
+ import org.apache.brooklyn.core.test.entity.TestApplication;
+ import org.apache.brooklyn.core.test.entity.TestEntity;
+ import org.apache.brooklyn.core.typereg.RegisteredTypeLoadingContexts;
+ import org.apache.brooklyn.core.typereg.RegisteredTypes;
+ import org.apache.brooklyn.test.support.TestResourceUnavailableException;
+ import org.apache.brooklyn.util.collections.MutableMap;
+ import org.apache.brooklyn.util.core.ResourceUtils;
+ import org.apache.brooklyn.util.core.config.ConfigBag;
+ import org.apache.brooklyn.util.stream.Streams;
+ import org.slf4j.Logger;
+ import org.slf4j.LoggerFactory;
+ import org.testng.Assert;
+ import org.testng.annotations.AfterMethod;
+ import org.testng.annotations.BeforeMethod;
+ import org.testng.annotations.Test;
+ 
+ import com.google.common.base.Joiner;
+ import com.google.common.base.Predicates;
+ import com.google.common.collect.Iterables;
+ 
+ /** Tests of lightweight CAMP integration. Since the "real" integration is in brooklyn-camp project,
+  * but some aspects of CAMP we want to be able to test here. */
+ public class CampYamlLiteTest {
+     private static final String TEST_VERSION = "0.1.2";
+ 
+     private static final Logger log = LoggerFactory.getLogger(CampYamlLiteTest.class);
+     
+     protected LocalManagementContext mgmt;
+     protected CampPlatformWithJustBrooklynMgmt platform;
+     
+     @BeforeMethod(alwaysRun=true)
+     public void setUp() {
+         mgmt = LocalManagementContextForTests.newInstanceWithOsgi();
+         platform = new CampPlatformWithJustBrooklynMgmt(mgmt);
+         MockWebPlatform.populate(platform, TestAppAssemblyInstantiator.class);
+     }
+     
+     @AfterMethod(alwaysRun=true)
+     public void tearDown() {
+         if (mgmt!=null) mgmt.terminate();
+     }
+     
+     /** based on {@link PdpYamlTest} for parsing,
+      * then creating a {@link TestAppAssembly} */
+     @Test
+     public void testYamlServiceMatchAndBrooklynInstantiate() throws Exception {
+         Reader input = new InputStreamReader(getClass().getResourceAsStream("test-app-service-blueprint.yaml"));
+         AssemblyTemplate at = platform.pdp().registerDeploymentPlan(input);
+         log.info("AT is:\n"+at.toString());
+         Assert.assertEquals(at.getName(), "sample");
+         Assert.assertEquals(at.getPlatformComponentTemplates().links().size(), 1);
+         
+         // now use brooklyn to instantiate - note it won't be faithful, but it will set some config keys
+         Assembly assembly = at.getInstantiator().newInstance().instantiate(at, platform);
+         
+         TestApplication app = ((TestAppAssembly)assembly).getBrooklynApp();
+         Assert.assertEquals( app.getConfig(TestEntity.CONF_NAME), "sample" );
+         Map<String, String> map = app.getConfig(TestEntity.CONF_MAP_THING);
+         Assert.assertEquals( map.get("desc"), "Tomcat sample JSP and servlet application." );
+         
+         Assert.assertEquals( app.getChildren().size(), 1 );
+         Entity svc = Iterables.getOnlyElement(app.getChildren());
+         Assert.assertEquals( svc.getConfig(TestEntity.CONF_NAME), "Hello WAR" );
+         map = svc.getConfig(TestEntity.CONF_MAP_THING);
+         Assert.assertEquals( map.get("type"), MockWebPlatform.APPSERVER.getType() );
+         // desc ensures we got the information from the matcher, as this value is NOT in the yaml
+         Assert.assertEquals( map.get("desc"), MockWebPlatform.APPSERVER.getDescription() );
+     }
+ 
+     /** based on {@link PdpYamlTest} for parsing,
+      * then creating a {@link TestAppAssembly} */
+     @SuppressWarnings({ "rawtypes", "unchecked" })
+     @Test
+     public void testAddChildrenEffector() throws Exception {
+         String childYaml = Streams.readFullyString(getClass().getResourceAsStream("test-app-service-blueprint.yaml"));
+         AddChildrenEffector newEff = new AddChildrenEffector(ConfigBag.newInstance()
+             .configure(AddChildrenEffector.EFFECTOR_NAME, "add_tomcat")
+             .configure(AddChildrenEffector.BLUEPRINT_YAML, childYaml)
+             .configure(AddChildrenEffector.EFFECTOR_PARAMETER_DEFS, MutableMap.of("war", (Object)MutableMap.of(
+                 "defaultValue", "foo.war"))) ) ;
+         TestApplication app = mgmt.getEntityManager().createEntity(EntitySpec.create(TestApplication.class).addInitializer(newEff));
+ 
+         // test adding, with a parameter
+         Task<List> task = app.invoke(Effectors.effector(List.class, "add_tomcat").buildAbstract(), MutableMap.of("war", "foo.bar"));
+         List result = task.get();
+         
+         Entity newChild = Iterables.getOnlyElement(app.getChildren());
+         Assert.assertEquals(newChild.getConfig(ConfigKeys.newStringConfigKey("war")), "foo.bar");
+         
+         Assert.assertEquals(Iterables.getOnlyElement(result), newChild.getId());
+         Entities.unmanage(newChild);
+         
+         // and test default value
+         task = app.invoke(Effectors.effector(List.class, "add_tomcat").buildAbstract(), MutableMap.<String,Object>of());
+         result = task.get();
+         
+         newChild = Iterables.getOnlyElement(app.getChildren());
+         Assert.assertEquals(newChild.getConfig(ConfigKeys.newStringConfigKey("war")), "foo.war");
+         
+         Assert.assertEquals(Iterables.getOnlyElement(result), newChild.getId());
+         Entities.unmanage(newChild);
+     }
+ 
+     @Test
+     public void testYamlServiceForCatalog() {
+         TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_PATH);
+ 
+         CatalogItem<?, ?> realItem = Iterables.getOnlyElement(mgmt.getCatalog().addItems(Streams.readFullyString(getClass().getResourceAsStream("test-app-service-blueprint.yaml"))));
+         Iterable<CatalogItem<Object, Object>> retrievedItems = mgmt.getCatalog()
+                 .getCatalogItems(CatalogPredicates.symbolicName(Predicates.equalTo("catalog-name")));
+         
+         Assert.assertEquals(Iterables.size(retrievedItems), 1, "Wrong retrieved items: "+retrievedItems);
+         CatalogItem<Object, Object> retrievedItem = Iterables.getOnlyElement(retrievedItems);
+         Assert.assertEquals(retrievedItem, realItem);
+ 
+         Collection<CatalogBundle> bundles = retrievedItem.getLibraries();
+         Assert.assertEquals(bundles.size(), 1);
+         CatalogBundle bundle = Iterables.getOnlyElement(bundles);
+         Assert.assertEquals(bundle.getUrl(), OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_URL);
+         Assert.assertEquals(bundle.getVersion(), "0.1.0");
+ 
+         @SuppressWarnings({ "unchecked", "rawtypes" })
+         EntitySpec<?> spec1 = (EntitySpec<?>) mgmt.getCatalog().createSpec((CatalogItem)retrievedItem);
+         assertNotNull(spec1);
+         Assert.assertEquals(spec1.getConfig().get(TestEntity.CONF_NAME), "sample");
+         
+         // TODO other assertions, about children
+     }
+ 
+     @Test
+     public void testRegisterCustomEntityWithBundleWhereEntityIsFromCoreAndIconFromBundle() throws IOException {
+         TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_PATH);
+ 
+         String symbolicName = "my.catalog.app.id";
+         String bundleUrl = OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_URL;
+         String yaml = getSampleMyCatalogAppYaml(symbolicName, bundleUrl);
+ 
+         mgmt.getCatalog().addItems(yaml);
+ 
+         assertMgmtHasSampleMyCatalogApp(symbolicName, bundleUrl);
+     }
+ 
+     @Test
+     public void testResetXmlWithCustomEntity() throws IOException {
+         TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_PATH);
+ 
+         String symbolicName = "my.catalog.app.id";
+         String bundleUrl = OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_URL;
+         String yaml = getSampleMyCatalogAppYaml(symbolicName, bundleUrl);
+ 
+         LocalManagementContext mgmt2 = LocalManagementContextForTests.newInstanceWithOsgi();
+         try {
+             CampPlatformWithJustBrooklynMgmt platform2 = new CampPlatformWithJustBrooklynMgmt(mgmt2);
+             MockWebPlatform.populate(platform2, TestAppAssemblyInstantiator.class);
+ 
+             mgmt2.getCatalog().addItems(yaml);
+             String xml = ((BasicBrooklynCatalog) mgmt2.getCatalog()).toXmlString();
+             ((BasicBrooklynCatalog) mgmt.getCatalog()).reset(CatalogDto.newDtoFromXmlContents(xml, "copy of temporary catalog"));
+         } finally {
+             mgmt2.terminate();
+         }
+ 
+         assertMgmtHasSampleMyCatalogApp(symbolicName, bundleUrl);
+     }
+ 
+     private String getSampleMyCatalogAppYaml(String symbolicName, String bundleUrl) {
+         return "brooklyn.catalog:\n" +
+                 "  id: " + symbolicName + "\n" +
+                 "  name: My Catalog App\n" +
+                 "  description: My description\n" +
+                 "  icon_url: classpath:/org/apache/brooklyn/test/osgi/entities/icon.gif\n" +
+                 "  version: " + TEST_VERSION + "\n" +
+                 "  libraries:\n" +
+                 "  - url: " + bundleUrl + "\n" +
+                 "\n" +
+                 "services:\n" +
+                 "- type: io.camp.mock:AppServer\n";
+     }
+ 
+     private void assertMgmtHasSampleMyCatalogApp(String symbolicName, String bundleUrl) {
 -        RegisteredType item = RegisteredTypes.validate(mgmt.getTypeRegistry().get(symbolicName), RegisteredTypeLoadingContexts.spec(Entity.class));
++        RegisteredType item = mgmt.getTypeRegistry().get(symbolicName);
+         assertNotNull(item, "failed to load item with id=" + symbolicName + " from catalog. Entries were: " +
+                 Joiner.on(",").join(mgmt.getTypeRegistry().getAll()));
+         assertEquals(item.getSymbolicName(), symbolicName);
+ 
++        RegisteredTypes.tryValidate(item, RegisteredTypeLoadingContexts.spec(Entity.class)).get();
++        
+         // stored as yaml, not java
+         String planYaml = RegisteredTypes.getImplementationDataStringForSpec(item);
+         assertNotNull(planYaml);
+         Assert.assertTrue(planYaml.contains("io.camp.mock:AppServer"));
+ 
+         // and let's check we have libraries
+         Collection<OsgiBundleWithUrl> libs = item.getLibraries();
+         assertEquals(libs.size(), 1);
+         OsgiBundleWithUrl bundle = Iterables.getOnlyElement(libs);
+         assertEquals(bundle.getUrl(), bundleUrl);
+ 
+         // now let's check other things on the item
+         assertEquals(item.getDisplayName(), "My Catalog App");
+         assertEquals(item.getDescription(), "My description");
+         assertEquals(item.getIconUrl(), "classpath:/org/apache/brooklyn/test/osgi/entities/icon.gif");
+ 
+         // and confirm we can resolve ICON
+         byte[] iconData = Streams.readFully(ResourceUtils.create(CatalogUtils.newClassLoadingContext(mgmt, item)).getResourceFromUrl(item.getIconUrl()));
+         assertEquals(iconData.length, 43);
+     }
+ 
+ }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/018a0e15/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogItemComparator.java
----------------------------------------------------------------------
diff --cc brooklyn-server/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogItemComparator.java
index 0000000,dc41457..abd4d08
mode 000000,100644..100644
--- a/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogItemComparator.java
+++ b/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogItemComparator.java
@@@ -1,0 -1,142 +1,52 @@@
+ /*
+  * 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.catalog.internal;
+ 
 -import java.util.ArrayList;
 -import java.util.Arrays;
 -import java.util.Collection;
++import java.util.Collections;
+ import java.util.Comparator;
+ 
+ import org.apache.brooklyn.api.catalog.CatalogItem;
 -import org.apache.brooklyn.util.text.NaturalOrderComparator;
++import org.apache.brooklyn.util.text.VersionComparator;
+ 
+ /**
+  * Largest version first order.
+  * 
+  * When using the comparator to sort - first using symbolicName
+  * and if equal puts larger versions first, snapshots at the back.
+  */
+ public class CatalogItemComparator<T,SpecT> implements Comparator<CatalogItem<T, SpecT>> {
 -    private static final String SNAPSHOT = "SNAPSHOT";
+ 
+     public static final CatalogItemComparator<?, ?> INSTANCE = new CatalogItemComparator<Object, Object>();
+ 
+     @SuppressWarnings("unchecked")
+     public static <T,SpecT> CatalogItemComparator<T,SpecT> getInstance() {
+         return (CatalogItemComparator<T, SpecT>) INSTANCE;
+     }
+ 
+     @Override
+     public int compare(CatalogItem<T, SpecT> o1, CatalogItem<T, SpecT> o2) {
+         int symbolicNameComparison = o1.getSymbolicName().compareTo(o2.getSymbolicName());
+         if (symbolicNameComparison != 0) {
+             return symbolicNameComparison;
+         } else {
 -            String v1 = o1.getVersion();
 -            String v2 = o2.getVersion();
 -
 -            boolean isV1Snapshot = v1.toUpperCase().contains(SNAPSHOT);
 -            boolean isV2Snapshot = v2.toUpperCase().contains(SNAPSHOT);
 -            if (isV1Snapshot == isV2Snapshot) {
 -                String[] v1Parts = split(v1);
 -                String[] v2Parts = split(v2);
 -                return -compare(v1Parts, v2Parts);
 -            } else if (isV1Snapshot) {
 -                return 1;
 -            } else {
 -                return -1;
 -            }
 -        }
 -    }
 -
 -    private String[] split(String v) {
 -        Collection<String> parts = new ArrayList<String>();
 -        int startPos = 0;
 -        int delimPos;
 -        while ((delimPos = v.indexOf('.', startPos)) != -1) {
 -            String part = v.substring(startPos, delimPos);
 -            if (parse(part) != -1) {
 -                parts.add(part);
 -            } else {
 -                break;
 -            }
 -            startPos = delimPos+1;
 -        }
 -        String remaining = v.substring(startPos);
 -        parts.addAll(Arrays.asList(remaining.split("[^\\d]", 2)));
 -        return parts.toArray(new String[parts.size()]);
 -    }
 -
 -    private int compare(String[] v1Parts, String[] v2Parts) {
 -        int len = Math.max(v1Parts.length, v2Parts.length);
 -        for (int i = 0; i < len; i++) {
 -            if (i == v1Parts.length) {
 -                return isNumber(v2Parts[i]) ? -1 : 1;
 -            }
 -            if (i == v2Parts.length) {
 -                return isNumber(v1Parts[i]) ? 1 : -1;
 -            }
 -
 -            String p1 = v1Parts[i];
 -            String p2 = v2Parts[i];
 -            int n1 = parse(p1);
 -            int n2 = parse(p2);
 -            if (n1 != -1 && n2 != -1) {
 -                if (n1 != n2) {
 -                    return compare(n1, n2);
 -                }
 -            } else if (n1 == -1 && n2 != -1) {
 -                return -1;
 -            } else if (n1 != -1 && n2 == -1) {
 -                return 1;
 -            } else {
 -                int cmp = NaturalOrderComparator.INSTANCE.compare(p1, p2);
 -                if (cmp < 0) {
 -                    return -1;
 -                } else if (cmp > 0) {
 -                    return 1;
 -                }
 -            }
++            return Collections.reverseOrder(VersionComparator.INSTANCE).compare(o1.getVersion(), o2.getVersion());
+         }
 -        return 0;
+     }
+ 
 -    private boolean isNumber(String v) {
 -        return parse(v) != -1;
 -    }
 -
 -    //Replace with Integer.compare in J7
 -    private int compare(int n1, int n2) {
 -        if (n1 == n2) {
 -            return 0;
 -        } else if (n1 < n2) {
 -            return -1;
 -        } else {
 -            return 1;
 -        }
 -    }
 -
 -    private int parse(String p) {
 -        try {
 -            return Integer.parseInt(p);
 -        } catch (NumberFormatException e) {
 -            return -1;
 -        }
 -    }
+ }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/018a0e15/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogUtils.java
----------------------------------------------------------------------
diff --cc brooklyn-server/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogUtils.java
index 0000000,ef455c6..dce5493
mode 000000,100644..100644
--- a/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogUtils.java
+++ b/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogUtils.java
@@@ -1,0 -1,319 +1,321 @@@
+ /*
+  * 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.catalog.internal;
+ 
+ import java.util.Collection;
+ 
+ import javax.annotation.Nullable;
+ 
+ import org.apache.brooklyn.api.catalog.BrooklynCatalog;
+ import org.apache.brooklyn.api.catalog.CatalogItem;
+ import org.apache.brooklyn.api.catalog.CatalogItem.CatalogBundle;
+ import org.apache.brooklyn.api.entity.Entity;
+ import org.apache.brooklyn.api.mgmt.ManagementContext;
+ import org.apache.brooklyn.api.mgmt.classloading.BrooklynClassLoadingContext;
+ import org.apache.brooklyn.api.objs.BrooklynObject;
+ import org.apache.brooklyn.api.typereg.BrooklynTypeRegistry;
+ import org.apache.brooklyn.api.typereg.OsgiBundleWithUrl;
+ import org.apache.brooklyn.api.typereg.RegisteredType;
+ import org.apache.brooklyn.core.BrooklynLogging;
+ import org.apache.brooklyn.core.catalog.internal.BasicBrooklynCatalog.BrooklynLoaderTracker;
+ import org.apache.brooklyn.core.entity.EntityInternal;
+ import org.apache.brooklyn.core.mgmt.classloading.BrooklynClassLoadingContextSequential;
+ import org.apache.brooklyn.core.mgmt.classloading.JavaBrooklynClassLoadingContext;
+ import org.apache.brooklyn.core.mgmt.classloading.OsgiBrooklynClassLoadingContext;
+ import org.apache.brooklyn.core.mgmt.ha.OsgiManager;
+ import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal;
+ import org.apache.brooklyn.core.mgmt.rebind.RebindManagerImpl.RebindTracker;
+ import org.apache.brooklyn.core.objs.BrooklynObjectInternal;
+ import org.apache.brooklyn.core.typereg.RegisteredTypeLoadingContexts;
++import org.apache.brooklyn.core.typereg.RegisteredTypePredicates;
+ import org.apache.brooklyn.core.typereg.RegisteredTypes;
+ import org.apache.brooklyn.util.guava.Maybe;
+ import org.apache.brooklyn.util.text.Strings;
+ import org.apache.brooklyn.util.time.Time;
+ import org.slf4j.Logger;
+ import org.slf4j.LoggerFactory;
+ 
 -import com.google.api.client.util.Preconditions;
+ import com.google.common.annotations.Beta;
+ import com.google.common.base.Joiner;
++import com.google.common.base.Preconditions;
+ import com.google.common.base.Stopwatch;
+ 
+ public class CatalogUtils {
+     private static final Logger log = LoggerFactory.getLogger(CatalogUtils.class);
+ 
+     public static final char VERSION_DELIMITER = ':';
+ 
+     public static BrooklynClassLoadingContext newClassLoadingContext(ManagementContext mgmt, CatalogItem<?, ?> item) {
+         // TODO getLibraries() should never be null but sometimes it is still
+         // e.g. run CatalogResourceTest without the above check
+         if (item.getLibraries() == null) {
+             log.debug("CatalogItemDtoAbstract.getLibraries() is null.", new Exception("Trace for null CatalogItemDtoAbstract.getLibraries()"));
+         }
+         return newClassLoadingContext(mgmt, item.getId(), item.getLibraries());
+     }
+     
+     public static BrooklynClassLoadingContext newClassLoadingContext(ManagementContext mgmt, RegisteredType item) {
+         return newClassLoadingContext(mgmt, item.getId(), item.getLibraries(), null);
+     }
+     
+     /** made @Beta in 0.9.0 because we're not sure to what extent to support stacking loaders; 
+      * only a couple places currently rely on such stacking, in general the item and the bundles *are* the context,
+      * and life gets hard if we support complex stacking! */
+     @Beta 
+     public static BrooklynClassLoadingContext newClassLoadingContext(ManagementContext mgmt, RegisteredType item, BrooklynClassLoadingContext loader) {
+         return newClassLoadingContext(mgmt, item.getId(), item.getLibraries(), loader);
+     }
+     
+     public static BrooklynClassLoadingContext getClassLoadingContext(Entity entity) {
+         ManagementContext mgmt = ((EntityInternal)entity).getManagementContext();
+         String catId = entity.getCatalogItemId();
+         if (Strings.isBlank(catId)) return JavaBrooklynClassLoadingContext.create(mgmt);
 -        RegisteredType cat = RegisteredTypes.validate(mgmt.getTypeRegistry().get(catId), RegisteredTypeLoadingContexts.spec(Entity.class));
 -        if (cat==null) {
++        Maybe<RegisteredType> cat = RegisteredTypes.tryValidate(mgmt.getTypeRegistry().get(catId), RegisteredTypeLoadingContexts.spec(Entity.class));
++        if (cat.isNull()) {
+             log.warn("Cannot load "+catId+" to get classloader for "+entity+"; will try with standard loader, but might fail subsequently");
+             return JavaBrooklynClassLoadingContext.create(mgmt);
+         }
 -        return newClassLoadingContext(mgmt, cat);
++        return newClassLoadingContext(mgmt, cat.get());
+     }
+ 
+     public static BrooklynClassLoadingContext newClassLoadingContext(@Nullable ManagementContext mgmt, String catalogItemId, Collection<? extends OsgiBundleWithUrl> libraries) {
+         return newClassLoadingContext(mgmt, catalogItemId, libraries, null);
+     }
+     
+     @Deprecated /** @deprecated since 0.9.0; becoming private because we should now always have a registered type callers can pass instead of the catalog item id */
+     public static BrooklynClassLoadingContext newClassLoadingContext(@Nullable ManagementContext mgmt, String catalogItemId, Collection<? extends OsgiBundleWithUrl> libraries, BrooklynClassLoadingContext loader) {
+         BrooklynClassLoadingContextSequential result = new BrooklynClassLoadingContextSequential(mgmt);
+ 
+         if (libraries!=null && !libraries.isEmpty()) {
+             result.add(new OsgiBrooklynClassLoadingContext(mgmt, catalogItemId, libraries));
+         }
+ 
+         if (loader !=null) {
+             // TODO determine whether to support stacking
+             result.add(loader);
+         }
+         BrooklynClassLoadingContext threadLocalLoader = BrooklynLoaderTracker.getLoader();
+         if (threadLocalLoader != null) {
+             // TODO and determine if this is needed/wanted
+             result.add(threadLocalLoader);
+         }
+ 
+         result.addSecondary(JavaBrooklynClassLoadingContext.create(mgmt));
+         return result;
+     }
+ 
+     /**
+      * @deprecated since 0.7.0 only for legacy catalog items which provide a non-osgi loader; see {@link #newDefault(ManagementContext)}
+      */ @Deprecated
+     public static BrooklynClassLoadingContext newClassLoadingContext(@Nullable ManagementContext mgmt, String catalogItemId, Collection<CatalogBundle> libraries, ClassLoader customClassLoader) {
+         BrooklynClassLoadingContextSequential result = new BrooklynClassLoadingContextSequential(mgmt);
+ 
+         if (libraries!=null && !libraries.isEmpty()) {
+             result.add(new OsgiBrooklynClassLoadingContext(mgmt, catalogItemId, libraries));
+         }
+ 
+         BrooklynClassLoadingContext loader = BrooklynLoaderTracker.getLoader();
+         if (loader != null) {
+             result.add(loader);
+         }
+ 
+         result.addSecondary(JavaBrooklynClassLoadingContext.create(mgmt, customClassLoader));
+         return result;
+     }
+ 
+     /**
+      * Registers all bundles with the management context's OSGi framework.
+      */
+     public static void installLibraries(ManagementContext managementContext, @Nullable Collection<CatalogBundle> libraries) {
+         if (libraries == null) return;
+ 
+         ManagementContextInternal mgmt = (ManagementContextInternal) managementContext;
+         if (!libraries.isEmpty()) {
+             Maybe<OsgiManager> osgi = mgmt.getOsgiManager();
+             if (osgi.isAbsent()) {
+                 throw new IllegalStateException("Unable to load bundles "+libraries+" because OSGi is not running.");
+             }
+             if (log.isDebugEnabled()) 
+                 logDebugOrTraceIfRebinding(log, 
+                     "Loading bundles in {}: {}", 
+                     new Object[] {managementContext, Joiner.on(", ").join(libraries)});
+             Stopwatch timer = Stopwatch.createStarted();
+             for (CatalogBundle bundleUrl : libraries) {
+                 osgi.get().registerBundle(bundleUrl);
+             }
+             if (log.isDebugEnabled()) 
+                 logDebugOrTraceIfRebinding(log, 
+                     "Registered {} bundles in {}",
+                     new Object[]{libraries.size(), Time.makeTimeStringRounded(timer)});
+         }
+     }
+ 
+     /** Scans the given {@link BrooklynClassLoadingContext} to detect what catalog item id is in effect. */
+     public static String getCatalogItemIdFromLoader(BrooklynClassLoadingContext loader) {
+         if (loader instanceof OsgiBrooklynClassLoadingContext) {
+             return ((OsgiBrooklynClassLoadingContext)loader).getCatalogItemId();
+         } else {
+             return null;
+         }
+     }
+ 
+     public static void setCatalogItemIdOnAddition(Entity entity, BrooklynObject itemBeingAdded) {
+         if (entity.getCatalogItemId()!=null) {
+             if (itemBeingAdded.getCatalogItemId()==null) {
+                 if (log.isDebugEnabled())
+                     BrooklynLogging.log(log, BrooklynLogging.levelDebugOrTraceIfReadOnly(entity),
+                         "Catalog item addition: "+entity+" from "+entity.getCatalogItemId()+" applying its catalog item ID to "+itemBeingAdded);
+                 ((BrooklynObjectInternal)itemBeingAdded).setCatalogItemId(entity.getCatalogItemId());
+             } else {
+                 if (!itemBeingAdded.getCatalogItemId().equals(entity.getCatalogItemId())) {
+                     // not a problem, but something to watch out for
+                     log.debug("Cross-catalog item detected: "+entity+" from "+entity.getCatalogItemId()+" has "+itemBeingAdded+" from "+itemBeingAdded.getCatalogItemId());
+                 }
+             }
+         } else if (itemBeingAdded.getCatalogItemId()!=null) {
+             if (log.isDebugEnabled())
+                 BrooklynLogging.log(log, BrooklynLogging.levelDebugOrTraceIfReadOnly(entity),
+                     "Catalog item addition: "+entity+" without catalog item ID has "+itemBeingAdded+" from "+itemBeingAdded.getCatalogItemId());
+         }
+     }
+ 
+     @Beta
+     public static void logDebugOrTraceIfRebinding(Logger log, String message, Object ...args) {
+         if (RebindTracker.isRebinding())
+             log.trace(message, args);
+         else
+             log.debug(message, args);
+     }
+ 
+     public static boolean looksLikeVersionedId(String versionedId) {
+         if (versionedId==null) return false;
+         int fi = versionedId.indexOf(VERSION_DELIMITER);
+         if (fi<0) return false;
+         int li = versionedId.lastIndexOf(VERSION_DELIMITER);
+         if (li!=fi) {
+             // if multiple colons, we say it isn't a versioned reference; the prefix in that case must understand any embedded versioning scheme
+             // this fixes the case of:  http://localhost:8080
+             return false;
+         }
+         String candidateVersion = versionedId.substring(li+1);
+         if (!candidateVersion.matches("[0-9]+(|(\\.|_).*)")) {
+             // version must start with a number, followed if by anything with full stop or underscore before any other characters
+             // e.g.  foo:1  or foo:1.1  or foo:1_SNAPSHOT all supported, but not e.g. foo:bar (or chef:cookbook or docker:my/image)
+             return false;
+         }
+         return true;
+     }
+ 
+     /** @deprecated since 0.9.0 use {@link #getSymbolicNameFromVersionedId(String)} */
+     // all uses removed
+     @Deprecated
+     public static String getIdFromVersionedId(String versionedId) {
+         return getSymbolicNameFromVersionedId(versionedId);
+     }
+     
+     public static String getSymbolicNameFromVersionedId(String versionedId) {
+         if (versionedId == null) return null;
+         int versionDelimiterPos = versionedId.lastIndexOf(VERSION_DELIMITER);
+         if (versionDelimiterPos != -1) {
+             return versionedId.substring(0, versionDelimiterPos);
+         } else {
+             return null;
+         }
+     }
+ 
+     public static String getVersionFromVersionedId(String versionedId) {
+         if (versionedId == null) return null;
+         int versionDelimiterPos = versionedId.lastIndexOf(VERSION_DELIMITER);
+         if (versionDelimiterPos != -1) {
+             return versionedId.substring(versionDelimiterPos+1);
+         } else {
+             return null;
+         }
+     }
+ 
+     public static String getVersionedId(String id, String version) {
+         // TODO null checks
+         return id + VERSION_DELIMITER + version;
+     }
+ 
+     /** @deprecated since 0.9.0 use {@link BrooklynTypeRegistry#get(String, org.apache.brooklyn.api.typereg.BrooklynTypeRegistry.RegisteredTypeKind, Class)} */
+     // only a handful of items remaining, and those require a CatalogItem
+     public static CatalogItem<?, ?> getCatalogItemOptionalVersion(ManagementContext mgmt, String versionedId) {
+         if (versionedId == null) return null;
+         if (looksLikeVersionedId(versionedId)) {
+             String id = getSymbolicNameFromVersionedId(versionedId);
+             String version = getVersionFromVersionedId(versionedId);
+             return mgmt.getCatalog().getCatalogItem(id, version);
+         } else {
+             return mgmt.getCatalog().getCatalogItem(versionedId, BrooklynCatalog.DEFAULT_VERSION);
+         }
+     }
+ 
+     public static boolean isBestVersion(ManagementContext mgmt, CatalogItem<?,?> item) {
 -        RegisteredType bestVersion = mgmt.getTypeRegistry().get(item.getSymbolicName(), BrooklynCatalog.DEFAULT_VERSION);
 -        if (bestVersion==null) return false;
 -        return (bestVersion.getVersion().equals(item.getVersion()));
++        RegisteredType best = RegisteredTypes.getBestVersion(mgmt.getTypeRegistry().getMatching(
++            RegisteredTypePredicates.symbolicName(item.getSymbolicName())));
++        if (best==null) return false;
++        return (best.getVersion().equals(item.getVersion()));
+     }
+ 
+     /** @deprecated since 0.9.0 use {@link BrooklynTypeRegistry#get(String, org.apache.brooklyn.api.typereg.BrooklynTypeRegistry.RegisteredTypeKind, Class)} */
+     // only a handful of items remaining, and those require a CatalogItem
+     public static <T,SpecT> CatalogItem<T, SpecT> getCatalogItemOptionalVersion(ManagementContext mgmt, Class<T> type, String versionedId) {
+         if (looksLikeVersionedId(versionedId)) {
+             String id = getSymbolicNameFromVersionedId(versionedId);
+             String version = getVersionFromVersionedId(versionedId);
+             return mgmt.getCatalog().getCatalogItem(type, id, version);
+         } else {
+             return mgmt.getCatalog().getCatalogItem(type, versionedId, BrooklynCatalog.DEFAULT_VERSION);
+         }
+     }
+ 
+     /** @deprecated since it was introduced in 0.9.0; TBD where this should live */
+     public static void setDeprecated(ManagementContext mgmt, String symbolicNameAndOptionalVersion, boolean newValue) {
+         RegisteredType item = mgmt.getTypeRegistry().get(symbolicNameAndOptionalVersion);
 -        Preconditions.checkNotNull(item, "No such item: "+symbolicNameAndOptionalVersion);
++        Preconditions.checkNotNull(item, "No such item: " + symbolicNameAndOptionalVersion);
+         setDeprecated(mgmt, item.getSymbolicName(), item.getVersion(), newValue);
+     }
+     
+     /** @deprecated since it was introduced in 0.9.0; TBD where this should live */
+     public static void setDisabled(ManagementContext mgmt, String symbolicNameAndOptionalVersion, boolean newValue) {
+         RegisteredType item = mgmt.getTypeRegistry().get(symbolicNameAndOptionalVersion);
+         Preconditions.checkNotNull(item, "No such item: "+symbolicNameAndOptionalVersion);
+         setDisabled(mgmt, item.getSymbolicName(), item.getVersion(), newValue);
+     }
+     
+     /** @deprecated since it was introduced in 0.9.0; TBD where this should live */
+     @Deprecated
+     public static void setDeprecated(ManagementContext mgmt, String symbolicName, String version, boolean newValue) {
+         CatalogItem<?, ?> item = mgmt.getCatalog().getCatalogItem(symbolicName, version);
+         Preconditions.checkNotNull(item, "No such item: "+symbolicName+" v "+version);
+         item.setDeprecated(newValue);
+         mgmt.getCatalog().persist(item);
+     }
+ 
+     /** @deprecated since it was introduced in 0.9.0; TBD where this should live */
+     @Deprecated
+     public static void setDisabled(ManagementContext mgmt, String symbolicName, String version, boolean newValue) {
+         CatalogItem<?, ?> item = mgmt.getCatalog().getCatalogItem(symbolicName, version);
+         Preconditions.checkNotNull(item, "No such item: "+symbolicName+" v "+version);
+         item.setDisabled(newValue);
+         mgmt.getCatalog().persist(item);
+     }
+ 
+ }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/018a0e15/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/config/BasicConfigKey.java
----------------------------------------------------------------------
diff --cc brooklyn-server/core/src/main/java/org/apache/brooklyn/core/config/BasicConfigKey.java
index 0000000,fe5e064..2e59185
mode 000000,100644..100644
--- a/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/config/BasicConfigKey.java
+++ b/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/config/BasicConfigKey.java
@@@ -1,0 -1,316 +1,321 @@@
+ /*
+  * 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.config;
+ 
+ import static com.google.common.base.Preconditions.checkNotNull;
+ 
+ import java.io.Serializable;
+ import java.util.Collection;
+ import java.util.Map;
+ import java.util.concurrent.ExecutionException;
+ 
+ import javax.annotation.Nonnull;
+ import javax.annotation.Nullable;
+ 
+ import org.apache.brooklyn.api.mgmt.ExecutionContext;
+ import org.apache.brooklyn.config.ConfigInheritance;
+ import org.apache.brooklyn.config.ConfigKey;
+ import org.apache.brooklyn.util.core.internal.ConfigKeySelfExtracting;
+ import org.apache.brooklyn.util.core.task.Tasks;
+ import org.apache.brooklyn.util.exceptions.Exceptions;
+ import org.apache.brooklyn.util.guava.TypeTokens;
+ import org.slf4j.Logger;
+ import org.slf4j.LoggerFactory;
+ 
+ import com.google.common.annotations.Beta;
+ import com.google.common.base.Objects;
+ import com.google.common.base.Preconditions;
+ import com.google.common.base.Predicate;
+ import com.google.common.base.Predicates;
+ import com.google.common.base.Splitter;
+ import com.google.common.collect.Lists;
+ import com.google.common.reflect.TypeToken;
+ 
+ public class BasicConfigKey<T> implements ConfigKeySelfExtracting<T>, Serializable {
+     
+     @SuppressWarnings("unused")
+     private static final Logger log = LoggerFactory.getLogger(BasicConfigKey.class);
+     private static final long serialVersionUID = -1762014059150215376L;
+     
+     private static final Splitter dots = Splitter.on('.');
+ 
+     public static <T> Builder<T> builder(TypeToken<T> type) {
+         return new Builder<T>().type(type);
+     }
+ 
+     public static <T> Builder<T> builder(Class<T> type) {
+         return new Builder<T>().type(type);
+     }
+ 
+     public static <T> Builder<T> builder(TypeToken<T> type, String name) {
+         return new Builder<T>().type(type).name(name);
+     }
+ 
+     public static <T> Builder<T> builder(Class<T> type, String name) {
+         return new Builder<T>().type(type).name(name);
+     }
+ 
+     public static <T> Builder<T> builder(ConfigKey<T> key) {
+         return new Builder<T>()
+             .name(checkNotNull(key.getName(), "name"))
+             .type(checkNotNull(key.getTypeToken(), "type"))
+             .description(key.getDescription())
+             .defaultValue(key.getDefaultValue())
+             .reconfigurable(key.isReconfigurable())
+             .inheritance(key.getInheritance())
+             .constraint(key.getConstraint());
+     }
+ 
+     public static class Builder<T> {
+         private String name;
+         private TypeToken<T> type;
+         private String description;
+         private T defaultValue;
+         private boolean reconfigurable;
+         private Predicate<? super T> constraint = Predicates.alwaysTrue();
+         private ConfigInheritance inheritance;
+         
+         public Builder<T> name(String val) {
+             this.name = val; return this;
+         }
+         public Builder<T> type(Class<T> val) {
+             this.type = TypeToken.of(val); return this;
+         }
+         public Builder<T> type(TypeToken<T> val) {
+             this.type = val; return this;
+         }
+         public Builder<T> description(String val) {
+             this.description = val; return this;
+         }
+         public Builder<T> defaultValue(T val) {
+             this.defaultValue = val; return this;
+         }
+         public Builder<T> reconfigurable(boolean val) {
+             this.reconfigurable = val; return this;
+         }
+         public Builder<T> inheritance(ConfigInheritance val) {
+             this.inheritance = val; return this;
+         }
+         @Beta
+         public Builder<T> constraint(Predicate<? super T> constraint) {
+             this.constraint = checkNotNull(constraint, "constraint"); return this;
+         }
+         public BasicConfigKey<T> build() {
+             return new BasicConfigKey<T>(this);
+         }
+     }
+     
+     private String name;
+     private TypeToken<T> typeToken;
+     private Class<? super T> type;
+     private String description;
+     private T defaultValue;
+     private boolean reconfigurable;
+     private ConfigInheritance inheritance;
+     private Predicate<? super T> constraint;
+ 
+     // FIXME In groovy, fields were `public final` with a default constructor; do we need the gson?
+     public BasicConfigKey() { /* for gson */ }
+ 
+     public BasicConfigKey(Class<T> type, String name) {
+         this(TypeToken.of(type), name);
+     }
+ 
+     public BasicConfigKey(Class<T> type, String name, String description) {
+         this(TypeToken.of(type), name, description);
+     }
+ 
+     public BasicConfigKey(Class<T> type, String name, String description, T defaultValue) {
+         this(TypeToken.of(type), name, description, defaultValue);
+     }
+ 
+     public BasicConfigKey(TypeToken<T> type, String name) {
+         this(type, name, name, null);
+     }
+     
+     public BasicConfigKey(TypeToken<T> type, String name, String description) {
+         this(type, name, description, null);
+     }
+     
+     public BasicConfigKey(TypeToken<T> type, String name, String description, T defaultValue) {
+         this.description = description;
+         this.name = checkNotNull(name, "name");
+         
+         this.type = TypeTokens.getRawTypeIfRaw(checkNotNull(type, "type"));
+         this.typeToken = TypeTokens.getTypeTokenIfNotRaw(type);
+         
+         this.defaultValue = defaultValue;
+         this.reconfigurable = false;
+         this.constraint = Predicates.alwaysTrue();
+     }
+ 
+     protected BasicConfigKey(Builder<T> builder) {
+         this.name = checkNotNull(builder.name, "name");
+         this.type = TypeTokens.getRawTypeIfRaw(checkNotNull(builder.type, "type"));
+         this.typeToken = TypeTokens.getTypeTokenIfNotRaw(builder.type);
+         this.description = builder.description;
+         this.defaultValue = builder.defaultValue;
+         this.reconfigurable = builder.reconfigurable;
+         this.inheritance = builder.inheritance;
+         // Note: it's intentionally possible to have default values that are not valid
+         // per the configured constraint. If validity were checked here any class that
+         // contained a weirdly-defined config key would fail to initialise.
+         this.constraint = checkNotNull(builder.constraint, "constraint");
+     }
+ 
+     /** @see ConfigKey#getName() */
+     @Override public String getName() { return name; }
+ 
+     /** @see ConfigKey#getTypeName() */
+     @Override public String getTypeName() { return getType().getName(); }
+ 
+     /** @see ConfigKey#getType() */
+     @Override public Class<? super T> getType() { return TypeTokens.getRawType(typeToken, type); }
+ 
+     /** @see ConfigKey#getTypeToken() */
+     @Override public TypeToken<T> getTypeToken() { return TypeTokens.getTypeToken(typeToken, type); }
+     
+     /** @see ConfigKey#getDescription() */
+     @Override public String getDescription() { return description; }
+ 
+     /** @see ConfigKey#getDefaultValue() */
+     @Override public T getDefaultValue() { return defaultValue; }
+ 
+     /** @see ConfigKey#hasDefaultValue() */
+     @Override public boolean hasDefaultValue() {
+         return defaultValue != null;
+     }
+ 
+     /** @see ConfigKey#isReconfigurable() */
+     @Override
+     public boolean isReconfigurable() {
+         return reconfigurable;
+     }
+     
+     /** @see ConfigKey#getInheritance() */
+     @Override @Nullable
+     public ConfigInheritance getInheritance() {
+         return inheritance;
+     }
+ 
+     /** @see ConfigKey#getConstraint() */
+     @Override @Nonnull
+     public Predicate<? super T> getConstraint() {
 -        return constraint;
++        // Could be null after rebinding
++        if (constraint != null) {
++            return constraint;
++        } else {
++            return Predicates.alwaysTrue();
++        }
+     }
+ 
+     /** @see ConfigKey#isValueValid(T) */
+     @Override
+     public boolean isValueValid(T value) {
+         // The likeliest source of an exception is a constraint from Guava that expects a non-null input.
+         try {
+             return getConstraint().apply(value);
+         } catch (Exception e) {
+             log.debug("Suppressing exception when testing validity of " + this, e);
+             return false;
+         }
+     }
+ 
+     /** @see ConfigKey#getNameParts() */
+     @Override public Collection<String> getNameParts() {
+         return Lists.newArrayList(dots.split(name));
+     }
+  
+     @Override
+     public boolean equals(Object obj) {
+         if (obj == this) return true;
+         if (!(obj instanceof BasicConfigKey)) return false;
+         BasicConfigKey<?> o = (BasicConfigKey<?>) obj;
+         
+         return Objects.equal(name,  o.name);
+     }
+     
+     @Override
+     public int hashCode() {
+         return Objects.hashCode(name);
+     }
+     
+     @Override
+     public String toString() {
+         return String.format("%s[ConfigKey:%s]", name, getTypeName());
+     }
+ 
+     /**
+      * Retrieves the value corresponding to this config key from the given map.
+      * Could be overridden by more sophisticated config keys, such as MapConfigKey etc.
+      */
+     @SuppressWarnings("unchecked")
+     @Override
+     public T extractValue(Map<?,?> vals, ExecutionContext exec) {
+         Object v = vals.get(this);
+         try {
+             return (T) resolveValue(v, exec);
+         } catch (Exception e) {
+             throw Exceptions.propagate(e);
+         }
+     }
+     
+     @Override
+     public boolean isSet(Map<?,?> vals) {
+         return vals.containsKey(this);
+     }
+     
+     protected Object resolveValue(Object v, ExecutionContext exec) throws ExecutionException, InterruptedException {
+         if (v instanceof Collection || v instanceof Map) {
+             return Tasks.resolveDeepValue(v, Object.class, exec, "config "+name);
+         } else {
+             return Tasks.resolveValue(v, getType(), exec, "config "+name);
+         }
+     }
+ 
+     /** used to record a key which overwrites another; only needed at disambiguation time 
+      * if a class declares a key and an equivalent one (often inherited) which overwrites it.
+      * See org.apache.brooklyn.core.entity.ConfigEntityInheritanceTest, and uses of this class, for more explanation.
+      */
+     public static class BasicConfigKeyOverwriting<T> extends BasicConfigKey<T> {
+         private static final long serialVersionUID = -3458116971918128018L;
+ 
+         private final ConfigKey<T> parentKey;
+ 
+         /** builder here should be based on the same key passed in as parent */
+         @Beta
+         public BasicConfigKeyOverwriting(Builder<T> builder, ConfigKey<T> parent) {
+             super(builder);
+             parentKey = parent;
+             Preconditions.checkArgument(Objects.equal(builder.name, parent.getName()), "Builder must use key of the same name.");
+         }
+         
+         public BasicConfigKeyOverwriting(ConfigKey<T> key, T defaultValue) {
+             this(builder(key).defaultValue(defaultValue), key);
+         }
+         
+         public BasicConfigKeyOverwriting(ConfigKey<T> key, String newDescription, T defaultValue) {
+             this(builder(key).description(newDescription).defaultValue(defaultValue), key);
+         }
+         
+         public ConfigKey<T> getParentKey() {
+             return parentKey;
+         }
+     }
+ }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/018a0e15/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/config/Sanitizer.java
----------------------------------------------------------------------
diff --cc brooklyn-server/core/src/main/java/org/apache/brooklyn/core/config/Sanitizer.java
index 0000000,f9b5de5..59ca6af
mode 000000,100644..100644
--- a/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/config/Sanitizer.java
+++ b/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/config/Sanitizer.java
@@@ -1,0 -1,172 +1,172 @@@
+ /*
+  * 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.config;
+ 
+ import java.util.List;
+ import java.util.Map;
+ import java.util.Set;
+ 
+ import org.apache.brooklyn.util.core.config.ConfigBag;
+ 
 -import com.google.api.client.util.Lists;
+ import com.google.common.base.Predicate;
+ import com.google.common.collect.ImmutableList;
++import com.google.common.collect.Lists;
+ import com.google.common.collect.Maps;
+ import com.google.common.collect.Sets;
+ 
+ public final class Sanitizer {
+ 
+     /**
+      * Names that, if they appear anywhere in an attribute/config/field
+      * indicates that it may be private, so should not be logged etc.
+      */
+     public static final List<String> SECRET_NAMES = ImmutableList.of(
+             "password", 
+             "passwd", 
+             "credential", 
+             "secret", 
+             "private",
+             "access.cert", 
+             "access.key");
+ 
+     public static final Predicate<Object> IS_SECRET_PREDICATE = new IsSecretPredicate();
+ 
+     private static class IsSecretPredicate implements Predicate<Object> {
+         @Override
+         public boolean apply(Object name) {
+             String lowerName = name.toString().toLowerCase();
+             for (String secretName : SECRET_NAMES) {
+                 if (lowerName.contains(secretName))
+                     return true;
+             }
+             return false;
+         }
+     };
+ 
+     /**
+      * Kept only in case this anonymous inner class has made it into any persisted state.
+      * 
+      * @deprecated since 0.7.0
+      */
+     @Deprecated
+     @SuppressWarnings("unused")
+     private static final Predicate<Object> IS_SECRET_PREDICATE_DEPRECATED = new Predicate<Object>() {
+         @Override
+         public boolean apply(Object name) {
+             String lowerName = name.toString().toLowerCase();
+             for (String secretName : SECRET_NAMES) {
+                 if (lowerName.contains(secretName))
+                     return true;
+             }
+             return false;
+         }
+     };
+ 
+     public static Sanitizer newInstance(Predicate<Object> sanitizingNeededCheck) {
+         return new Sanitizer(sanitizingNeededCheck);
+     }
+     
+     public static Sanitizer newInstance(){
+         return newInstance(IS_SECRET_PREDICATE);
+     }
+ 
+     public static Map<String, Object> sanitize(ConfigBag input) {
+         return sanitize(input.getAllConfig());
+     }
+ 
+     public static <K> Map<K, Object> sanitize(Map<K, ?> input) {
+         return sanitize(input, Sets.newHashSet());
+     }
+ 
+     static <K> Map<K, Object> sanitize(Map<K, ?> input, Set<Object> visited) {
+         return newInstance().apply(input, visited);
+     }
+     
+     private Predicate<Object> predicate;
+ 
+     private Sanitizer(Predicate<Object> sanitizingNeededCheck) {
+         predicate = sanitizingNeededCheck;
+     }
+ 
+     public <K> Map<K, Object> apply(Map<K, ?> input) {
+         return apply(input, Sets.newHashSet());
+     }
+ 
+     private <K> Map<K, Object> apply(Map<K, ?> input, Set<Object> visited) {
+         Map<K, Object> result = Maps.newLinkedHashMap();
+         for (Map.Entry<K, ?> e : input.entrySet()) {
+             if (predicate.apply(e.getKey())){
+                 result.put(e.getKey(), "xxxxxxxx");
+                 continue;
+             } 
+             
+             // need to compare object reference, not equality since we may miss some.
+             // not a perfect identifier, but very low probability of collision.
+             if (visited.contains(System.identityHashCode(e.getValue()))) {
+                 result.put(e.getKey(), e.getValue());
+                 continue;
+             }
+ 
+             visited.add(System.identityHashCode(e.getValue()));
+             if (e.getValue() instanceof Map) {
+                 result.put(e.getKey(), apply((Map<?, ?>) e.getValue(), visited));
+             } else if (e.getValue() instanceof List) {
+                 result.put(e.getKey(), applyList((List<?>) e.getValue(), visited));
+             } else if (e.getValue() instanceof Set) {
+                 result.put(e.getKey(), applySet((Set<?>) e.getValue(), visited));
+             } else {
+                 result.put(e.getKey(), e.getValue());
+             }
+         }
+         return result;
+     }
+     
+     private List<Object> applyIterable(Iterable<?> input, Set<Object> visited){
+         List<Object> result = Lists.newArrayList();
+         for(Object o : input){
+             if(visited.contains(System.identityHashCode(o))){
+                 result.add(o);
+                 continue;
+             }
+ 
+             visited.add(System.identityHashCode(o));
+             if (o instanceof Map) {
+                 result.add(apply((Map<?, ?>) o, visited));
+             } else if (o instanceof List) {
+                 result.add(applyList((List<?>) o, visited));
+             } else if (o instanceof Set) {
+                 result.add(applySet((Set<?>) o, visited));
+             } else {
+                 result.add(o);
+             }
+ 
+         }
+         return result;
+     }
+     
+     private List<Object> applyList(List<?> input, Set<Object> visited) {
+        return applyIterable(input, visited);
+     }
+     
+     private Set<Object> applySet(Set<?> input, Set<Object> visited) {
+         Set<Object> result = Sets.newLinkedHashSet();
+         result.addAll(applyIterable(input, visited));
+         return result;
+     }
+ }