[SPLITPREP] move more PR items to the right place

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

Branch: refs/heads/master
Commit: 8a3c17b963576ec75b66d1e7b20422f86a0df11d
Parents: 018a0e1
Author: Alex Heneveld <[email protected]>
Authored: Mon Dec 21 12:37:58 2015 +0000
Committer: Alex Heneveld <[email protected]>
Committed: Mon Dec 21 16:43:29 2015 +0000

----------------------------------------------------------------------
 .../downstreamparent/DownstreamParentTest.java  |  64 +++
 .../test/projects/downstream-parent-test/README |   5 +
 .../projects/downstream-parent-test/pom.xml     | 120 +++++
 .../src/main/java/com/example/HelloEntity.java  |  26 +
 .../main/java/com/example/HelloEntityImpl.java  |  31 ++
 .../src/main/resources/blueprint.yaml           |  19 +
 .../src/main/resources/catalog.bom              |  33 ++
 .../dns/geoscaling/GeoDnsServiceYamlTest.java   |  45 ++
 .../brooklyn/entity/dns/geoscaling/geodns.yaml  |  42 ++
 .../ExternalConfigBrooklynPropertiesTest.java   | 146 ++++++
 .../core/internal/BrooklynPropertiesImpl.java   | 477 +++++++++++++++++++
 .../core/mgmt/internal/CampYamlParser.java      |  34 ++
 .../internal/DeferredBrooklynProperties.java    | 370 ++++++++++++++
 .../brooklyn/util/text/VersionComparator.java   | 199 ++++++++
 .../util/text/VersionComparatorTest.java        | 102 ++++
 .../core/internal/BrooklynPropertiesImpl.java   | 477 -------------------
 .../core/mgmt/internal/CampYamlParser.java      |  34 --
 .../internal/DeferredBrooklynProperties.java    | 370 --------------
 .../ExternalConfigBrooklynPropertiesTest.java   | 146 ------
 .../camp/brooklyn/GeoDnsServiceYamlTest.java    |  45 --
 .../apache/brooklyn/camp/brooklyn/geodns.yaml   |  42 --
 .../downstreamparent/DownstreamParentTest.java  |  64 ---
 .../test/projects/downstream-parent-test/README |   5 -
 .../projects/downstream-parent-test/pom.xml     | 120 -----
 .../src/main/java/com/example/HelloEntity.java  |  26 -
 .../main/java/com/example/HelloEntityImpl.java  |  31 --
 .../src/main/resources/blueprint.yaml           |  19 -
 .../src/main/resources/catalog.bom              |  33 --
 .../brooklyn/util/text/VersionComparator.java   | 199 --------
 .../util/text/VersionComparatorTest.java        | 102 ----
 30 files changed, 1713 insertions(+), 1713 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/8a3c17b9/brooklyn-library/qa/src/test/java/org/apache/brooklyn/qa/downstreamparent/DownstreamParentTest.java
----------------------------------------------------------------------
diff --git 
a/brooklyn-library/qa/src/test/java/org/apache/brooklyn/qa/downstreamparent/DownstreamParentTest.java
 
