You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by al...@apache.org on 2015/12/17 13:54:23 UTC

[2/5] incubator-brooklyn git commit: Create BrooklynProperties interface

Create BrooklynProperties interface

Renames BrooklynProperties to BrooklynPropertiesImpl, and turns
BrooklynProperties into an interface.

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

Branch: refs/heads/master
Commit: ac8009e4e5bf34ff805a3a41807d418a446f727a
Parents: 6a52bbc
Author: Aled Sage <al...@gmail.com>
Authored: Wed Dec 16 20:12:06 2015 +0000
Committer: Aled Sage <al...@gmail.com>
Committed: Wed Dec 16 20:12:06 2015 +0000

----------------------------------------------------------------------
 .../core/internal/BrooklynProperties.java       | 260 ++--------
 .../core/internal/BrooklynPropertiesImpl.java   | 477 +++++++++++++++++++
 2 files changed, 519 insertions(+), 218 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/ac8009e4/core/src/main/java/org/apache/brooklyn/core/internal/BrooklynProperties.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/internal/BrooklynProperties.java b/core/src/main/java/org/apache/brooklyn/core/internal/BrooklynProperties.java
index c1ffb42..75b7b34 100644
--- a/core/src/main/java/org/apache/brooklyn/core/internal/BrooklynProperties.java
+++ b/core/src/main/java/org/apache/brooklyn/core/internal/BrooklynProperties.java
@@ -19,59 +19,44 @@
 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.config.StringConfigMap;
-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;
 
-/** utils for accessing command-line and system-env properties;
+/** 
+ * Utils for accessing command-line and system-env properties;
  * doesn't resolve anything (unless an execution context is supplied)
  * and treats ConfigKeys as of type object when in doubt,
  * or string when that is likely wanted (e.g. {@link #getFirst(Map, String...)}
  * <p>
- * TODO methods in this class are not thread safe.
- * intention is that they are set during startup and not modified thereafter. */
+ * Intention for normal use is that they are set during startup and not modified 
+ * thereafter.
+ */
 @SuppressWarnings("rawtypes")
-public class BrooklynProperties extends LinkedHashMap implements StringConfigMap {
-
-    private static final long serialVersionUID = -945875483083108978L;
-    private static final Logger LOG = LoggerFactory.getLogger(BrooklynProperties.class);
+public interface BrooklynProperties extends Map, StringConfigMap {
 
     public static class Factory {
+        private static final Logger LOG = LoggerFactory.getLogger(BrooklynProperties.Factory.class);
+        
         /** creates a new empty {@link BrooklynProperties} */
         public static BrooklynProperties newEmpty() {
-            return new BrooklynProperties();
+            return new BrooklynPropertiesImpl();
         }
 
         /** creates a new {@link BrooklynProperties} with contents loaded 
@@ -122,7 +107,7 @@ public class BrooklynProperties extends LinkedHashMap implements StringConfigMap
              * Creates a Builder that when built, will return the BrooklynProperties passed to this constructor
              */
             private Builder(BrooklynProperties originalProperties) {
-                this.originalProperties = new BrooklynProperties().addFromMap(originalProperties);
+                this.originalProperties = new BrooklynPropertiesImpl().addFromMap(originalProperties);
             }
             
             /**
@@ -167,9 +152,9 @@ public class BrooklynProperties extends LinkedHashMap implements StringConfigMap
             
             public BrooklynProperties build() {
                 if (originalProperties != null) 
-                    return new BrooklynProperties().addFromMap(originalProperties);
+                    return new BrooklynPropertiesImpl().addFromMap(originalProperties);
                 
-                BrooklynProperties properties = new BrooklynProperties();
+                BrooklynProperties 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?
@@ -226,73 +211,20 @@ public class BrooklynProperties extends LinkedHashMap implements StringConfigMap
         }
     }
 
-    protected BrooklynProperties() {
-    }
+    public BrooklynProperties addEnvironmentVars();
 
-    public BrooklynProperties addEnvironmentVars() {
-        addFrom(System.getenv());
-        return this;
-    }
-
-    public BrooklynProperties addSystemProperties() {
-        addFrom(System.getProperties());
-        return this;
-    }
+    public BrooklynProperties addSystemProperties();
 
-    public BrooklynProperties addFrom(ConfigBag cfg) {
-        addFrom(cfg.getAllConfig());
-        return this;
-    }
+    public BrooklynProperties addFrom(ConfigBag cfg);
 
-    @SuppressWarnings("unchecked")
-    public BrooklynProperties addFrom(Map map) {
-        putAll(Maps.transformValues(map, StringFunctions.trim()));
-        return this;
-    }
+    public BrooklynProperties addFrom(Map map);
 
-    public BrooklynProperties 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 BrooklynProperties.this.put(key, s);
-            }
-        };
-        try {
-            p.load(i);
-        } catch (IOException e) {
-            throw Throwables.propagate(e);
-        }
-        return this;
-    }
+    public BrooklynProperties addFrom(InputStream i);
     
-    public BrooklynProperties 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 BrooklynProperties addFrom(URL u) {
-        try {
-            return addFrom(u.openStream());
-        } catch (IOException e) {
-            throw new RuntimeException("Error reading properties from "+u+": "+e, e);
-        }
-    }
+    public BrooklynProperties addFrom(File f);
+
+    public BrooklynProperties addFrom(URL u);
+
     /**
      * @see ResourceUtils#getResourceFromUrl(String)
      *
@@ -300,48 +232,25 @@ public class BrooklynProperties extends LinkedHashMap implements StringConfigMap
      * 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 BrooklynProperties 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);
-        }
-    }
+    public BrooklynProperties addFromUrl(String url);
 
     /** expects a property already set in scope, whose value is acceptable to {@link #addFromUrl(String)};
      * if property not set, does nothing */
-    public BrooklynProperties addFromUrlProperty(String urlProperty) {
-        String url = (String) get(urlProperty);
-        if (url==null) addFromUrl(url);
-        return this;
-    }
+    public BrooklynProperties addFromUrlProperty(String urlProperty);
 
     /**
     * adds the indicated properties
     */
-    public BrooklynProperties addFromMap(Map properties) {
-        putAll(properties);
-        return this;
-    }
+    public BrooklynProperties addFromMap(Map properties);
 
     /** 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;
-    }
+    public boolean putIfAbsent(String key, Object value);
 
    /** @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);
-   }
+   public String get(Map flags, String key);
 
     /** returns the value of the first key which is defined
      * <p>
@@ -349,133 +258,48 @@ public class BrooklynProperties extends LinkedHashMap implements StringConfigMap
      * '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;
-    }
+    public String getFirst(String ...keys);
 
     @Override
-    public String toString() {
-        return "BrooklynProperties["+size()+"]";
-    }
+    public String getFirst(Map flags, String ...keys);
 
     /** 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);
-    }
+    public Object put(Object key, Object 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());
-        }
-    }
+    public void putAll(Map vals);
     
-    @SuppressWarnings("unchecked")
-    public <T> Object put(HasConfigKey<T> key, T value) {
-        return super.put(key.getConfigKey().getName(), value);
-    }
+    public <T> Object put(HasConfigKey<T> key, T value);
 
-    @SuppressWarnings("unchecked")
-    public <T> Object put(ConfigKey<T> key, T value) {
-        return super.put(key.getName(), value);
-    }
+    public <T> Object put(ConfigKey<T> key, T value);
     
-    public <T> boolean putIfAbsent(ConfigKey<T> key, T value) {
-        return putIfAbsent(key.getName(), value);
-    }
+    public <T> boolean putIfAbsent(ConfigKey<T> key, T value);
     
     @Override
-    public <T> T getConfig(ConfigKey<T> key) {
-        return getConfig(key, null);
-    }
+    public <T> T getConfig(ConfigKey<T> key);
 
     @Override
-    public <T> T getConfig(HasConfigKey<T> key) {
-        return getConfig(key.getConfigKey(), null);
-    }
+    public <T> T getConfig(HasConfigKey<T> key);
 
     @Override
-    public <T> T getConfig(HasConfigKey<T> key, T defaultValue) {
-        return getConfig(key.getConfigKey(), defaultValue);
-    }
+    public <T> T getConfig(HasConfigKey<T> key, T 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());
-    }
+    public <T> T getConfig(ConfigKey<T> key, T defaultValue);
 
     @Override
-    public Object getRawConfig(ConfigKey<?> key) {
-        return get(key.getName());
-    }
+    public Object getRawConfig(ConfigKey<?> key);
     
     @Override
-    public Maybe<Object> getConfigRaw(ConfigKey<?> key, boolean includeInherited) {
-        if (containsKey(key.getName())) return Maybe.of(get(key.getName()));
-        return Maybe.absent();
-    }
+    public Maybe<Object> getConfigRaw(ConfigKey<?> key, boolean includeInherited);
 
     @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;
-    }
+    public Map<ConfigKey<?>, Object> getAllConfig();
 
     @Override
-    public BrooklynProperties submap(Predicate<ConfigKey<?>> filter) {
-        BrooklynProperties 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;
-    }
+    public BrooklynProperties submap(Predicate<ConfigKey<?>> filter);
 
-    @SuppressWarnings("unchecked")
     @Override
-    public Map<String, Object> asMapWithStringKeys() {
-        return this;
-    }
-
+    public Map<String, Object> asMapWithStringKeys();
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/ac8009e4/core/src/main/java/org/apache/brooklyn/core/internal/BrooklynPropertiesImpl.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/internal/BrooklynPropertiesImpl.java b/core/src/main/java/org/apache/brooklyn/core/internal/BrooklynPropertiesImpl.java
new file mode 100644
index 0000000..023b3e3
--- /dev/null
+++ b/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;
+    }
+
+}