b/brooklyn-library/qa/src/test/java/org/apache/brooklyn/qa/downstreamparent/DownstreamParentTest.java
new file mode 100644
index 0000000..33c4c42
--- /dev/null
+++ 
b/brooklyn-library/qa/src/test/java/org/apache/brooklyn/qa/downstreamparent/DownstreamParentTest.java
@@ -0,0 +1,64 @@
+/*
+ * 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.qa.downstreamparent;
+
+import static org.testng.Assert.assertTrue;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.apache.brooklyn.util.net.Networking;
+import org.apache.maven.it.Verifier;
+import org.apache.maven.shared.utils.io.FileUtils;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableMap;
+
+public class DownstreamParentTest {
+
+    private static final String PROJECTS_DIR = "src/test/projects";
+    private static final String WORK_DIR = "target/ut/";
+
+    /**
+     * Asserts that a trivial project using brooklyn-downstream-parent can be
+     * loaded into Brooklyn's catalogue and its entities deployed.
+     */
+    @Test(groups = "Integration")
+    public void 
testDownstreamProjectsCanBeLoadedIntoBrooklynCatalogByDefault() throws 
Exception {
+        int port = Networking.nextAvailablePort(57000);
+        File dir = getBasedir("downstream-parent-test");
+        Verifier verifier = new Verifier(dir.getAbsolutePath());
+        verifier.setMavenDebug(true);
+        verifier.executeGoal("post-integration-test", ImmutableMap.of(
+                "bindPort", String.valueOf(port)));
+        verifier.verifyErrorFreeLog();
+        verifier.verifyTextInLog("Hello from the init method of the 
HelloEntity");
+    }
+
+    /** Replicates the behaviour of getBasedir in JUnit's TestResources class 
*/
+    public File getBasedir(String project) throws IOException {
+        File src = (new File(PROJECTS_DIR, project)).getCanonicalFile();
+        assertTrue(src.isDirectory(), "Test project directory does not exist: 
" + src.getPath());
+        File basedir = (new File(WORK_DIR, getClass().getSimpleName() + "_" + 
project)).getCanonicalFile();
+        FileUtils.deleteDirectory(basedir);
+        assertTrue(basedir.mkdirs(), "Test project working directory created");
+        FileUtils.copyDirectoryStructure(src, basedir);
+        return basedir;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/8a3c17b9/brooklyn-library/qa/src/test/projects/downstream-parent-test/README
----------------------------------------------------------------------
diff --git 
a/brooklyn-library/qa/src/test/projects/downstream-parent-test/README 
b/brooklyn-library/qa/src/test/projects/downstream-parent-test/README
new file mode 100644
index 0000000..3f2f574
--- /dev/null
+++ b/brooklyn-library/qa/src/test/projects/downstream-parent-test/README
@@ -0,0 +1,5 @@
+A successful build of this project (`mvn clean verify`) means that projects 
that
+use brooklyn-downstream-parent can be loaded into Brooklyn's catalogue by 
default.
+
+If the build fails there is almost certainly something wrong with the parent 
and
+the wider consumers of Brooklyn will probably face similar problems.

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/8a3c17b9/brooklyn-library/qa/src/test/projects/downstream-parent-test/pom.xml
----------------------------------------------------------------------
diff --git 
a/brooklyn-library/qa/src/test/projects/downstream-parent-test/pom.xml 
b/brooklyn-library/qa/src/test/projects/downstream-parent-test/pom.xml
new file mode 100644
index 0000000..7e3c0e0
--- /dev/null
+++ b/brooklyn-library/qa/src/test/projects/downstream-parent-test/pom.xml
@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"; 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/xsd/maven-4.0.0.xsd";>
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>org.apache.brooklyn.downstream-parent-test</groupId>
+    <artifactId>catalogue-load-test</artifactId>
+    <version>0.9.0-SNAPSHOT</version> <!-- BROOKLYN_VERSION -->
+    <packaging>jar</packaging>
+
+    <name>Downstream parent catalogue load test test</name>
+
+    <parent>
+        <groupId>org.apache.brooklyn</groupId>
+        <artifactId>brooklyn-downstream-parent</artifactId>
+        <version>0.9.0-SNAPSHOT</version> <!-- BROOKLYN_VERSION -->
+    </parent>
+
+    <repositories>
+        <repository>
+            <id>apache-snapshots</id>
+            
<url>https://repository.apache.org/content/repositories/snapshots/</url>
+            <snapshots>
+                <enabled>true</enabled>
+            </snapshots>
+        </repository>
+    </repositories>
+
+    <pluginRepositories>
+        <pluginRepository>
+            <id>sonatype-nexus-snapshots</id>
+            <name>Sonatype Nexus Snapshots</name>
+            <url>https://oss.sonatype.org/content/repositories/snapshots</url>
+            <releases>
+                <enabled>false</enabled>
+            </releases>
+            <snapshots>
+                <enabled>true</enabled>
+            </snapshots>
+        </pluginRepository>
+    </pluginRepositories>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.brooklyn</groupId>
+            <artifactId>brooklyn-all</artifactId>
+            <version>0.9.0-SNAPSHOT</version> <!-- BROOKLYN_VERSION -->
+            <scope>provided</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <resources>
+            <resource>
+                <directory>${basedir}/src/main/resources</directory>
+                <filtering>true</filtering>
+            </resource>
+        </resources>
+        <plugins>
+            <plugin>
+                <groupId>io.brooklyn.maven</groupId>
+                <artifactId>brooklyn-maven-plugin</artifactId>
+                <version>0.3.0-SNAPSHOT</version>
+                <executions>
+                    <execution>
+                        <id>Run and deploy Brooklyn</id>
+                        <goals>
+                            <goal>start-server</goal>
+                        </goals>
+                        <configuration>
+                            <bindPort>${bindPort}</bindPort>
+                            <!--
+                            Make sure that the test entities aren't already on 
the classpath.
+                            -->
+                            <outputDirOnClasspath>false</outputDirOnClasspath>
+                            <arguments>
+                                <argument>--catalogInitial</argument>
+                                
<argument>${project.build.outputDirectory}/catalog.bom</argument>
+                            </arguments>
+                        </configuration>
+                    </execution>
+                    <execution>
+                        <id>Deploy entity from catalogue</id>
+                        <phase>integration-test</phase>
+                        <goals>
+                            <goal>deploy</goal>
+                        </goals>
+                        <configuration>
+                            
<blueprint>${project.build.outputDirectory}/blueprint.yaml</blueprint>
+                        </configuration>
+                    </execution>
+                    <execution>
+                        <id>Stop Brooklyn</id>
+                        <goals>
+                            <goal>stop-server</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+</project>

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/8a3c17b9/brooklyn-library/qa/src/test/projects/downstream-parent-test/src/main/java/com/example/HelloEntity.java
----------------------------------------------------------------------
diff --git 
a/brooklyn-library/qa/src/test/projects/downstream-parent-test/src/main/java/com/example/HelloEntity.java
 
b/brooklyn-library/qa/src/test/projects/downstream-parent-test/src/main/java/com/example/HelloEntity.java
new file mode 100644
index 0000000..242708b
--- /dev/null
+++ 
b/brooklyn-library/qa/src/test/projects/downstream-parent-test/src/main/java/com/example/HelloEntity.java
@@ -0,0 +1,26 @@
+/*
+ * 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 com.example;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.entity.ImplementedBy;
+
+@ImplementedBy(HelloEntityImpl.class)
+public interface HelloEntity extends Entity {
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/8a3c17b9/brooklyn-library/qa/src/test/projects/downstream-parent-test/src/main/java/com/example/HelloEntityImpl.java
----------------------------------------------------------------------
diff --git 
a/brooklyn-library/qa/src/test/projects/downstream-parent-test/src/main/java/com/example/HelloEntityImpl.java
 
b/brooklyn-library/qa/src/test/projects/downstream-parent-test/src/main/java/com/example/HelloEntityImpl.java
new file mode 100644
index 0000000..76d9ffd
--- /dev/null
+++ 
b/brooklyn-library/qa/src/test/projects/downstream-parent-test/src/main/java/com/example/HelloEntityImpl.java
@@ -0,0 +1,31 @@
+/*
+ * 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 com.example;
+
+import org.apache.brooklyn.core.entity.AbstractEntity;
+
+public class HelloEntityImpl extends AbstractEntity implements HelloEntity {
+
+    @Override
+    public void init() {
+        super.init();
+        System.out.println("Hello from the init method of the HelloEntity");
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/8a3c17b9/brooklyn-library/qa/src/test/projects/downstream-parent-test/src/main/resources/blueprint.yaml
----------------------------------------------------------------------
diff --git 
a/brooklyn-library/qa/src/test/projects/downstream-parent-test/src/main/resources/blueprint.yaml
 
b/brooklyn-library/qa/src/test/projects/downstream-parent-test/src/main/resources/blueprint.yaml
new file mode 100644
index 0000000..76cc82e
--- /dev/null
+++ 
b/brooklyn-library/qa/src/test/projects/downstream-parent-test/src/main/resources/blueprint.yaml
@@ -0,0 +1,19 @@
+# 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.
+#
+services:
+- type: downstream-project

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/8a3c17b9/brooklyn-library/qa/src/test/projects/downstream-parent-test/src/main/resources/catalog.bom
----------------------------------------------------------------------
diff --git 
a/brooklyn-library/qa/src/test/projects/downstream-parent-test/src/main/resources/catalog.bom
 
b/brooklyn-library/qa/src/test/projects/downstream-parent-test/src/main/resources/catalog.bom
new file mode 100644
index 0000000..c168c72
--- /dev/null
+++ 
b/brooklyn-library/qa/src/test/projects/downstream-parent-test/src/main/resources/catalog.bom
@@ -0,0 +1,33 @@
+# 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.
+#
+brooklyn.catalog:
+  version: 1.0
+
+  brooklyn.libraries:
+  - 
"file://${project.build.directory}/${project.build.finalName}.${project.packaging}"
+
+  items:
+
+  - id: downstream-project
+    name: Downstream project
+    itemType: template
+    description: |
+      A downstream project
+    item:
+      services:
+      - type: com.example.HelloEntity

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/8a3c17b9/brooklyn-library/software/webapp/src/test/java/org/apache/brooklyn/entity/dns/geoscaling/GeoDnsServiceYamlTest.java
----------------------------------------------------------------------
diff --git 
a/brooklyn-library/software/webapp/src/test/java/org/apache/brooklyn/entity/dns/geoscaling/GeoDnsServiceYamlTest.java
 
b/brooklyn-library/software/webapp/src/test/java/org/apache/brooklyn/entity/dns/geoscaling/GeoDnsServiceYamlTest.java
new file mode 100644
index 0000000..3f41e8d
--- /dev/null
+++ 
b/brooklyn-library/software/webapp/src/test/java/org/apache/brooklyn/entity/dns/geoscaling/GeoDnsServiceYamlTest.java
@@ -0,0 +1,45 @@
+/*
+ * 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;
+
+import static org.testng.Assert.assertEquals;
+
+import org.apache.brooklyn.api.entity.Application;
+import org.apache.brooklyn.core.entity.Entities;
+import org.apache.brooklyn.core.mgmt.EntityManagementUtils;
+import org.apache.brooklyn.entity.dns.AbstractGeoDnsService;
+import org.apache.brooklyn.entity.dns.geoscaling.GeoscalingDnsService;
+import org.apache.brooklyn.entity.group.DynamicFabric;
+import org.apache.brooklyn.util.stream.Streams;
+import org.testng.annotations.Test;
+
+public class GeoDnsServiceYamlTest extends AbstractYamlTest {
+
+    @Test
+    public void testTargetGroupCanBeSetInYaml() throws Exception {
+        final String resourceName = "classpath:/" + 
getClass().getPackage().getName().replace('.', '/') + "/geodns.yaml";
+        final String blueprint = Streams.readFully(loadYaml(resourceName));
+        Application app = EntityManagementUtils.createUnstarted(mgmt(), 
blueprint);
+        GeoscalingDnsService geodns = Entities.descendants(app, 
GeoscalingDnsService.class).iterator().next();
+        DynamicFabric fabric = Entities.descendants(app, 
DynamicFabric.class).iterator().next();
+        
assertEquals(geodns.config().get(AbstractGeoDnsService.ENTITY_PROVIDER), 
fabric);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/8a3c17b9/brooklyn-library/software/webapp/src/test/resources/org/apache/brooklyn/entity/dns/geoscaling/geodns.yaml
----------------------------------------------------------------------
diff --git 
a/brooklyn-library/software/webapp/src/test/resources/org/apache/brooklyn/entity/dns/geoscaling/geodns.yaml
 
b/brooklyn-library/software/webapp/src/test/resources/org/apache/brooklyn/entity/dns/geoscaling/geodns.yaml
new file mode 100644
index 0000000..3fdc7b7
--- /dev/null
+++ 
b/brooklyn-library/software/webapp/src/test/resources/org/apache/brooklyn/entity/dns/geoscaling/geodns.yaml
@@ -0,0 +1,42 @@
+#
+# 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.
+#
+services:
+
+- name: Web cluster
+  type: org.apache.brooklyn.entity.group.DynamicRegionsFabric
+  id: web-fabric
+
+  # Location required but test should not do any provisioning.
+  locations:
+  - localhost
+
+  memberSpec:
+    $brooklyn:entitySpec:
+      type: org.apache.brooklyn.entity.webapp.ControlledDynamicWebAppCluster
+      brooklyn.config:
+        initialSize: 0
+
+- name: Geo DNS
+  type: org.apache.brooklyn.entity.dns.geoscaling.GeoscalingDnsService
+  brooklyn.config:
+    provider: $brooklyn:component("web-fabric")
+    username: madeUp
+    password: madeUp
+    primaryDomainName: example.com
+    smartSubdomainName: test

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/8a3c17b9/brooklyn-server/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/ExternalConfigBrooklynPropertiesTest.java
----------------------------------------------------------------------
diff --git 
a/brooklyn-server/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/ExternalConfigBrooklynPropertiesTest.java
 
b/brooklyn-server/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/ExternalConfigBrooklynPropertiesTest.java
new file mode 100644
index 0000000..39b444d
--- /dev/null
+++ 
b/brooklyn-server/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/ExternalConfigBrooklynPropertiesTest.java
@@ -0,0 +1,146 @@
+/*
+ * 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;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+import org.apache.brooklyn.api.mgmt.ExecutionContext;
+import 
org.apache.brooklyn.camp.brooklyn.ExternalConfigYamlTest.MyExternalConfigSupplier;
+import 
org.apache.brooklyn.camp.brooklyn.ExternalConfigYamlTest.MyExternalConfigSupplierWithoutMapArg;
+import org.apache.brooklyn.core.config.ConfigKeys;
+import org.apache.brooklyn.core.config.ConfigPredicates;
+import org.apache.brooklyn.core.internal.BrooklynProperties;
+import org.apache.brooklyn.core.mgmt.internal.LocalManagementContext;
+import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal;
+import org.apache.brooklyn.core.test.entity.LocalManagementContextForTests;
+import org.apache.brooklyn.location.jclouds.JcloudsLocation;
+import org.apache.brooklyn.test.Asserts;
+import org.apache.brooklyn.util.core.task.DeferredSupplier;
+import org.apache.brooklyn.util.core.task.Tasks;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableMap;
+
+@Test
+public class ExternalConfigBrooklynPropertiesTest extends AbstractYamlTest {
+
+    @Override
+    protected LocalManagementContext newTestManagementContext() {
+        BrooklynProperties props = BrooklynProperties.Factory.newEmpty();
+        props.put("brooklyn.external.myprovider", 
MyExternalConfigSupplier.class.getName());
+        props.put("brooklyn.external.myprovider.mykey", "myval");
+        props.put("brooklyn.external.myprovider.mykey2", "myval2");
+        props.put("brooklyn.external.myproviderWithoutMapArg", 
MyExternalConfigSupplierWithoutMapArg.class.getName());
+        props.put("myproperty", "$brooklyn:external(\"myprovider\", 
\"mykey\")");
+
+        return LocalManagementContextForTests.builder(true)
+                .useProperties(props)
+                .build();
+    }
+
+    // Yaml parsing support is more generic than just external-config.
+    // Test other parsing here, even though it's not directly related to 
external-config.
+    @Test
+    public void testYamlLiteralFromPropertiesInLocation() throws Exception {
+        ((ManagementContextInternal)mgmt()).getBrooklynProperties().put(
+                ConfigKeys.newStringConfigKey("myDynamicProperty"), 
"$brooklyn:literal(\"myliteral\")");
+        
+        String val = 
mgmt().getConfig().getConfig(ConfigKeys.newStringConfigKey("myDynamicProperty"));
+        assertEquals(val, "myliteral");
+    }
+
+    @Test
+    public void testInvalidYamlExpression() throws Exception {
+        ((ManagementContextInternal)mgmt()).getBrooklynProperties().put(
+                ConfigKeys.newStringConfigKey("myInvalidExternal"), 
"$brooklyn:external");
+        
+        try {
+            String val = 
mgmt().getConfig().getConfig(ConfigKeys.newStringConfigKey("myInvalidExternal"));
+            Asserts.shouldHaveFailedPreviously("val="+val);
+        } catch (IllegalArgumentException e) {
+            Asserts.expectedFailureContains(e, "Error evaluating node");
+        }
+    }
+
+    @Test
+    public void testExternalisedConfigFromPropertiesInLocation() throws 
Exception {
+        BrooklynProperties props = 
((ManagementContextInternal)mgmt()).getBrooklynProperties();
+        props.put("brooklyn.location.jclouds.aws-ec2.identity", 
"$brooklyn:external(\"myprovider\", \"mykey\")");
+        props.put("brooklyn.location.jclouds.aws-ec2.credential", 
"$brooklyn:external(\"myprovider\", \"mykey2\")");
+        
+        JcloudsLocation loc = (JcloudsLocation) 
mgmt().getLocationRegistry().resolve("jclouds:aws-ec2:us-east-1");
+        assertEquals(loc.getIdentity(), "myval");
+        assertEquals(loc.getCredential(), "myval2");
+    }
+
+    @Test
+    public void testExternalisedConfigInProperties() throws Exception {
+        runExternalisedConfigGetters("myproperty", "myval");
+    }
+    
+    @Test
+    public void testExternalisedConfigInAddedStringProperty() throws Exception 
{
+        ((ManagementContextInternal)mgmt()).getBrooklynProperties().put(
+                "myDynamicProperty", "$brooklyn:external(\"myprovider\", 
\"mykey\")");
+        runExternalisedConfigGetters("myDynamicProperty", "myval");
+    }
+    
+    @Test
+    public void testExternalisedConfigInAddedKeyProperty() throws Exception {
+        ((ManagementContextInternal)mgmt()).getBrooklynProperties().put(
+                ConfigKeys.newStringConfigKey("myDynamicProperty"), 
"$brooklyn:external(\"myprovider\", \"mykey\")");
+        runExternalisedConfigGetters("myDynamicProperty", "myval");
+    }
+    
+    @Test
+    public void testExternalisedConfigInAddedMapProperty() throws Exception {
+        ((ManagementContextInternal)mgmt()).getBrooklynProperties().addFromMap(
+                ImmutableMap.of("myDynamicProperty", 
"$brooklyn:external(\"myprovider\", \"mykey\")"));
+        runExternalisedConfigGetters("myDynamicProperty", "myval");
+    }
+
+    protected void runExternalisedConfigGetters(String property, String 
expectedVal) throws Exception {
+        
runExternalisedConfigGetters(((ManagementContextInternal)mgmt()).getBrooklynProperties(),
 property, expectedVal, true);
+    }
+    
+    protected void runExternalisedConfigGetters(BrooklynProperties props, 
String property, String expectedVal, boolean testSubMap) throws Exception {
+        ExecutionContext exec = mgmt().getServerExecutionContext();
+
+        String val1 = props.getConfig(ConfigKeys.newStringConfigKey(property));
+        assertEquals(val1, expectedVal);
+        
+        DeferredSupplier<?> val2 = (DeferredSupplier<?>) 
props.getRawConfig(ConfigKeys.newStringConfigKey(property));
+        assertEquals(Tasks.resolveValue(val2, String.class, exec), 
expectedVal);
+        
+        DeferredSupplier<?> val3 = (DeferredSupplier<?>) 
props.getConfigRaw(ConfigKeys.newStringConfigKey(property), false).get();
+        assertEquals(Tasks.resolveValue(val3, String.class, exec), 
expectedVal);
+
+        DeferredSupplier<?> val4 = (DeferredSupplier<?>) 
props.getAllConfig().get(ConfigKeys.newStringConfigKey(property));
+        assertEquals(Tasks.resolveValue(val4, String.class, exec), 
expectedVal);
+        
+        String val5 = props.getFirst(property);
+        assertTrue(val5.startsWith("$brooklyn:external"), "val="+val5);
+        
+        if (testSubMap) {
+            BrooklynProperties submap = 
props.submap(ConfigPredicates.nameEqualTo(property));
+            runExternalisedConfigGetters(submap, property, expectedVal, false);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/8a3c17b9/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/internal/BrooklynPropertiesImpl.java
----------------------------------------------------------------------
diff --git 
a/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/internal/BrooklynPropertiesImpl.java
 
b/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/internal/BrooklynPropertiesImpl.java
new file mode 100644
index 0000000..023b3e3
--- /dev/null
+++ 
b/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/internal/BrooklynPropertiesImpl.java
@@ -0,0 +1,477 @@
+/*
+ * 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.internal;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import groovy.lang.Closure;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Properties;
+
+import org.apache.brooklyn.config.ConfigKey;
+import org.apache.brooklyn.config.ConfigKey.HasConfigKey;
+import org.apache.brooklyn.core.config.BasicConfigKey;
+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.core.flags.TypeCoercions;
+import org.apache.brooklyn.util.guava.Maybe;
+import org.apache.brooklyn.util.os.Os;
+import org.apache.brooklyn.util.text.StringFunctions;
+import org.apache.brooklyn.util.text.Strings;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.CharMatcher;
+import com.google.common.base.Objects;
+import com.google.common.base.Predicate;
+import com.google.common.base.Throwables;
+import com.google.common.collect.Maps;
+
+/**
+ * TODO methods in this class are not thread safe.
+ * intention is that they are set during startup and not modified thereafter.
+ */
+@SuppressWarnings("rawtypes")
+public class BrooklynPropertiesImpl extends LinkedHashMap implements 
BrooklynProperties {
+
+    private static final long serialVersionUID = -945875483083108978L;
+    private static final Logger LOG = 
LoggerFactory.getLogger(BrooklynPropertiesImpl.class);
+
+    public static class Factory {
+        /** creates a new empty {@link BrooklynPropertiesImpl} */
+        public static BrooklynPropertiesImpl newEmpty() {
+            return new BrooklynPropertiesImpl();
+        }
+
+        /** creates a new {@link BrooklynPropertiesImpl} with contents loaded 
+         * from the usual places, including *.properties files and environment 
variables */
+        public static BrooklynPropertiesImpl newDefault() {
+            return new Builder(true).build();
+        }
+
+        public static Builder builderDefault() {
+            return new Builder(true);
+        }
+
+        public static Builder builderEmpty() {
+            return new Builder(false);
+        }
+
+        public static class Builder {
+            private String defaultLocationMetadataUrl;
+            private String globalLocationMetadataFile = null;
+            private String globalPropertiesFile = null;
+            private String localPropertiesFile = null;
+            private BrooklynPropertiesImpl originalProperties = null;
+            
+            /** @deprecated since 0.7.0 use static methods in {@link Factory} 
to create */
+            public Builder() {
+                this(true);
+            }
+            
+            private Builder(boolean setGlobalFileDefaults) {
+                resetDefaultLocationMetadataUrl();
+                if (setGlobalFileDefaults) {
+                    resetGlobalFiles();
+                }
+            }
+            
+            public Builder resetDefaultLocationMetadataUrl() {
+                defaultLocationMetadataUrl = 
"classpath://brooklyn/location-metadata.properties";
+                return this;
+            }
+            public Builder resetGlobalFiles() {
+                defaultLocationMetadataUrl = 
"classpath://brooklyn/location-metadata.properties";
+                globalLocationMetadataFile = Os.mergePaths(Os.home(), 
".brooklyn", "location-metadata.properties");
+                globalPropertiesFile = Os.mergePaths(Os.home(), ".brooklyn", 
"brooklyn.properties");
+                return this;
+            }
+            
+            /**
+             * Creates a Builder that when built, will return the 
BrooklynProperties passed to this constructor
+             */
+            private Builder(BrooklynPropertiesImpl originalProperties) {
+                this.originalProperties = new 
BrooklynPropertiesImpl().addFromMap(originalProperties);
+            }
+            
+            /**
+             * The URL of a default location-metadata.properties (for 
meta-data about different locations, such as iso3166 and global lat/lon). 
+             * Defaults to classpath://brooklyn/location-metadata.properties
+             */
+            public Builder defaultLocationMetadataUrl(String val) {
+                defaultLocationMetadataUrl = checkNotNull(val, "file");
+                return this;
+            }
+            
+            /**
+             * The URL of a location-metadata.properties file that appends to 
and overwrites values in the locationMetadataUrl. 
+             * Defaults to ~/.brooklyn/location-metadata.properties
+             */
+            public Builder globalLocationMetadataFile(String val) {
+                globalLocationMetadataFile = checkNotNull(val, "file");
+                return this;
+            }
+            
+            /**
+             * The URL of a shared brooklyn.properties file. Defaults to 
~/.brooklyn/brooklyn.properties.
+             * Can be null to disable.
+             */
+            public Builder globalPropertiesFile(String val) {
+                globalPropertiesFile = val;
+                return this;
+            }
+            
+            @Beta
+            public boolean hasDelegateOriginalProperties() {
+                return this.originalProperties==null;
+            }
+            
+            /**
+             * The URL of a brooklyn.properties file specific to this launch. 
Appends to and overwrites values in globalPropertiesFile.
+             */
+            public Builder localPropertiesFile(String val) {
+                localPropertiesFile = val;
+                return this;
+            }
+            
+            public BrooklynPropertiesImpl build() {
+                if (originalProperties != null) 
+                    return new 
BrooklynPropertiesImpl().addFromMap(originalProperties);
+                
+                BrooklynPropertiesImpl properties = new 
BrooklynPropertiesImpl();
+
+                // TODO Could also read from http://brooklyn.io, for 
up-to-date values?
+                // But might that make unit tests run very badly when 
developer is offline?
+                addPropertiesFromUrl(properties, defaultLocationMetadataUrl, 
false);
+                
+                addPropertiesFromFile(properties, globalLocationMetadataFile);
+                addPropertiesFromFile(properties, globalPropertiesFile);
+                addPropertiesFromFile(properties, localPropertiesFile);
+                
+                properties.addEnvironmentVars();
+                properties.addSystemProperties();
+
+                return properties;
+            }
+
+            public static Builder fromProperties(BrooklynPropertiesImpl 
brooklynProperties) {
+                return new Builder(brooklynProperties);
+            }
+
+            @Override
+            public String toString() {
+                return Objects.toStringHelper(this)
+                        .omitNullValues()
+                        .add("originalProperties", originalProperties)
+                        .add("defaultLocationMetadataUrl", 
defaultLocationMetadataUrl)
+                        .add("globalLocationMetadataUrl", 
globalLocationMetadataFile)
+                        .add("globalPropertiesFile", globalPropertiesFile)
+                        .add("localPropertiesFile", localPropertiesFile)
+                        .toString();
+            }
+        }
+        
+        private static void addPropertiesFromUrl(BrooklynPropertiesImpl p, 
String url, boolean warnIfNotFound) {
+            if (url==null) return;
+            
+            try {
+                
p.addFrom(ResourceUtils.create(BrooklynPropertiesImpl.class).getResourceFromUrl(url));
+            } catch (Exception e) {
+                if (warnIfNotFound)
+                    LOG.warn("Could not load {}; continuing", url);
+                if (LOG.isTraceEnabled()) LOG.trace("Could not load "+url+"; 
continuing", e);
+            }
+        }
+        
+        private static void addPropertiesFromFile(BrooklynPropertiesImpl p, 
String file) {
+            if (file==null) return;
+            
+            String fileTidied = Os.tidyPath(file);
+            File f = new File(fileTidied);
+
+            if (f.exists()) {
+                p.addFrom(f);
+            }
+        }
+    }
+
+    protected BrooklynPropertiesImpl() {
+    }
+
+    public BrooklynPropertiesImpl addEnvironmentVars() {
+        addFrom(System.getenv());
+        return this;
+    }
+
+    public BrooklynPropertiesImpl addSystemProperties() {
+        addFrom(System.getProperties());
+        return this;
+    }
+
+    public BrooklynPropertiesImpl addFrom(ConfigBag cfg) {
+        addFrom(cfg.getAllConfig());
+        return this;
+    }
+
+    @SuppressWarnings("unchecked")
+    public BrooklynPropertiesImpl addFrom(Map map) {
+        putAll(Maps.transformValues(map, StringFunctions.trim()));
+        return this;
+    }
+
+    public BrooklynPropertiesImpl addFrom(InputStream i) {
+        // Ugly way to load them in order, but Properties is a Hashtable so 
loses order otherwise.
+        @SuppressWarnings({ "serial" })
+        Properties p = new Properties() {
+            @Override
+            public synchronized Object put(Object key, Object value) {
+                // Trim the string values to remove leading and trailing spaces
+                String s = (String) value;
+                if (Strings.isBlank(s)) {
+                    s = Strings.EMPTY;
+                } else {
+                    s = CharMatcher.BREAKING_WHITESPACE.trimFrom(s);
+                }
+                return BrooklynPropertiesImpl.this.put(key, s);
+            }
+        };
+        try {
+            p.load(i);
+        } catch (IOException e) {
+            throw Throwables.propagate(e);
+        }
+        return this;
+    }
+    
+    public BrooklynPropertiesImpl addFrom(File f) {
+        if (!f.exists()) {
+            LOG.warn("Unable to find file '"+f.getAbsolutePath()+"' when 
loading properties; ignoring");
+            return this;
+        } else {
+            try {
+                return addFrom(new FileInputStream(f));
+            } catch (FileNotFoundException e) {
+                throw Throwables.propagate(e);
+            }
+        }
+    }
+    public BrooklynPropertiesImpl addFrom(URL u) {
+        try {
+            return addFrom(u.openStream());
+        } catch (IOException e) {
+            throw new RuntimeException("Error reading properties from "+u+": 
"+e, e);
+        }
+    }
+    /**
+     * @see ResourceUtils#getResourceFromUrl(String)
+     *
+     * of the form form file:///home/... or http:// or classpath://xx ;
+     * for convenience if not starting with xxx: it is treated as a classpath 
reference or a file;
+     * throws if not found (but does nothing if argument is null)
+     */
+    public BrooklynPropertiesImpl addFromUrl(String url) {
+        try {
+            if (url==null) return this;
+            return addFrom(ResourceUtils.create(this).getResourceFromUrl(url));
+        } catch (Exception e) {
+            throw new RuntimeException("Error reading properties from "+url+": 
"+e, e);
+        }
+    }
+
+    /** expects a property already set in scope, whose value is acceptable to 
{@link #addFromUrl(String)};
+     * if property not set, does nothing */
+    public BrooklynPropertiesImpl addFromUrlProperty(String urlProperty) {
+        String url = (String) get(urlProperty);
+        if (url==null) addFromUrl(url);
+        return this;
+    }
+
+    /**
+    * adds the indicated properties
+    */
+    public BrooklynPropertiesImpl addFromMap(Map properties) {
+        putAll(properties);
+        return this;
+    }
+
+    /** inserts the value under the given key, if it was not present */
+    public boolean putIfAbsent(String key, Object value) {
+        if (containsKey(key)) return false;
+        put(key, value);
+        return true;
+    }
+
+   /** @deprecated attempts to call get with this syntax are probably 
mistakes; get(key, defaultValue) is fine but
+    * Map is unlikely the key, much more likely they meant getFirst(flags, 
key).
+    */
+   @Deprecated
+   public String get(Map flags, String key) {
+       LOG.warn("Discouraged use of 'BrooklynProperties.get(Map,String)' 
(ambiguous); use getFirst(Map,String) or get(String) -- assuming the former");
+       LOG.debug("Trace for discouraged use of 
'BrooklynProperties.get(Map,String)'",
+           new Throwable("Arguments: "+flags+" "+key));
+       return getFirst(flags, key);
+   }
+
+    /** returns the value of the first key which is defined
+     * <p>
+     * takes the following flags:
+     * 'warnIfNone', 'failIfNone' (both taking a boolean (to use default 
message) or a string (which is the message));
+     * and 'defaultIfNone' (a default value to return if there is no such 
property); defaults to no warning and null response */
+    @Override
+    public String getFirst(String ...keys) {
+       return getFirst(MutableMap.of(), keys);
+    }
+    @Override
+    public String getFirst(Map flags, String ...keys) {
+        for (String k: keys) {
+            if (k!=null && containsKey(k)) return (String) get(k);
+        }
+        if (flags.get("warnIfNone")!=null && 
!Boolean.FALSE.equals(flags.get("warnIfNone"))) {
+            if (Boolean.TRUE.equals(flags.get("warnIfNone")))
+                LOG.warn("Unable to find Brooklyn property "+keys);
+            else
+                LOG.warn(""+flags.get("warnIfNone"));
+        }
+        if (flags.get("failIfNone")!=null && 
!Boolean.FALSE.equals(flags.get("failIfNone"))) {
+            Object f = flags.get("failIfNone");
+            if (f instanceof Closure)
+                ((Closure)f).call((Object[])keys);
+            if (Boolean.TRUE.equals(f))
+                throw new NoSuchElementException("Brooklyn unable to find 
mandatory property "+keys[0]+
+                    (keys.length>1 ? " (or "+(keys.length-1)+" other possible 
names, full list is "+Arrays.asList(keys)+")" : "") );
+            else
+                throw new NoSuchElementException(""+f);
+        }
+        if (flags.get("defaultIfNone")!=null) {
+            return (String) flags.get("defaultIfNone");
+        }
+        return null;
+    }
+
+    @Override
+    public String toString() {
+        return "BrooklynProperties["+size()+"]";
+    }
+
+    /** like normal map.put, except config keys are dereferenced on the way in 
*/
+    @SuppressWarnings("unchecked")
+    public Object put(Object key, Object value) {
+        if (key instanceof HasConfigKey) key = 
((HasConfigKey)key).getConfigKey().getName();
+        if (key instanceof ConfigKey) key = ((ConfigKey)key).getName();
+        return super.put(key, value);
+    }
+
+    /** like normal map.putAll, except config keys are dereferenced on the way 
in */
+    @Override
+    public void putAll(Map vals) {
+        for (Map.Entry<?,?> entry : ((Map<?,?>)vals).entrySet()) {
+            put(entry.getKey(), entry.getValue());
+        }
+    }
+    
+    @SuppressWarnings("unchecked")
+    public <T> Object put(HasConfigKey<T> key, T value) {
+        return super.put(key.getConfigKey().getName(), value);
+    }
+
+    @SuppressWarnings("unchecked")
+    public <T> Object put(ConfigKey<T> key, T value) {
+        return super.put(key.getName(), value);
+    }
+    
+    public <T> boolean putIfAbsent(ConfigKey<T> key, T value) {
+        return putIfAbsent(key.getName(), value);
+    }
+    
+    @Override
+    public <T> T getConfig(ConfigKey<T> key) {
+        return getConfig(key, null);
+    }
+
+    @Override
+    public <T> T getConfig(HasConfigKey<T> key) {
+        return getConfig(key.getConfigKey(), null);
+    }
+
+    @Override
+    public <T> T getConfig(HasConfigKey<T> key, T defaultValue) {
+        return getConfig(key.getConfigKey(), defaultValue);
+    }
+
+    @Override
+    public <T> T getConfig(ConfigKey<T> key, T defaultValue) {
+        // TODO does not support MapConfigKey etc where entries use subkey 
notation; for now, access using submap
+        if (!containsKey(key.getName())) {
+            if (defaultValue!=null) return defaultValue;
+            return key.getDefaultValue();
+        }
+        Object value = get(key.getName());
+        if (value==null) return null;
+        // no evaluation / key extraction here
+        return TypeCoercions.coerce(value, key.getTypeToken());
+    }
+
+    @Override
+    public Object getRawConfig(ConfigKey<?> key) {
+        return get(key.getName());
+    }
+    
+    @Override
+    public Maybe<Object> getConfigRaw(ConfigKey<?> key, boolean 
includeInherited) {
+        if (containsKey(key.getName())) return Maybe.of(get(key.getName()));
+        return Maybe.absent();
+    }
+
+    @Override
+    public Map<ConfigKey<?>, Object> getAllConfig() {
+        Map<ConfigKey<?>, Object> result = new LinkedHashMap<ConfigKey<?>, 
Object>();
+        for (Object entry: entrySet())
+            result.put(new BasicConfigKey<Object>(Object.class, 
""+((Map.Entry)entry).getKey()), ((Map.Entry)entry).getValue());
+        return result;
+    }
+
+    @Override
+    public BrooklynPropertiesImpl submap(Predicate<ConfigKey<?>> filter) {
+        BrooklynPropertiesImpl result = Factory.newEmpty();
+        for (Object entry: entrySet()) {
+            ConfigKey<?> k = new BasicConfigKey<Object>(Object.class, 
""+((Map.Entry)entry).getKey());
+            if (filter.apply(k))
+                result.put(((Map.Entry)entry).getKey(), 
((Map.Entry)entry).getValue());
+        }
+        return result;
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public Map<String, Object> asMapWithStringKeys() {
+        return this;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/8a3c17b9/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/mgmt/internal/CampYamlParser.java
----------------------------------------------------------------------
diff --git 
a/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/mgmt/internal/CampYamlParser.java
 
b/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/mgmt/internal/CampYamlParser.java
new file mode 100644
index 0000000..35841be
--- /dev/null
+++ 
b/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/mgmt/internal/CampYamlParser.java
@@ -0,0 +1,34 @@
+/*
+ * 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.internal;
+
+import java.util.Map;
+
+import org.apache.brooklyn.config.ConfigKey;
+import org.apache.brooklyn.core.config.ConfigKeys;
+
+public interface CampYamlParser {
+
+    ConfigKey<CampYamlParser> YAML_PARSER_KEY = 
ConfigKeys.newConfigKey(CampYamlParser.class, "brooklyn.camp.yamlParser");
+
+    Map<String, Object> parse(Map<String, Object> map);
+    
+    Object parse(String val);
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/8a3c17b9/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/mgmt/internal/DeferredBrooklynProperties.java
----------------------------------------------------------------------
diff --git 
a/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/mgmt/internal/DeferredBrooklynProperties.java
 
b/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/mgmt/internal/DeferredBrooklynProperties.java
new file mode 100644
index 0000000..ae0c7a5
--- /dev/null
+++ 
b/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/mgmt/internal/DeferredBrooklynProperties.java
@@ -0,0 +1,370 @@
+/*
+ * 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.internal;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.io.File;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+
+import org.apache.brooklyn.api.mgmt.ExecutionContext;
+import org.apache.brooklyn.config.ConfigKey;
+import org.apache.brooklyn.config.ConfigKey.HasConfigKey;
+import org.apache.brooklyn.core.internal.BrooklynProperties;
+import org.apache.brooklyn.util.core.config.ConfigBag;
+import org.apache.brooklyn.util.core.flags.TypeCoercions;
+import org.apache.brooklyn.util.core.task.DeferredSupplier;
+import org.apache.brooklyn.util.core.task.Tasks;
+import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.apache.brooklyn.util.guava.Maybe;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Predicate;
+import com.google.common.collect.Maps;
+
+/**
+ * Delegates to another {@link BrooklynProperties} implementation, but 
intercepts all calls to get.
+ * The results are transformed: if they are in the external-config format then 
they are 
+ * automatically converted to {@link DeferredSupplier}.
+ * 
+ * The external-config format is that same as that for camp-yaml blueprints 
(i.e. 
+ * {@code $brooklyn:external("myprovider", "mykey")}.
+ */
+public class DeferredBrooklynProperties implements BrooklynProperties {
+
+    private static final Logger LOG = 
LoggerFactory.getLogger(DeferredBrooklynProperties.class);
+
+    private static final String BROOKLYN_YAML_PREFIX = "$brooklyn:";
+    
+    private final BrooklynProperties delegate;
+    private final ManagementContextInternal mgmt;
+
+    public DeferredBrooklynProperties(BrooklynProperties delegate, 
ManagementContextInternal mgmt) {
+        this.delegate = checkNotNull(delegate, "delegate");
+        this.mgmt = checkNotNull(mgmt, "mgmt");
+    }
+    
+    private Object transform(ConfigKey<?> key, Object value) {
+        if (value instanceof CharSequence) {
+            String raw = value.toString();
+            if (raw.startsWith(BROOKLYN_YAML_PREFIX)) {
+                CampYamlParser parser = 
mgmt.getConfig().getConfig(CampYamlParser.YAML_PARSER_KEY);
+                if (parser == null) {
+                    // TODO Should we fail or return the untransformed value?
+                    // Problem is this gets called during initialisation, e.g. 
by BrooklynFeatureEnablement calling asMapWithStringKeys()
+                    // throw new IllegalStateException("Cannot parse 
external-config for "+key+" because no camp-yaml parser available");
+                    LOG.debug("Not transforming external-config {}, as no 
camp-yaml parser available", key);
+                    return value;
+                }
+                return parser.parse(raw);
+            }
+        }
+        return value;
+    }
+    
+    private <T> T resolve(ConfigKey<T> key, Object value) {
+        Object transformed = transform(key, value);
+
+        Object result;
+        if (transformed instanceof DeferredSupplier) {
+            ExecutionContext exec = mgmt.getServerExecutionContext();
+            try {
+                result = Tasks.resolveValue(transformed, key.getType(), exec);
+            } catch (ExecutionException | InterruptedException e) {
+                throw Exceptions.propagate(e);
+            }
+        } else {
+            result = transformed;
+        }
+
+        return TypeCoercions.coerce(result, key.getTypeToken());
+    }
+    
+    @Override
+    public <T> T getConfig(ConfigKey<T> key) {
+        T raw = delegate.getConfig(key);
+        return resolve(key, raw);
+    }
+
+    @Override
+    public <T> T getConfig(HasConfigKey<T> key) {
+        T raw = delegate.getConfig(key);
+        return resolve(key.getConfigKey(), raw);
+    }
+
+    @Override
+    public <T> T getConfig(HasConfigKey<T> key, T defaultValue) {
+        T raw = delegate.getConfig(key, defaultValue);
+        return resolve(key.getConfigKey(), raw);
+    }
+
+    @Override
+    public <T> T getConfig(ConfigKey<T> key, T defaultValue) {
+        T raw = delegate.getConfig(key, defaultValue);
+        return resolve(key, raw);
+    }
+
+    @Deprecated
+    @Override
+    public Object getRawConfig(ConfigKey<?> key) {
+        return transform(key, delegate.getRawConfig(key));
+    }
+    
+    @Override
+    public Maybe<Object> getConfigRaw(ConfigKey<?> key, boolean 
includeInherited) {
+        Maybe<Object> result = delegate.getConfigRaw(key, includeInherited);
+        return (result.isPresent()) ? Maybe.of(transform(key, result.get())) : 
Maybe.absent();
+    }
+
+    @Override
+    public Map<ConfigKey<?>, Object> getAllConfig() {
+        Map<ConfigKey<?>, Object> raw = delegate.getAllConfig();
+        Map<ConfigKey<?>, Object> result = Maps.newLinkedHashMap();
+        for (Map.Entry<ConfigKey<?>, Object> entry : raw.entrySet()) {
+            result.put(entry.getKey(), transform(entry.getKey(), 
entry.getValue()));
+        }
+        return result;
+    }
+
+    @Override
+    public Map<String, Object> asMapWithStringKeys() {
+        Map<ConfigKey<?>, Object> raw = delegate.getAllConfig();
+        Map<String, Object> result = Maps.newLinkedHashMap();
+        for (Map.Entry<ConfigKey<?>, Object> entry : raw.entrySet()) {
+            result.put(entry.getKey().getName(), transform(entry.getKey(), 
entry.getValue()));
+        }
+        return result;
+    }
+
+    /**
+     * Discouraged; returns the String so if it is external config, it will be 
the 
+     * {@code $brooklyn:external(...)} format.
+     */
+    @Override
+    @SuppressWarnings("rawtypes")
+    @Deprecated
+    public String get(Map flags, String key) {
+        return delegate.get(flags, key);
+    }
+
+    /**
+     * Discouraged; returns the String so if it is external config, it will be 
the 
+     * {@code $brooklyn:external(...)} format.
+     */
+    @Override
+    public String getFirst(String ...keys) {
+        return delegate.getFirst(keys);
+    }
+    
+    /**
+     * Discouraged; returns the String so if it is external config, it will be 
the 
+     * {@code $brooklyn:external(...)} format.
+     */
+    @Override
+    @SuppressWarnings("rawtypes")
+    public String getFirst(Map flags, String ...keys) {
+        return delegate.getFirst(flags, keys);
+    }
+
+    @Override
+    public BrooklynProperties submap(Predicate<ConfigKey<?>> filter) {
+        BrooklynProperties submap = delegate.submap(filter);
+        return new DeferredBrooklynProperties(submap, mgmt);
+    }
+
+    @Override
+    public BrooklynProperties addEnvironmentVars() {
+        delegate.addEnvironmentVars();
+        return this;
+    }
+
+    @Override
+    public BrooklynProperties addSystemProperties() {
+        delegate.addSystemProperties();
+        return this;
+    }
+
+    @Override
+    public BrooklynProperties addFrom(ConfigBag cfg) {
+        delegate.addFrom(cfg);
+        return this;
+    }
+
+    @Override
+    @SuppressWarnings("rawtypes")
+    public BrooklynProperties addFrom(Map map) {
+        delegate.addFrom(map);
+        return this;
+    }
+
+    @Override
+    public BrooklynProperties addFrom(InputStream i) {
+        delegate.addFrom(i);
+        return this;
+    }
+    
+    @Override
+    public BrooklynProperties addFrom(File f) {
+        delegate.addFrom(f);
+        return this;
+    }
+    
+    @Override
+    public BrooklynProperties addFrom(URL u) {
+        delegate.addFrom(u);
+        return this;
+    }
+
+    @Override
+    public BrooklynProperties addFromUrl(String url) {
+        delegate.addFromUrl(url);
+        return this;
+    }
+
+    @Override
+    public BrooklynProperties addFromUrlProperty(String urlProperty) {
+        delegate.addFromUrlProperty(urlProperty);
+        return this;
+    }
+
+    @Override
+    @SuppressWarnings("rawtypes")
+    public BrooklynProperties addFromMap(Map properties) {
+        delegate.addFromMap(properties);
+        return this;
+    }
+
+    @Override
+    public boolean putIfAbsent(String key, Object value) {
+        return delegate.putIfAbsent(key, value);
+    }
+
+    @Override
+    public String toString() {
+        return delegate.toString();
+    }
+
+    @Override
+    public Object put(Object key, Object value) {
+        return delegate.put(key, value);
+    }
+
+    @Override
+    @SuppressWarnings("rawtypes")
+    public void putAll(Map vals) {
+        delegate.putAll(vals);
+    }
+    
+    @Override
+    public <T> Object put(HasConfigKey<T> key, T value) {
+        return delegate.put(key, value);
+    }
+
+    @Override
+    public <T> Object put(ConfigKey<T> key, T value) {
+        return delegate.put(key, value);
+    }
+    
+    @Override
+    public <T> boolean putIfAbsent(ConfigKey<T> key, T value) {
+        return delegate.putIfAbsent(key, value);
+    }
+    
+    
+    
//////////////////////////////////////////////////////////////////////////////////
+    // Methods below from java.util.LinkedHashMap, which BrooklynProperties 
extends //
+    
//////////////////////////////////////////////////////////////////////////////////
+    
+    @Override
+    public int size() {
+        return delegate.size();
+    }
+
+    @Override
+    public boolean isEmpty() {
+        return delegate.isEmpty();
+    }
+
+    @Override
+    public boolean containsKey(Object key) {
+        return delegate.containsKey(key);
+    }
+
+    @Override
+    public boolean containsValue(Object value) {
+        return delegate.containsValue(value);
+    }
+
+    @Override
+    public Object get(Object key) {
+        return delegate.get(key);
+    }
+
+    @Override
+    public Object remove(Object key) {
+        return delegate.remove(key);
+    }
+
+    @Override
+    public void clear() {
+        delegate.clear();
+    }
+
+    @Override
+    @SuppressWarnings("rawtypes")
+    public Set keySet() {
+        return delegate.keySet();
+    }
+
+    @Override
+    @SuppressWarnings("rawtypes")
+    public Collection values() {
+        return delegate.values();
+    }
+    
+    @Override
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    public Set<Map.Entry> entrySet() {
+        return delegate.entrySet();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        return delegate.equals(o);
+    }
+
+    @Override
+    public int hashCode() {
+        return delegate.hashCode();
+    }
+    
+    // put(Object, Object) already overridden
+    //@Override
+    //public Object put(Object key, Object value) {
+
+    // putAll(Map) already overridden
+    //@Override
+    //public void putAll(Map m) {
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/8a3c17b9/brooklyn-server/utils/common/src/main/java/org/apache/brooklyn/util/text/VersionComparator.java
----------------------------------------------------------------------
diff --git 
a/brooklyn-server/utils/common/src/main/java/org/apache/brooklyn/util/text/VersionComparator.java
 
b/brooklyn-server/utils/common/src/main/java/org/apache/brooklyn/util/text/VersionComparator.java
new file mode 100644
index 0000000..94553b0
--- /dev/null
+++ 
b/brooklyn-server/utils/common/src/main/java/org/apache/brooklyn/util/text/VersionComparator.java
@@ -0,0 +1,199 @@
+/*
+ * 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.util.text;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Comparator;
+
+import org.apache.brooklyn.util.text.NaturalOrderComparator;
+import org.apache.brooklyn.util.text.Strings;
+
+import com.google.common.annotations.VisibleForTesting;
+
+/**
+ * {@link Comparator} for version strings.
+ * <p>
+ * SNAPSHOT items always lowest rated, 
+ * then splitting on dots,
+ * using natural order comparator (so "9" < "10" and "4u8" < "4u20"),
+ * and preferring segments without qualifiers ("4" > "4beta").
+ * <p>
+ * Impossible to follow semantics for all versioning schemes but 
+ * does the obvious right thing for normal schemes
+ * and pretty well in fringe cases.
+ * <p>
+ * See test case for lots of examples.
+ */
+public class VersionComparator implements Comparator<String> {
+    
+    private static final String SNAPSHOT = "SNAPSHOT";
+
+    public static final VersionComparator INSTANCE = new VersionComparator();
+
+    public static VersionComparator getInstance() {
+        return INSTANCE;
+    }
+
+    @Override
+    public int compare(String v1, String v2) {
+        if (v1==null && v2==null) return 0;
+        if (v1==null) return -1;
+        if (v2==null) return 1;
+        
+        boolean isV1Snapshot = v1.toUpperCase().contains(SNAPSHOT);
+        boolean isV2Snapshot = v2.toUpperCase().contains(SNAPSHOT);
+        if (isV1Snapshot == isV2Snapshot) {
+            // if snapshot status is the same, look at dot-split parts first
+            return compareDotSplitParts(splitOnDot(v1), splitOnDot(v2));
+        } else {
+            // snapshot goes first
+            return isV1Snapshot ? -1 : 1;
+        }
+    }
+
+    @VisibleForTesting
+    static String[] splitOnDot(String v) {
+        return v.split("(?<=\\.)|(?=\\.)");
+    }
+    
+    private int compareDotSplitParts(String[] v1Parts, String[] v2Parts) {
+        for (int i = 0; ; i++) {
+            if (i >= v1Parts.length && i >= v2Parts.length) {
+                // end of both
+                return 0;
+            }
+            if (i == v1Parts.length) {
+                // sequence depends whether the extra part *starts with* a 
number
+                // ie
+                //                   2.0 < 2.0.0
+                // and
+                //   2.0.qualifier < 2.0 < 2.0.0qualifier < 2.0.0-qualifier < 
2.0.0.qualifier < 2.0.0 < 2.0.9-qualifier
+                return isNumberInFirstCharPossiblyAfterADot(v2Parts, i) ? -1 : 
1;
+            }
+            if (i == v2Parts.length) {
+                // as above but inverted
+                return isNumberInFirstCharPossiblyAfterADot(v1Parts, i) ? 1 : 
-1;
+            }
+            // not at end; compare this dot split part
+            
+            int result = compareDotSplitPart(v1Parts[i], v2Parts[i]);
+            if (result!=0) return result;
+        }
+    }
+    
+    private int compareDotSplitPart(String v1, String v2) {
+        String[] v1Parts = splitOnNonWordChar(v1);
+        String[] v2Parts = splitOnNonWordChar(v2);
+        
+        for (int i = 0; ; i++) {
+            if (i >= v1Parts.length && i >= v2Parts.length) {
+                // end of both
+                return 0;
+            }
+            if (i == v1Parts.length) {
+                // shorter set always wins here; i.e.
+                // 1-qualifier < 1
+                return 1;
+            }
+            if (i == v2Parts.length) {
+                // as above but inverted
+                return -1;
+            }
+            // not at end; compare this dot split part
+            
+            String v1p = v1Parts[i];
+            String v2p = v2Parts[i];
+            
+            if (v1p.equals(v2p)) continue;
+            
+            if (isNumberInFirstChar(v1p) || isNumberInFirstChar(v2p)) {
+                // something starting with a number is higher than something 
not
+                if (!isNumberInFirstChar(v1p)) return -1;
+                if (!isNumberInFirstChar(v2p)) return 1;
+                
+                // both start with numbers; can use natural order comparison 
*unless*
+                // one is purely a number AND the other *begins* with that 
number,
+                // followed by non-digit chars, in which case prefer the pure 
number
+                // ie:
+                //           1beta < 1
+                // but note
+                //            1 < 2beta < 11beta
+                if (isNumber(v1p) || isNumber(v2p)) {
+                    if (!isNumber(v1p)) {
+                        if (v1p.startsWith(v2p)) {
+                            if 
(!isNumberInFirstChar(Strings.removeFromStart(v1p, v2p))) {
+                                // v2 is a number, and v1 is the same followed 
by non-numbers
+                                return -1;
+                            }
+                        }
+                    }
+                    if (!isNumber(v2p)) {
+                        // as above but inverted
+                        if (v2p.startsWith(v1p)) {
+                            if 
(!isNumberInFirstChar(Strings.removeFromStart(v2p, v1p))) {
+                                return 1;
+                            }
+                        }
+                    }
+                    // both numbers, skip to natural order comparison
+                }
+            }
+            
+            // otherwise it is in-order
+            int result = NaturalOrderComparator.INSTANCE.compare(v1p, v2p);
+            if (result!=0) return result;
+        }
+    }
+
+    @VisibleForTesting
+    static String[] splitOnNonWordChar(String v) {
+        Collection<String> parts = new ArrayList<String>();
+        String remaining = v;
+        
+        // use lookahead to split on all non-letter non-numbers, putting them 
into their own buckets 
+        
parts.addAll(Arrays.asList(remaining.split("(?<=[^0-9\\p{L}])|(?=[^0-9\\p{L}])")));
+        return parts.toArray(new String[parts.size()]);
+    }
+
+    @VisibleForTesting
+    static boolean isNumberInFirstCharPossiblyAfterADot(String[] parts, int i) 
{
+        if (parts==null || parts.length<=i) return false;
+        if (isNumberInFirstChar(parts[i])) return true;
+        if (".".equals(parts[i])) {
+            if (parts.length>i+1)
+                if (isNumberInFirstChar(parts[i+1])) 
+                    return true;
+        }
+        return false;
+    }
+
+    @VisibleForTesting
+    static boolean isNumberInFirstChar(String v) {
+        if (v==null || v.length()==0) return false;
+        return Character.isDigit(v.charAt(0));
+    }
+    
+    @VisibleForTesting
+    static boolean isNumber(String v) {
+        if (v==null || v.length()==0) return false;
+        return v.matches("[\\d]+");
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/8a3c17b9/brooklyn-server/utils/common/src/test/java/org/apache/brooklyn/util/text/VersionComparatorTest.java
----------------------------------------------------------------------
diff --git 
a/brooklyn-server/utils/common/src/test/java/org/apache/brooklyn/util/text/VersionComparatorTest.java
 
b/brooklyn-server/utils/common/src/test/java/org/apache/brooklyn/util/text/VersionComparatorTest.java
new file mode 100644
index 0000000..00fdb6e
--- /dev/null
+++ 
b/brooklyn-server/utils/common/src/test/java/org/apache/brooklyn/util/text/VersionComparatorTest.java
@@ -0,0 +1,102 @@
+/*
+ * 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.util.text;
+
+import static org.testng.Assert.assertEquals;
+
+import java.util.List;
+
+import org.apache.brooklyn.util.collections.MutableList;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+public class VersionComparatorTest {
+
+    @Test
+    public void testStaticHelpers() {
+        Assert.assertEquals(VersionComparator.splitOnDot("a.b.cc"), new 
String[] { "a", ".", "b", ".", "cc" });
+        Assert.assertEquals(VersionComparator.splitOnDot("a..b-c"), new 
String[] { "a", ".", ".", "b-c" });
+
+        
Assert.assertEquals(VersionComparator.splitOnNonWordChar("a1-b__cc9c"), new 
String[] { 
+            "a1", "-", "b", "_", "_", "cc9c" });
+
+        Assert.assertEquals(VersionComparator.isNumberInFirstChar("1a"), true);
+        Assert.assertEquals(VersionComparator.isNumberInFirstChar("a1"), 
false);
+        Assert.assertEquals(VersionComparator.isNumberInFirstChar(""), false);
+        Assert.assertEquals(VersionComparator.isNumberInFirstChar(null), 
false);
+        
+        Assert.assertEquals(VersionComparator.isNumber("1"), true);
+        Assert.assertEquals(VersionComparator.isNumber("1111"), true);
+        Assert.assertEquals(VersionComparator.isNumber("1a"), false);
+        Assert.assertEquals(VersionComparator.isNumber("a1"), false);
+        Assert.assertEquals(VersionComparator.isNumber(""), false);
+        Assert.assertEquals(VersionComparator.isNumber(null), false);
+    }
+    
+    @Test
+    public void testComparison() {
+        VersionComparator.INSTANCE.compare("B", "B-2");
+        
+        assertVersionOrder("0", "1");
+        assertVersionOrder("0", "0.0", "0.9", "0.10", "0.10.0", "1");
+        
+        assertVersionOrder("a", "b");
+        
+        assertVersionOrder("1beta", "1", "2beta", "11beta");
+        assertVersionOrder("beta", "0", "1beta", "1-alpha", "1", "11beta", 
"11-alpha", "11");
+        assertVersionOrder("1.0-a", "1.0-b", "1.0");
+        
+        assertVersionOrder("qualifier", "0qualifier", "0-qualifier", "0", 
"1-qualifier", "1");
+
+        assertVersionOrder("2.0.qualifier", "2.0", "2.0.0qualifier", 
"2.0.0-qualifier", "2.0.0.qualifier", "2.0.0");
+        assertVersionOrder("2.0.qualifier.0", "2.0", "2.0.0qualifier.0", 
"2.0.0-qualifier.0", "2.0.0.qualifier.0", "2.0.0", "2.0.0.0");
+        
+        assertVersionOrder("0", "0.0", "0.1", "0.1.0", "0.1.1", "0.2", 
"0.2.1", "1", "1.0", "2");
+        // case sensitive
+        assertVersionOrder("AA", "Aa", "aa");
+        // letters in order, ignoring case, and using natural order on 
numbers, splitting at word boundaries
+        assertVersionOrder("A", "B-2", "B-10", "B", "B0", "C", "b", "b1", 
"b9", "b10", "c", "0");
+        // and non-letter symbols are compared, in alpha order (e.g. - less 
than _) with dots even higher
+        assertVersionOrder("0-qual", "0", "0.1", "1-qualC", "1_qualB", 
"1.qualA", "1", "1.0");
+        
+        // numeric comparison works with qualifiers, preferring unqualified
+        assertVersionOrder("0--qual", "0-qual", "0-qualB", "0-qualB2", 
"0-qualB10", "0-qualC", "0.qualA", "0", "0.1.qual", "0.1", "1");
+        
+        // all snapshots rated lower
+        assertVersionOrder(
+            "0_SNAPSHOT", "0.1.SNAPSHOT", "1-SNAPSHOT-X-X", "1-SNAPSHOT-X", 
"1-SNAPSHOT-XX-X", "1-SNAPSHOT-XX", "1-SNAPSHOT", 
+            "1.0-SNAPSHOT-B", "1.0.SNAPSHOT-A", 
+            "1.2-SNAPSHOT", "1.10-SNAPSHOT",
+            "qualifer",
+            "0", "0.1", "1");
+    }
+    
+    private static void assertVersionOrder(String v1, String v2, String 
...otherVersions) {
+        List<String> versions = MutableList.<String>of().append(v1, v2, 
otherVersions);
+        
+        for (int i=0; i<versions.size(); i++) {
+            for (int j=0; j<versions.size(); j++) {
+                assertEquals(VersionComparator.getInstance().compare(
+                        versions.get(i), versions.get(j)),
+                    new Integer(i).compareTo(j), "comparing 
"+versions.get(i)+" and "+versions.get(j));
+            }
+        }
+    }
+
+}

Reply via email to