Merge pull request #995 from aledsage/feature/EnricherSpec

Adds EnricherSpec, and adds config to Enricher
diff --git a/api/src/main/java/brooklyn/location/LocationRegistry.java b/api/src/main/java/brooklyn/location/LocationRegistry.java
index 43cb944..5b1d616 100644
--- a/api/src/main/java/brooklyn/location/LocationRegistry.java
+++ b/api/src/main/java/brooklyn/location/LocationRegistry.java
@@ -12,6 +12,8 @@
 @SuppressWarnings("rawtypes")
 public interface LocationRegistry {
 
+    /** map of ID (possibly randomly generated) to the definition (spec, name, id, and props; 
+     * where spec is the spec as defined, for instance possibly another named:xxx location) */
     public Map<String,LocationDefinition> getDefinedLocations();
     
     /**
diff --git a/core/src/main/java/brooklyn/catalog/internal/CatalogDtoUtils.java b/core/src/main/java/brooklyn/catalog/internal/CatalogDtoUtils.java
index ff829d3..86a499b 100644
--- a/core/src/main/java/brooklyn/catalog/internal/CatalogDtoUtils.java
+++ b/core/src/main/java/brooklyn/catalog/internal/CatalogDtoUtils.java
@@ -40,7 +40,7 @@
     public static CatalogDto newDtoFromUrl(String url) {
         if (log.isDebugEnabled()) log.debug("Retrieving catalog from: {}", url);
         try {
-            InputStream source = new ResourceUtils(null).getResourceFromUrl(url);
+            InputStream source = ResourceUtils.create().getResourceFromUrl(url);
             CatalogDto result = (CatalogDto) new CatalogXmlSerializer().deserialize(new InputStreamReader(source));
             if (log.isDebugEnabled()) log.debug("Retrieved catalog from: {}", url);
             return result;
diff --git a/core/src/main/java/brooklyn/config/BrooklynProperties.java b/core/src/main/java/brooklyn/config/BrooklynProperties.java
index 36ad3c6..9feb0be 100644
--- a/core/src/main/java/brooklyn/config/BrooklynProperties.java
+++ b/core/src/main/java/brooklyn/config/BrooklynProperties.java
@@ -110,7 +110,7 @@
         
         private static void addPropertiesFromUrl(BrooklynProperties p, String url, boolean warnIfNotFound) {
             try {
-                p.addFrom(new ResourceUtils(BrooklynProperties.class).getResourceFromUrl(url));
+                p.addFrom(ResourceUtils.create(BrooklynProperties.class).getResourceFromUrl(url));
             } catch (Exception e) {
                 if (warnIfNotFound)
                     LOG.warn("Could not load {}; continuing", url);
@@ -189,7 +189,7 @@
     public BrooklynProperties addFromUrl(String url) {
         try {
             if (url==null) return this;
-            return addFrom(new ResourceUtils(this).getResourceFromUrl(url));
+            return addFrom(ResourceUtils.create(this).getResourceFromUrl(url));
         } catch (Exception e) {
             throw new RuntimeException("Error reading properties from "+url+": "+e, e);
         }
diff --git a/core/src/main/java/brooklyn/entity/basic/Entities.java b/core/src/main/java/brooklyn/entity/basic/Entities.java
index 36fe599..dee6296 100644
--- a/core/src/main/java/brooklyn/entity/basic/Entities.java
+++ b/core/src/main/java/brooklyn/entity/basic/Entities.java
@@ -55,6 +55,7 @@
 import brooklyn.util.task.ParallelTask;
 import brooklyn.util.task.Tasks;
 
+import com.google.common.base.Preconditions;
 import com.google.common.base.Supplier;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
@@ -722,9 +723,8 @@
     /** fails-fast if value of the given key is null or unresolveable */
     public static String getRequiredUrlConfig(Entity entity, ConfigKey<String> urlKey) {
         String url = entity.getConfig(urlKey);
-        if (url==null)
-            throw new NullPointerException("Key "+urlKey+" on "+entity+" should not be null");
-        return new ResourceUtils(entity).checkUrlExists(url);
+        Preconditions.checkNotNull(url, "Key %s on %s should not be null", urlKey, entity);
+        return ResourceUtils.create(entity).checkUrlExists(url);
     }
     /** as {@link #getRequiredUrlConfig(Entity, ConfigKey)} */
     public static String getRequiredUrlConfig(Entity entity, HasConfigKey<String> urlKey) {
diff --git a/core/src/main/java/brooklyn/event/feed/function/FunctionFeed.java b/core/src/main/java/brooklyn/event/feed/function/FunctionFeed.java
index 05cb785..d4c3f7f 100644
--- a/core/src/main/java/brooklyn/event/feed/function/FunctionFeed.java
+++ b/core/src/main/java/brooklyn/event/feed/function/FunctionFeed.java
@@ -15,6 +15,7 @@
 import brooklyn.event.feed.AttributePollHandler;
 import brooklyn.event.feed.DelegatingPollHandler;
 import brooklyn.event.feed.Poller;
+import brooklyn.util.time.Duration;
 
 import com.google.common.base.Objects;
 import com.google.common.collect.HashMultimap;
@@ -76,6 +77,9 @@
             this.entity = val;
             return this;
         }
+        public Builder period(Duration d) {
+            return period(d.toMilliseconds(), TimeUnit.MILLISECONDS);
+        }
         public Builder period(long millis) {
             return period(millis, TimeUnit.MILLISECONDS);
         }
diff --git a/core/src/main/java/brooklyn/location/basic/SshMachineLocation.java b/core/src/main/java/brooklyn/location/basic/SshMachineLocation.java
index e56e962..1643665 100644
--- a/core/src/main/java/brooklyn/location/basic/SshMachineLocation.java
+++ b/core/src/main/java/brooklyn/location/basic/SshMachineLocation.java
@@ -1,8 +1,6 @@
 package brooklyn.location.basic;
 
-import static brooklyn.util.GroovyJavaMethods.truth;
-
-import com.google.common.net.HostAndPort;
+import static brooklyn.util.GroovyJavaMethods.*;
 import groovy.lang.Closure;
 
 import java.io.Closeable;
@@ -42,7 +40,6 @@
 import brooklyn.location.OsDetails;
 import brooklyn.location.PortRange;
 import brooklyn.location.PortSupplier;
-import brooklyn.location.basic.PortRanges.BasicPortRange;
 import brooklyn.location.geo.HasHostGeoInfo;
 import brooklyn.location.geo.HostGeoInfo;
 import brooklyn.util.ResourceUtils;
@@ -80,6 +77,7 @@
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Sets;
+import com.google.common.net.HostAndPort;
 
 /**
  * Operations on a machine that is accessible via ssh.
@@ -626,7 +624,7 @@
 
     /**
      * @see #obtainPort(PortRange)
-     * @see BasicPortRange#ANY_HIGH_PORT
+     * @see PortRanges#ANY_HIGH_PORT
      */
     public boolean obtainSpecificPort(int portNumber) {
         synchronized (usedPorts) {
@@ -731,7 +729,8 @@
     /** returns the un-passphrased key-pair info if a key is being used, or else null */ 
     public KeyPair findKeyPair() {
         String fn = getConfig(SshTool.PROP_PRIVATE_KEY_FILE);
-        if (fn!=null) return SecureKeys.readPem(new ResourceUtils(this).getResourceFromUrl(fn), getConfig(SshTool.PROP_PRIVATE_KEY_PASSPHRASE));
+        ResourceUtils r = ResourceUtils.create(this);
+        if (fn!=null) return SecureKeys.readPem(r.getResourceFromUrl(fn), getConfig(SshTool.PROP_PRIVATE_KEY_PASSPHRASE));
         String data = getConfig(SshTool.PROP_PRIVATE_KEY_DATA);
         if (data!=null) return SecureKeys.readPem(new ReaderInputStream(new StringReader(data)), getConfig(SshTool.PROP_PRIVATE_KEY_PASSPHRASE));
         if (findPassword()!=null)
@@ -739,9 +738,9 @@
             return null;
         // fall back to id_rsa and id_dsa
         if (new File( Urls.mergePaths(System.getProperty("user.home"), ".ssh/id_rsa") ).exists() )
-            return SecureKeys.readPem(new ResourceUtils(this).getResourceFromUrl("~/.ssh/id_rsa"), getConfig(SshTool.PROP_PRIVATE_KEY_PASSPHRASE));
+            return SecureKeys.readPem(r.getResourceFromUrl("~/.ssh/id_rsa"), getConfig(SshTool.PROP_PRIVATE_KEY_PASSPHRASE));
         if (new File( Urls.mergePaths(System.getProperty("user.home"), ".ssh/id_dsa") ).exists() )
-            return SecureKeys.readPem(new ResourceUtils(this).getResourceFromUrl("~/.ssh/id_dsa"), getConfig(SshTool.PROP_PRIVATE_KEY_PASSPHRASE));
+            return SecureKeys.readPem(r.getResourceFromUrl("~/.ssh/id_dsa"), getConfig(SshTool.PROP_PRIVATE_KEY_PASSPHRASE));
         LOG.warn("Unable to extract any key or passphrase data in request to findKeyPair for "+this);
         return null;
     }
diff --git a/core/src/main/java/brooklyn/location/geo/UtraceHostGeoLookup.java b/core/src/main/java/brooklyn/location/geo/UtraceHostGeoLookup.java
index 9ea526a..319d93a 100644
--- a/core/src/main/java/brooklyn/location/geo/UtraceHostGeoLookup.java
+++ b/core/src/main/java/brooklyn/location/geo/UtraceHostGeoLookup.java
@@ -71,7 +71,7 @@
                 public void run() {
                     try {
                         log.debug("Looking up external IP of this host in private thread "+Thread.currentThread());
-                        localExternalIp = new ResourceUtils(HostGeoLookup.class).getResourceAsString("http://api.externalip.net/ip/").trim();
+                        localExternalIp = ResourceUtils.create(HostGeoLookup.class).getResourceAsString("http://api.externalip.net/ip/").trim();
                         log.debug("Finished looking up external IP of this host in private thread, result "+localExternalIp);
                     } catch (Throwable t) {
                         log.debug("Not able to look up external IP of this host in private thread, probably offline ("+t+")");
diff --git a/core/src/main/java/brooklyn/util/ResourceUtils.java b/core/src/main/java/brooklyn/util/ResourceUtils.java
index a4af9c1..9003e1a 100644
--- a/core/src/main/java/brooklyn/util/ResourceUtils.java
+++ b/core/src/main/java/brooklyn/util/ResourceUtils.java
@@ -13,7 +13,6 @@
 import java.util.List;
 import java.util.NoSuchElementException;
 import java.util.Properties;
-import java.util.concurrent.CopyOnWriteArrayList;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -28,32 +27,88 @@
 import com.google.common.base.Function;
 import com.google.common.base.Optional;
 import com.google.common.base.Throwables;
+import com.google.common.collect.Lists;
 import com.google.common.io.ByteStreams;
 import com.google.common.io.Closeables;
 
 public class ResourceUtils {
     
     private static final Logger log = LoggerFactory.getLogger(ResourceUtils.class);
+    private static final List<Function<Object,ClassLoader>> classLoaderProviders = Lists.newCopyOnWriteArrayList();
 
-    ClassLoader loader = null;
-    String context = null;
-    Object contextObject = null;
+    private ClassLoader loader = null;
+    private String context = null;
+    private Object contextObject = null;
     
-    /** context string used for errors */
+    /**
+     * Creates a {@link ResourceUtils} object with a specific class loader and context.
+     * <p>
+     * Use the provided {@link ClassLoader} object for class loading with the
+     * {@code contextObject} for context and the {@code contextMessage} string for
+     * error messages.
+     *
+     * @see ResourceUtils#create(Object, String)
+     * @see ResourceUtils#create(Object)
+     */
+    public static final ResourceUtils create(ClassLoader loader, Object contextObject, String contextMessage) {
+        return new ResourceUtils(loader, contextObject, contextMessage);
+    }
+
+    /**
+     * Creates a {@link ResourceUtils} object with the given context.
+     * <p>
+     * Uses the {@link ClassLoader} of the given {@code contextObject} for class
+     * loading and the {@code contextMessage} string for error messages.
+     *
+     * @see ResourceUtils#create(ClassLoader, Object, String)
+     * @see ResourceUtils#create(Object)
+     */
+    public static final ResourceUtils create(Object contextObject, String contextMessage) {
+        return new ResourceUtils(contextObject, contextMessage);
+    }
+
+    /**
+     * Creates a {@link ResourceUtils} object with the given context.
+     * <p>
+     * Uses the {@link ClassLoader} of the given {@code contextObject} for class
+     * loading and its {@link Object#toString()} (preceded by the word 'for') as
+     * the string used in error messages.
+     *
+     * @see ResourceUtils#create(ClassLoader, Object, String)
+     * @see ResourceUtils#create(Object)
+     */
+    public static final ResourceUtils create(Object contextObject) {
+        return new ResourceUtils(contextObject);
+    }
+
+    /**
+     * Creates a {@link ResourceUtils} object with itself as the context.
+     *
+     * @see ResourceUtils#create(Object)
+     */
+    public static final ResourceUtils create() {
+        return new ResourceUtils(null);
+    }
+
+    /** @deprecated since 0.6.0 use {@link ResourceUtils#create(ClassLoader, Object, String)} */
+    @Deprecated
     public ResourceUtils(ClassLoader loader, Object contextObject, String contextMessage) {
         this.loader = loader;
         this.contextObject = contextObject;
         this.context = contextMessage;
     }
-    /** contextObject used for classloading, contextMessage used for errors */
+
+    /** @deprecated since 0.6.0 use {@link ResourceUtils#create(Object, String)} */
+    @Deprecated
     public ResourceUtils(Object contextObject, String contextMessage) {
-        this(contextObject==null ? null : 
-            getClassLoaderForObject(contextObject), 
-            contextObject, contextMessage);
+        this(contextObject==null ? null : getClassLoaderForObject(contextObject), contextObject, contextMessage);
     }
-    
-    private static List<Function<Object,ClassLoader>> classLoaderProviders =
-        new CopyOnWriteArrayList<Function<Object,ClassLoader>>(); 
+
+    /** @deprecated since 0.6.0 use {@link ResourceUtils#create(Object)} */
+    @Deprecated
+    public ResourceUtils(Object contextObject) {
+        this(contextObject, "for " + Strings.toString(contextObject));
+    }
     
     /** used to register custom mechanisms for getting classloaders given an object */
     public static void addClassLoaderProvider(Function<Object,ClassLoader> provider) {
@@ -70,11 +125,6 @@
                 contextObject.getClass().getClassLoader();
     }
     
-    /** uses the classloader of the given object, and the phrase object's toString (preceded by the word 'for') as the context string used in errors */
-    public ResourceUtils(Object context) {
-        this(context, Strings.toString(context));
-    }
-    
     public ClassLoader getLoader() {
         //TODO allow a sequence of loaders?
         return (loader!=null ? loader : getClass().getClassLoader());
diff --git a/core/src/test/java/brooklyn/location/basic/SshMachineLocationTest.groovy b/core/src/test/java/brooklyn/location/basic/SshMachineLocationTest.groovy
index 96a126e..406b7db 100644
--- a/core/src/test/java/brooklyn/location/basic/SshMachineLocationTest.groovy
+++ b/core/src/test/java/brooklyn/location/basic/SshMachineLocationTest.groovy
@@ -115,7 +115,7 @@
         File dest = new File(System.getProperty("java.io.tmpdir")+"/"+"sssMachineLocationTest_dir/");
         dest.mkdir();
         try {
-            int result = host.installTo(new ResourceUtils(this), "classpath://brooklyn/config/sample.properties", dest.getCanonicalPath()+"/");
+            int result = host.installTo(ResourceUtils.create(this), "classpath://brooklyn/config/sample.properties", dest.getCanonicalPath()+"/");
             assertEquals(result, 0);
             String contents = ResourceUtils.readFullyString(new FileInputStream(new File(dest, "sample.properties")));
             assertTrue(contents.contains("Property 1"), "contents missing expected phrase; contains:\n"+contents);
diff --git a/core/src/test/java/brooklyn/test/HttpService.java b/core/src/test/java/brooklyn/test/HttpService.java
index bd665e5..e19058f 100644
--- a/core/src/test/java/brooklyn/test/HttpService.java
+++ b/core/src/test/java/brooklyn/test/HttpService.java
@@ -97,7 +97,7 @@
                     server.removeConnector(c);
                 }
     
-                InputStream keyStoreStream = new ResourceUtils(this).getResourceFromUrl(SERVER_KEYSTORE);
+                InputStream keyStoreStream = ResourceUtils.create(this).getResourceFromUrl(SERVER_KEYSTORE);
                 KeyStore keyStore;
                 try {
                     keyStore = SecureKeys.newKeyStore(keyStoreStream, "password");
@@ -118,7 +118,7 @@
             addShutdownHook();
     
             File tmpWarFile = ResourceUtils.writeToTempFile(
-                    new ResourceUtils(this).getResourceFromUrl(ROOT_WAR_URL), 
+                    ResourceUtils.create(this).getResourceFromUrl(ROOT_WAR_URL), 
                     "TestHttpService", 
                     ".war");
             
diff --git a/core/src/test/java/brooklyn/util/BrooklynMavenArtifactsTest.java b/core/src/test/java/brooklyn/util/BrooklynMavenArtifactsTest.java
index 4a824e9..cc9e289 100644
--- a/core/src/test/java/brooklyn/util/BrooklynMavenArtifactsTest.java
+++ b/core/src/test/java/brooklyn/util/BrooklynMavenArtifactsTest.java
@@ -17,19 +17,19 @@
     
     @Test(groups="Integration")
     public void testUtilsCommon() {
-        new ResourceUtils(this).checkUrlExists(BrooklynMavenArtifacts.localUrlForJar("brooklyn-utils-common"));
+        ResourceUtils.create(this).checkUrlExists(BrooklynMavenArtifacts.localUrlForJar("brooklyn-utils-common"));
     }
 
     @Test(groups="Integration")
     public void testExampleWar() {
         String url = BrooklynMavenArtifacts.localUrl("example", "brooklyn-example-hello-world-sql-webapp", "war");
-        new ResourceUtils(this).checkUrlExists(url);
+        ResourceUtils.create(this).checkUrlExists(url);
         log.info("found example war at: "+url);
     }
 
     public void testBadExampleWar() {
         String url = BrooklynMavenArtifacts.localUrl("example", "brooklyn-example-GOODBYE-world-sql-webapp", "war");
-        Assert.assertFalse(new ResourceUtils(this).doesUrlExist(url), "should not exist: "+url);
+        Assert.assertFalse(ResourceUtils.create(this).doesUrlExist(url), "should not exist: "+url);
     }
 
     public void testHostedIsHttp() {
@@ -44,7 +44,7 @@
         String snapshot = MavenRetriever.hostedUrl(MavenArtifact.fromCoordinate("io.brooklyn:brooklyn-utils-common:jar:0.6.0-SNAPSHOT"));
         log.info("Sample snapshot URL is: "+snapshot);
         checkValidArchive(snapshot);
-        new ResourceUtils(this).checkUrlExists(snapshot);
+        ResourceUtils.create(this).checkUrlExists(snapshot);
         
         // NB: this should be a version known to be up at sonatype or maven central, NOT necessarily the current version!
         String release = MavenRetriever.hostedUrl(MavenArtifact.fromCoordinate("io.brooklyn:brooklyn-utils-common:jar:0.6.0-M1"));
@@ -54,7 +54,7 @@
 
     private void checkValidArchive(String url) {
         try {
-            byte[] bytes = ResourceUtils.readFullyBytes(new ResourceUtils(this).getResourceFromUrl(url));
+            byte[] bytes = ResourceUtils.readFullyBytes(ResourceUtils.create(this).getResourceFromUrl(url));
             // confirm this follow redirects!
             Assert.assertTrue(bytes.length > 100*1000, "download of "+url+" is suspect ("+Strings.makeSizeString(bytes.length)+")");
             // (could also check it is a zip etc)
diff --git a/core/src/test/java/brooklyn/util/ResourceUtilsTest.java b/core/src/test/java/brooklyn/util/ResourceUtilsTest.java
index e91f019..12fc6a6 100644
--- a/core/src/test/java/brooklyn/util/ResourceUtilsTest.java
+++ b/core/src/test/java/brooklyn/util/ResourceUtilsTest.java
@@ -33,7 +33,7 @@
     
     @BeforeClass(alwaysRun=true)
     public void setUp() throws Exception {
-        utils = new ResourceUtils(this, "mycontext");
+        utils = ResourceUtils.create(this, "mycontext");
         tempFile = ResourceUtils.writeToTempFile(new ByteArrayInputStream(tempFileContents.getBytes()), "resourceutils-test", ".txt");
     }
     
diff --git a/core/src/test/java/brooklyn/util/crypto/SecureKeysAndSignerTest.java b/core/src/test/java/brooklyn/util/crypto/SecureKeysAndSignerTest.java
index 0a4e71a..0902139 100644
--- a/core/src/test/java/brooklyn/util/crypto/SecureKeysAndSignerTest.java
+++ b/core/src/test/java/brooklyn/util/crypto/SecureKeysAndSignerTest.java
@@ -70,25 +70,25 @@
 
     @Test
     public void testReadRsaKey() throws Exception {
-        KeyPair key = SecureKeys.readPem(new ResourceUtils(this).getResourceFromUrl("classpath://brooklyn/util/crypto/sample_rsa.pem"), null);
+        KeyPair key = SecureKeys.readPem(ResourceUtils.create(this).getResourceFromUrl("classpath://brooklyn/util/crypto/sample_rsa.pem"), null);
         checkNonTrivial(key);
     }
 
     @Test
     public void testReadDsaKey() throws Exception {
-        KeyPair key = SecureKeys.readPem(new ResourceUtils(this).getResourceFromUrl("classpath://brooklyn/util/crypto/sample_dsa.pem"), null);
+        KeyPair key = SecureKeys.readPem(ResourceUtils.create(this).getResourceFromUrl("classpath://brooklyn/util/crypto/sample_dsa.pem"), null);
         checkNonTrivial(key);
     }
 
     @Test(expectedExceptions=Exception.class)
     public void testCantReadRsaPassphraseKeyWithoutPassphrase() throws Exception {
-        KeyPair key = SecureKeys.readPem(new ResourceUtils(this).getResourceFromUrl("classpath://brooklyn/util/crypto/sample_rsa_passphrase.pem"), null);
+        KeyPair key = SecureKeys.readPem(ResourceUtils.create(this).getResourceFromUrl("classpath://brooklyn/util/crypto/sample_rsa_passphrase.pem"), null);
         checkNonTrivial(key);
     }
 
     @Test
     public void testReadRsaPassphraseKeyAndWriteWithoutPassphrase() throws Exception {
-        KeyPair key = SecureKeys.readPem(new ResourceUtils(this).getResourceFromUrl("classpath://brooklyn/util/crypto/sample_rsa_passphrase.pem"), "passphrase");
+        KeyPair key = SecureKeys.readPem(ResourceUtils.create(this).getResourceFromUrl("classpath://brooklyn/util/crypto/sample_rsa_passphrase.pem"), "passphrase");
         checkNonTrivial(key);
         File f = File.createTempFile("brooklyn-sample_rsa_passphrase_without_passphrase", "pem");
         f.deleteOnExit();
diff --git a/docs/use/examples/messaging/index.md b/docs/use/examples/messaging/index.md
index 33c78c2..b30a8dd 100644
--- a/docs/use/examples/messaging/index.md
+++ b/docs/use/examples/messaging/index.md
@@ -41,7 +41,7 @@
 no queues or topics:
 
 {% highlight java %}
-public class StandaloneBrokerExample extends AbstractApplication {
+public class StandaloneQpidBrokerExample extends AbstractApplication {
     @Override
     public void init() {
         // Configure the Qpid broker entity
@@ -103,7 +103,7 @@
 as follows:
 
 {% highlight bash %}
-% ${BROOKLYN_HOME}/bin/brooklyn -v launch --app brooklyn.demo.StandaloneBrokerExample --location localhost
+% ${BROOKLYN_HOME}/bin/brooklyn -v launch --app brooklyn.demo.StandaloneQpidBrokerExample --location localhost
 {% endhighlight %}
 
 Now, visit the Brooklyn web console on port 8081 (for pre 0.6 releases,
@@ -175,7 +175,7 @@
 it's Amazon Ireland, as follows:
 
 {% highlight bash %}
-% ${BROOKLYN_HOME}/bin/brooklyn launch --app brooklyn.demo.StandaloneBrokerExample --location aws-ec2:eu-west-1
+% ${BROOKLYN_HOME}/bin/brooklyn launch --app brooklyn.demo.StandaloneQpidBrokerExample --location aws-ec2:eu-west-1
 {% endhighlight %}
 
 If you encounter any difficulties, please
diff --git a/docs/use/guide/quickstart/brooklyn.properties b/docs/use/guide/quickstart/brooklyn.properties
index 1da149c..57e64a0 100644
--- a/docs/use/guide/quickstart/brooklyn.properties
+++ b/docs/use/guide/quickstart/brooklyn.properties
@@ -14,19 +14,24 @@
 
 ############################ Getting Started Options  ####################################
 
+# By default we have AWS and Rackspace (non-UK) set up.
+# For each of those, either set the credentials immediately below
+# or remove the corresponding lines far below (look for  brooklyn.location.named...=...<aws>..  or =...<cloudservers>... )
+
+# For other clouds, ADD corresponding identity and credential lines and enable the setup far below
+
 ## Amazon EC2 Credentials
 
 brooklyn.jclouds.aws-ec2.identity = YOURAPIKEY
 brooklyn.jclouds.aws-ec2.credential = YOURSECRETKEY
 
-## Rackspace First Gen Credentials
+## Rackspace Credentials
 
-brooklyn.jclouds.cloudservers-us.identity = YOURAPIKEY
-brooklyn.jclouds.cloudservers-us.credential = YOURSECRETKEY
-
-# Based in the UK?
-#brooklyn.jclouds.cloudservers-uk.identity = YOURAPIKEY
-#brooklyn.jclouds.cloudservers-uk.credential = YOURSECRETKEY
+brooklyn.jclouds.rackspace-cloudservers-us.identity = YOURAPIKEY
+brooklyn.jclouds.rackspace-cloudservers-us.credential = YOURSECRETKEY
+# For UK you need a *different* account, at rackspace.co.uk account (disabled by default)
+#brooklyn.jclouds.rackspace-cloudservers-uk.identity = YOURAPIKEY
+#brooklyn.jclouds.rackspace-cloudservers-uk.credential = YOURSECRETKEY
 
 ## Geoscaling Service (Used for global web fabric demo) https://www.geoscaling.com/dns2/
 brooklyn.geoscaling.username = USERNAME
@@ -46,8 +51,10 @@
 # brooklyn.localhost.private-key-passphrase = s3cr3tpassphrase
 
 ## GUI Security
-# brooklyn.webconsole.security.users=admin
+# brooklyn.webconsole.security.https.required=true
+# brooklyn.webconsole.security.users=admin,bob
 # brooklyn.webconsole.security.user.admin.password=password
+# brooklyn.webconsole.security.user.bob.password=bobsword
 
 ## GUI Security: Allow all.
 # brooklyn.webconsole.security.provider = brooklyn.rest.security.provider.AnyoneSecurityProvider
@@ -152,20 +159,49 @@
 
 # brooklyn.location.named.GoGrid = jclouds:gogrid
 
+## Google Compute: at present you have to create and download the P12 key from the Google "APIs & auth -> Registered Apps" interface,
+## then convert to PEM private key format using  `openssl pkcs12 -in Certificates.p12 -out Certificates.pem -nodes`
+## then embed that on one line as the 'credential, replacing new lines with \n as below
+## (hopefully this will be improved in jclouds in the future)
+# brooklyn.location.jclouds.google-compute-engine.identity=1234567890-somet1mesArand0mU1Dhere@developer.gserviceaccount.com
+# brooklyn.location.jclouds.google-compute-engine.credential=-----BEGIN PRIVATE KEY----- \nMIIblahablahblah \nblahblahblah \n-----END PRIVATE KEY-----
+# brooklyn.location.named.Google\ US = jclouds:google-compute-engine
+# brooklyn.location.named.Google\ US.region=us-central1-a
+# brooklyn.location.named.Google\ EU = jclouds:google-compute-engine
+# brooklyn.location.named.Google\ EU.region=europe-west1-a
+## the following flags for GCE are recommended
+## groupId makes it used a fixed network - otherwise it creates new networks (security groups) each time and you hit quotas pretty quickly
+## you have to manually create this network AND enable a firewall rule EG  tcp:1-65535;udp:1-65535;icmp:-1  
+## (fix for this is in progress)
+# brooklyn.location.jclouds.google-compute-engine.groupId=brooklyn-gce-default-group
+## imageId hard-coded prevents jclouds from auto-detected _deprecated_ images (fix for this in progress)
+# brooklyn.location.jclouds.google-compute-engine.imageId=gcel-12-04-v20130104
+## gce images have bad entropy, this ensures they have noisy /dev/random (even if the "randomness" is not quite as random)
+# brooklyn.location.jclouds.google-compute-engine.installDevUrandom=true
+
 # brooklyn.location.named.Green\ House\ Data = jclouds:greenhousedata-element-vcloud
 
 # brooklyn.location.named.Ninefold = jclouds:ninefold-compute
 
 # brooklyn.location.named.OpenHosting = jclouds:openhosting-east1
 
+brooklyn.location.named.Rackspace\ US-TX\ DFW = jclouds:rackspace-cloudservers-us:DFW
+brooklyn.location.named.Rackspace\ US-VA\ IAD = jclouds:rackspace-cloudservers-us:IAD
+brooklyn.location.named.Rackspace\ US-IL\ ORD = jclouds:rackspace-cloudservers-us:ORD
+brooklyn.location.named.Rackspace\ HKG = jclouds:rackspace-cloudservers-us:HKG
+brooklyn.location.named.Rackspace\ SYD = jclouds:rackspace-cloudservers-us:SYD
+# for UK you will need a separate account with rackspace.co.uk
+# brooklyn.location.named.Rackspace\ UK = jclouds:rackspace-cloudservers-uk
+
+## if you need to use Rackspace "first gen" API
+## (note the "next gen" api configured above seems to be faster)
 # brooklyn.jclouds.cloudservers-us.identity = YOURAPIKEY
 # brooklyn.jclouds.cloudservers-us.credential = YOURSECRETKEY
+# brooklyn.location.named.Rackspace\ US\ (First Gen) = jclouds:cloudservers-us
+## and as with next gen, first gen requires a separate acct for the UK:
 # brooklyn.jclouds.cloudservers-uk.identity = YOURAPIKEY
 # brooklyn.jclouds.cloudservers-uk.credential = YOURSECRETKEY
-brooklyn.location.named.Rackspace\ UK = jclouds:cloudservers-uk
-brooklyn.location.named.Rackspace\ US = jclouds:cloudservers-us
-brooklyn.location.named.Rackspace\ UK\ (Next\ Gen) = jclouds:rackspace-cloudservers-uk
-brooklyn.location.named.Rackspace\ US\ (Next\ Gen) = jclouds:rackspace-cloudservers-us
+# brooklyn.location.named.Rackspace\ UK\ (First Gen) = jclouds:cloudservers-uk
 
 # brooklyn.location.named.SeverLove = jclouds:serverlove-z1-man
 
@@ -184,6 +220,13 @@
 #brooklyn.location.named.On-Prem\ Iron\ Example.privateKeyFile=~/.ssh/produser_id_rsa
 #brooklyn.location.named.On-Prem\ Iron\ Example.privateKeyPassphrase=s3cr3tpassphrase
 
+## Various private clouds
+## openstack identity and credential are random strings of letters and numbers (TBC - still the case?)
+#brooklyn.location.named.My\ Openstack=jclouds:openstack-nova:https://9.9.9.9:9999/v2.0/
+## cloudstack identity and credential are rather long random strings of letters and numbers
+#brooklyn.location.named.My\ Cloudstack=jclouds:cloudstack:http://9.9.9.9:9999/client/api/
+## abiquo identity and credential are your login username/passed
+#brooklyn.location.named.My\ Abiquo=jclouds:abiquo:http://demonstration.abiquo.com/api/
 
 ###############################  Formatting Guide  #######################################
 
diff --git a/examples/simple-messaging-pubsub/src/main/java/brooklyn/demo/Publish.java b/examples/simple-messaging-pubsub/src/main/java/brooklyn/demo/Publish.java
index e07073d..d94edb5 100644
--- a/examples/simple-messaging-pubsub/src/main/java/brooklyn/demo/Publish.java
+++ b/examples/simple-messaging-pubsub/src/main/java/brooklyn/demo/Publish.java
@@ -37,7 +37,7 @@
 
 	        // Send 100 messages
 	        for (int n = 0; n < 100; n++) {
-	            String body = String.format("test message %03d", n);
+	            String body = String.format("test message %03d", n+1);
 	            TextMessage message = session.createTextMessage(body);
 	            messageProducer.send(message);
 	            System.out.printf("Sent message %s\n", body);
diff --git a/examples/simple-messaging-pubsub/src/main/java/brooklyn/demo/Subscribe.java b/examples/simple-messaging-pubsub/src/main/java/brooklyn/demo/Subscribe.java
index 51ce0c7..7899f47 100644
--- a/examples/simple-messaging-pubsub/src/main/java/brooklyn/demo/Subscribe.java
+++ b/examples/simple-messaging-pubsub/src/main/java/brooklyn/demo/Subscribe.java
@@ -15,7 +15,9 @@
 /** Receives messages from a queue on a Qpid broker at a given URL. */
 public class Subscribe {
     public static final String QUEUE = "'amq.direct'/'testQueue'; { node: { type: queue } }";
-
+    private static final long MESSAGE_TIMEOUT_MILLIS = 15000L;
+    private static final int MESSAGE_COUNT = 100;
+    
     public static void main(String...argv) throws Exception {
         Preconditions.checkElementIndex(0, argv.length, "Must specify broker URL");
         String url = argv[0];
@@ -30,18 +32,21 @@
         connection.start();
         Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
 
+        System.out.printf("Waiting up to %s milliseconds to receive %s messages\n", MESSAGE_TIMEOUT_MILLIS, MESSAGE_COUNT);
         try {
             // Create a producer for the queue
             Queue destination = session.createQueue(QUEUE);
             MessageConsumer messageConsumer = session.createConsumer(destination);
 
             // Try and receive 100 messages
-            int n = 100;
-            do {
-                TextMessage msg = (TextMessage) messageConsumer.receive(15000L);
-                if (msg == null) break;
-                System.out.printf("Got message: '%s'\n", msg.getText());
-            } while (n --> 0);
+            for (int n = 0; n < MESSAGE_COUNT; n++) {
+                TextMessage msg = (TextMessage) messageConsumer.receive(MESSAGE_TIMEOUT_MILLIS);
+                if (msg == null) {
+                    System.out.printf("No message received in %s milliseconds, exiting", MESSAGE_TIMEOUT_MILLIS);
+                    break;
+                }
+                System.out.printf("Got message %d: '%s'\n", n+1, msg.getText());
+            }
         } catch (Exception e) {
             System.err.printf("Error while receiving - %s\n", e.getMessage());
             System.err.printf("Cause: %s\n", Throwables.getStackTraceAsString(e));
diff --git a/examples/simple-nosql-cluster/src/main/java/brooklyn/demo/CumulusRDFApplication.java b/examples/simple-nosql-cluster/src/main/java/brooklyn/demo/CumulusRDFApplication.java
index 879fe66..af6e469 100644
--- a/examples/simple-nosql-cluster/src/main/java/brooklyn/demo/CumulusRDFApplication.java
+++ b/examples/simple-nosql-cluster/src/main/java/brooklyn/demo/CumulusRDFApplication.java
@@ -154,7 +154,7 @@
                 // Process the YAML template given in the application config
                 String url = Entities.getRequiredUrlConfig(CumulusRDFApplication.this, CUMULUS_RDF_CONFIG_URL);
                 Map<String, Object> config = MutableMap.<String, Object>of("cassandraHostname", endpoint.getHostText(), "cassandraThriftPort", endpoint.getPort());
-                String contents = TemplateProcessor.processTemplateContents(new ResourceUtils(CumulusRDFApplication.this).getResourceAsString(url), config);
+                String contents = TemplateProcessor.processTemplateContents(ResourceUtils.create(CumulusRDFApplication.this).getResourceAsString(url), config);
 
                 // Copy the file contents to the remote machine
                 return DynamicTasks.queue(SshEffectorTasks.put("/tmp/cumulus.yaml").contents(contents)).get();
diff --git a/examples/simple-web-cluster/src/main/java/brooklyn/demo/WebClusterDatabaseExampleApp.java b/examples/simple-web-cluster/src/main/java/brooklyn/demo/WebClusterDatabaseExampleApp.java
index f961634..34180f7 100644
--- a/examples/simple-web-cluster/src/main/java/brooklyn/demo/WebClusterDatabaseExampleApp.java
+++ b/examples/simple-web-cluster/src/main/java/brooklyn/demo/WebClusterDatabaseExampleApp.java
@@ -63,9 +63,9 @@
     
     public static final String DEFAULT_LOCATION = "localhost";
 
-    public static final String DEFAULT_WAR_PATH = new ResourceUtils(WebClusterDatabaseExampleApp.class).
+    public static final String DEFAULT_WAR_PATH = ResourceUtils.create(WebClusterDatabaseExampleApp.class)
             // take this war, from the classpath, or via maven if not on the classpath
-            firstAvailableUrl(
+            .firstAvailableUrl(
                     "classpath://hello-world-sql-webapp.war",
                     BrooklynMavenArtifacts.localUrl("example", "brooklyn-example-hello-world-sql-webapp", "war"))
             .or("classpath://hello-world-sql-webapp.war");
diff --git a/locations/jclouds/src/main/java/brooklyn/location/jclouds/JcloudsLocationConfig.java b/locations/jclouds/src/main/java/brooklyn/location/jclouds/JcloudsLocationConfig.java
index 4ea8144..c634482 100644
--- a/locations/jclouds/src/main/java/brooklyn/location/jclouds/JcloudsLocationConfig.java
+++ b/locations/jclouds/src/main/java/brooklyn/location/jclouds/JcloudsLocationConfig.java
@@ -138,7 +138,7 @@
     
     public static final ConfigKey<String> CUSTOM_MACHINE_SETUP_SCRIPT_VARS = ConfigKeys.newStringConfigKey("setup.script.vars", "vars to customize a setup.script i.e.: key1:value1,key2:value2");
     
-    public static final ConfigKey<Boolean> GENERATE_HOSTNAME = new BasicConfigKey<Boolean>(Boolean.class, "generate.hostname", "Use the nodename generated by jclouds ", false);
+    public static final ConfigKey<Boolean> GENERATE_HOSTNAME = new BasicConfigKey<Boolean>(Boolean.class, "generate.hostname", "Use the nodename generated by jclouds", false);
 
     public static final ConfigKey<Integer> MACHINE_CREATE_ATTEMPTS = ConfigKeys.newIntegerConfigKey(
             "machineCreateAttempts", "Number of times to retry if jclouds fails to create a VM", 1);
diff --git a/software/base/src/main/java/brooklyn/entity/basic/AbstractSoftwareProcessDriver.java b/software/base/src/main/java/brooklyn/entity/basic/AbstractSoftwareProcessDriver.java
index 091194d..7474703 100644
--- a/software/base/src/main/java/brooklyn/entity/basic/AbstractSoftwareProcessDriver.java
+++ b/software/base/src/main/java/brooklyn/entity/basic/AbstractSoftwareProcessDriver.java
@@ -26,11 +26,13 @@
 	private static final Logger log = LoggerFactory.getLogger(AbstractSoftwareProcessDriver.class);
 	
     protected final EntityLocal entity;
-    private final Location location;
+    protected final ResourceUtils resource;
+    protected final Location location;
     
     public AbstractSoftwareProcessDriver(EntityLocal entity, Location location) {
         this.entity = checkNotNull(entity, "entity");
         this.location = checkNotNull(location, "location");
+        this.resource = ResourceUtils.create(entity);
     }
 	
     /*
@@ -140,11 +142,11 @@
     public Location getLocation() { return location; } 
     
     public InputStream getResource(String url) {
-        return new ResourceUtils(entity).getResourceFromUrl(url);
+        return resource.getResourceFromUrl(url);
     }
     
     public String getResourceAsString(String url) {
-        return new ResourceUtils(entity).getResourceAsString(url);
+        return resource.getResourceAsString(url);
     }
 
     public String processTemplate(File templateConfigFile, Map<String,Object> extraSubstitutions) {
diff --git a/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNodeSshDriver.java b/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNodeSshDriver.java
index 1e6e958..536ef24 100644
--- a/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNodeSshDriver.java
+++ b/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNodeSshDriver.java
@@ -1,7 +1,7 @@
 package brooklyn.entity.brooklynnode;
 
-import static com.google.common.base.Preconditions.checkNotNull;
-import static java.lang.String.format;
+import static com.google.common.base.Preconditions.*;
+import static java.lang.String.*;
 
 import java.io.ByteArrayInputStream;
 import java.io.File;
@@ -16,7 +16,6 @@
 import brooklyn.entity.java.JarBuilder;
 import brooklyn.entity.java.JavaSoftwareProcessSshDriver;
 import brooklyn.location.basic.SshMachineLocation;
-import brooklyn.util.ResourceUtils;
 import brooklyn.util.collections.MutableMap;
 import brooklyn.util.net.Networking;
 import brooklyn.util.ssh.BashCommands;
@@ -81,11 +80,11 @@
                     .body.append("cd "+getInstallDir(), "test -f BROOKLYN")
                     .execute() == 0;
             if (!exists) {
-                InputStream distroStream = new ResourceUtils(entity).getResourceFromUrl(uploadUrl);
+                InputStream distroStream = resource.getResourceFromUrl(uploadUrl);
                 getMachine().copyTo(distroStream, getInstallDir()+"/"+saveAs);
             }
         } else {
-            commands.addAll(BashCommands.downloadUrlAs(urls, saveAs));
+            commands.addAll(BashCommands.commandsToDownloadUrlsAs(urls, saveAs));
         }
         commands.add(BashCommands.INSTALL_TAR);
         commands.add("tar xzfv " + saveAs);
@@ -145,7 +144,7 @@
                 String val = substitution.getValue();
                 resolvedRemotePath = resolvedRemotePath.replace("${"+key+"}", val).replace("$"+key, val);
             }
-            machine.copyTo(MutableMap.of("permissions", "0600"), new ResourceUtils(entity).getResourceFromUrl(localResource), resolvedRemotePath);
+            machine.copyTo(MutableMap.of("permissions", "0600"), resource.getResourceFromUrl(localResource), resolvedRemotePath);
         }
         
         // TODO Copied from VanillaJavaApp; share code there? Or delete this functionality from here?
@@ -165,7 +164,7 @@
                 toinstall = f;
             }
             
-            int result = machine.installTo(new ResourceUtils(entity), toinstall, getRunDir() + "/" + "lib" + "/");
+            int result = machine.installTo(resource, toinstall, getRunDir() + "/" + "lib" + "/");
             if (result != 0)
                 throw new IllegalStateException(format("unable to install classpath entry %s for %s at %s",f,entity,machine));
             
@@ -278,7 +277,7 @@
         if (contents != null) {
             machine.copyTo(new ByteArrayInputStream(contents.getBytes()), tempRemotePath);
         } else if (alternativeUri != null) {
-            InputStream propertiesStream = new ResourceUtils(entity).getResourceFromUrl(alternativeUri);
+            InputStream propertiesStream = resource.getResourceFromUrl(alternativeUri);
             machine.copyTo(propertiesStream, tempRemotePath);
         } else {
             throw new IllegalStateException("No contents supplied for file "+remotePath);
diff --git a/software/base/src/main/java/brooklyn/entity/java/JavaSoftwareProcessSshDriver.java b/software/base/src/main/java/brooklyn/entity/java/JavaSoftwareProcessSshDriver.java
index b3f5e4d..16f9d5f 100644
--- a/software/base/src/main/java/brooklyn/entity/java/JavaSoftwareProcessSshDriver.java
+++ b/software/base/src/main/java/brooklyn/entity/java/JavaSoftwareProcessSshDriver.java
@@ -271,8 +271,8 @@
                         BashCommands.installJava6OrFail()
                         // could use Jclouds routines -- but the following complains about yum-install not defined
                         // even though it is set as an alias (at the start of the first file)
-                        //   new ResourceUtils(this).getResourceAsString("classpath:///functions/setupPublicCurl.sh"),
-                        //   new ResourceUtils(this).getResourceAsString("classpath:///functions/installOpenJDK.sh"),
+                        //   resource.getResourceAsString("classpath:///functions/setupPublicCurl.sh"),
+                        //   resource.getResourceAsString("classpath:///functions/installOpenJDK.sh"),
                         //   "installOpenJDK"
                         ).execute();
                 if (result==0)
diff --git a/software/base/src/main/java/brooklyn/entity/java/JmxSupport.java b/software/base/src/main/java/brooklyn/entity/java/JmxSupport.java
index c925846..c9d3ada 100644
--- a/software/base/src/main/java/brooklyn/entity/java/JmxSupport.java
+++ b/software/base/src/main/java/brooklyn/entity/java/JmxSupport.java
@@ -137,7 +137,7 @@
                     log.warn("Auto-detecting JMX configuration for "+entity+": cannot identify location so setting JMXMP");
                     jmxAgentMode = JmxAgentModes.JMXMP;                    
                 }
-                if (!new ResourceUtils(this).doesUrlExist(getJmxAgentJarUrl())) {
+                if (!ResourceUtils.create(this).doesUrlExist(getJmxAgentJarUrl())) {
                     // can happen e.g. if eclipse build
                     log.warn("JMX agent JAR not found ("+getJmxAgentJarUrl()+") when auto-detecting JMX settings for "+entity+"; " +
                             "likely cause is an incomplete build (e.g. from Eclipse; run a maven build then retry in the IDE); "+
@@ -253,7 +253,7 @@
         if (artifact==null)
             throw new IllegalStateException("Either JMX is not enabled or there is an error in the configuration (JMX mode "+getJmxAgentMode()+" does not support agent JAR)");
         String jar = "classpath://" + artifact.getFilename();
-        if (new ResourceUtils(this).doesUrlExist(jar))
+        if (ResourceUtils.create(this).doesUrlExist(jar))
             return jar;
         
         String result = MavenRetriever.localUrl(artifact);
@@ -319,7 +319,7 @@
     /** installs files needed for JMX, to the runDir given in constructor, assuming the runDir has been created */ 
     public void install() {
         if (getJmxAgentMode()!=JmxAgentModes.NONE) {
-            getMachine().get().copyTo(new ResourceUtils(this).getResourceFromUrl(
+            getMachine().get().copyTo(ResourceUtils.create(this).getResourceFromUrl(
                 getJmxAgentJarUrl()), getJmxAgentJarDestinationFilePath());
         }
         if (isSecure()) {
diff --git a/software/base/src/main/java/brooklyn/entity/java/VanillaJavaAppSshDriver.java b/software/base/src/main/java/brooklyn/entity/java/VanillaJavaAppSshDriver.java
index 2e2a79b..1ab5d59 100644
--- a/software/base/src/main/java/brooklyn/entity/java/VanillaJavaAppSshDriver.java
+++ b/software/base/src/main/java/brooklyn/entity/java/VanillaJavaAppSshDriver.java
@@ -1,6 +1,6 @@
 package brooklyn.entity.java;
 
-import static java.lang.String.format;
+import static java.lang.String.*;
 
 import java.io.File;
 import java.io.IOException;
@@ -10,13 +10,12 @@
 import java.util.List;
 import java.util.Map;
 
-import com.google.common.collect.ImmutableList;
-
 import brooklyn.location.basic.SshMachineLocation;
-import brooklyn.util.ResourceUtils;
 import brooklyn.util.collections.MutableMap;
 import brooklyn.util.text.StringEscapes.BashStringEscapes;
 
+import com.google.common.collect.ImmutableList;
+
 /**
  * The SSH implementation of the {@link VanillaJavaAppDriver}.
  */
@@ -48,7 +47,6 @@
                 body.append(format("mkdir -p %s/lib", getRunDir())).
                 execute();
 
-        ResourceUtils r = new ResourceUtils(entity);
         SshMachineLocation machine = getMachine();
         VanillaJavaApp entity = getEntity();
         for (String f : entity.getClasspath()) {
@@ -67,7 +65,7 @@
                 toinstall = f;
             }
             
-            int result = machine.installTo(new ResourceUtils(entity), toinstall, getRunDir() + "/" + "lib" + "/");
+            int result = machine.installTo(resource, toinstall, getRunDir() + "/" + "lib" + "/");
             if (result != 0)
                 throw new IllegalStateException(format("unable to install classpath entry %s for %s at %s",f,entity,machine));
             
diff --git a/software/base/src/test/java/brooklyn/entity/basic/lifecycle/MyEntityImpl.java b/software/base/src/test/java/brooklyn/entity/basic/lifecycle/MyEntityImpl.java
index af1c353..d4ca659 100644
--- a/software/base/src/test/java/brooklyn/entity/basic/lifecycle/MyEntityImpl.java
+++ b/software/base/src/test/java/brooklyn/entity/basic/lifecycle/MyEntityImpl.java
@@ -53,10 +53,11 @@
         @Override
         public void install() {
             String resourceName = "/"+MyEntityApp.class.getName().replace(".", "/")+".class";
-            if (new ResourceUtils(this).getResourceFromUrl(resourceName) == null) 
+            ResourceUtils r = ResourceUtils.create(this);
+            if (r.getResourceFromUrl(resourceName) == null) 
                 throw new IllegalStateException("Cannot find resource "+resourceName);
             String tmpFile = "/tmp/brooklyn-test-MyEntityApp-"+Identifiers.makeRandomId(6)+".class";
-            int result = getMachine().installTo(new ResourceUtils(this), resourceName, tmpFile);
+            int result = getMachine().installTo(r, resourceName, tmpFile);
             if (result!=0) throw new IllegalStateException("Cannot install "+resourceName+" to "+tmpFile);
             String saveAs = "classes/"+MyEntityApp.class.getPackage().getName().replace(".", "/")+"/"+MyEntityApp.class.getSimpleName()+".class";
             newScript(INSTALLING).
diff --git a/software/base/src/test/java/brooklyn/entity/chef/ChefLiveTestSupport.java b/software/base/src/test/java/brooklyn/entity/chef/ChefLiveTestSupport.java
index c767a9e..e0edb89 100644
--- a/software/base/src/test/java/brooklyn/entity/chef/ChefLiveTestSupport.java
+++ b/software/base/src/test/java/brooklyn/entity/chef/ChefLiveTestSupport.java
@@ -58,7 +58,7 @@
     public synchronized static String installBrooklynChefHostedConfig() {
         if (defaultConfigFile!=null) return defaultConfigFile;
         File tempDir = Files.createTempDir();
-        ResourceUtils r = new ResourceUtils(ChefServerTasksIntegrationTest.class);
+        ResourceUtils r = ResourceUtils.create(ChefServerTasksIntegrationTest.class);
         try {
             for (String f: new String[] { "knife.rb", "brooklyn-tests.pem", "brooklyn-validator.pem" }) {
                 Files.copy(InputStreamSupplier.fromString(r.getResourceAsString(
diff --git a/software/base/src/test/java/brooklyn/entity/java/JmxSupportTest.java b/software/base/src/test/java/brooklyn/entity/java/JmxSupportTest.java
index e5f38ae..667af53 100644
--- a/software/base/src/test/java/brooklyn/entity/java/JmxSupportTest.java
+++ b/software/base/src/test/java/brooklyn/entity/java/JmxSupportTest.java
@@ -44,7 +44,7 @@
         Assert.assertEquals(support.getJmxAgentJarMavenArtifact().getArtifactId(),
                 "brooklyn-jmxmp-agent");
         
-        Assert.assertTrue(new ResourceUtils(this).doesUrlExist(support.getJmxAgentJarUrl()), support.getJmxAgentJarUrl());
+        Assert.assertTrue(ResourceUtils.create(this).doesUrlExist(support.getJmxAgentJarUrl()), support.getJmxAgentJarUrl());
         Assert.assertTrue(support.getJmxAgentJarUrl().contains("-shaded-"), support.getJmxAgentJarUrl());
     }
     
@@ -56,7 +56,7 @@
         Assert.assertEquals(support.getJmxAgentJarMavenArtifact().getArtifactId(),
                 "brooklyn-jmxrmi-agent");
         
-        Assert.assertTrue(new ResourceUtils(this).doesUrlExist(support.getJmxAgentJarUrl()), support.getJmxAgentJarUrl());
+        Assert.assertTrue(ResourceUtils.create(this).doesUrlExist(support.getJmxAgentJarUrl()), support.getJmxAgentJarUrl());
     }
 
     @Test(groups="Integration")
@@ -83,7 +83,7 @@
     private void checkValidArchive(String url, long minSize) {
         byte[] bytes;
         try {
-            bytes = ResourceUtils.readFullyBytes(new ResourceUtils(this).getResourceFromUrl(url));
+            bytes = ResourceUtils.readFullyBytes(ResourceUtils.create(this).getResourceFromUrl(url));
             log.info("read "+bytes.length+" bytes from "+url+" for "+JavaClassNames.callerNiceClassAndMethod(1));
         } catch (Exception e) {
             log.warn("Unable to read URL "+url+" for " +JavaClassNames.callerNiceClassAndMethod(1)+
diff --git a/software/base/src/test/java/brooklyn/entity/java/VanillaJavaAppRebindTest.java b/software/base/src/test/java/brooklyn/entity/java/VanillaJavaAppRebindTest.java
index e7b0167..3691a9a 100644
--- a/software/base/src/test/java/brooklyn/entity/java/VanillaJavaAppRebindTest.java
+++ b/software/base/src/test/java/brooklyn/entity/java/VanillaJavaAppRebindTest.java
@@ -46,7 +46,7 @@
         managementContext = RebindTestUtils.newPersistingManagementContext(mementoDir, classLoader);
         
         if (BROOKLYN_THIS_CLASSPATH==null) {
-            BROOKLYN_THIS_CLASSPATH = new ResourceUtils(MAIN_CLASS).getClassLoaderDir();
+            BROOKLYN_THIS_CLASSPATH = ResourceUtils.create(MAIN_CLASS).getClassLoaderDir();
         }
         app = new TestApplicationImpl();
         loc = new LocalhostMachineProvisioningLocation(MutableMap.of("address", "localhost"));
diff --git a/software/base/src/test/java/brooklyn/entity/java/VanillaJavaAppTest.java b/software/base/src/test/java/brooklyn/entity/java/VanillaJavaAppTest.java
index e492129..bd51644 100644
--- a/software/base/src/test/java/brooklyn/entity/java/VanillaJavaAppTest.java
+++ b/software/base/src/test/java/brooklyn/entity/java/VanillaJavaAppTest.java
@@ -65,7 +65,7 @@
     @BeforeMethod(alwaysRun = true)
     public void setUp() throws Exception {
         if (BROOKLYN_THIS_CLASSPATH==null) {
-            BROOKLYN_THIS_CLASSPATH = new ResourceUtils(MAIN_CLASS).getClassLoaderDir();
+            BROOKLYN_THIS_CLASSPATH = ResourceUtils.create(MAIN_CLASS).getClassLoaderDir();
         }
         app = new AbstractApplication() {};
         loc = new LocalhostMachineProvisioningLocation(MutableMap.of("address", "localhost"));
diff --git a/software/database/src/main/java/brooklyn/entity/database/mysql/MySqlSshDriver.java b/software/database/src/main/java/brooklyn/entity/database/mysql/MySqlSshDriver.java
index 606244f..427b3ed 100644
--- a/software/database/src/main/java/brooklyn/entity/database/mysql/MySqlSshDriver.java
+++ b/software/database/src/main/java/brooklyn/entity/database/mysql/MySqlSshDriver.java
@@ -2,6 +2,7 @@
 
 import static brooklyn.util.GroovyJavaMethods.elvis;
 import static brooklyn.util.GroovyJavaMethods.truth;
+import static brooklyn.util.ssh.BashCommands.commandsToDownloadUrlsAs;
 import static brooklyn.util.ssh.BashCommands.installPackage;
 import static brooklyn.util.ssh.BashCommands.ok;
 import static java.lang.String.format;
@@ -22,7 +23,6 @@
 import brooklyn.location.OsDetails;
 import brooklyn.location.basic.BasicOsDetails.OsVersions;
 import brooklyn.location.basic.SshMachineLocation;
-import brooklyn.util.ResourceUtils;
 import brooklyn.util.collections.MutableMap;
 import brooklyn.util.ssh.BashCommands;
 import brooklyn.util.text.ComparableVersion;
@@ -101,7 +101,7 @@
         // these deps are needed on some OS versions but others don't need them so ignore failures (ok(...))
         commands.add(ok(installPackage(ImmutableMap.of("yum", "libaio", "apt", "ia32-libs"), null)));
         commands.add("echo finished installing extra packages");
-        commands.addAll(BashCommands.commandsToDownloadUrlsAs(urls, saveAs));
+        commands.addAll(commandsToDownloadUrlsAs(urls, saveAs));
         commands.add(format("tar xfvz %s", saveAs));
 
         newScript(INSTALLING).
@@ -146,7 +146,7 @@
         Reader creationScript;
         String url = entity.getConfig(MySqlNode.CREATION_SCRIPT_URL);
         if (!Strings.isBlank(url))
-            creationScript = new InputStreamReader(new ResourceUtils(entity).getResourceFromUrl(url));
+            creationScript = new InputStreamReader(resource.getResourceFromUrl(url));
         else creationScript =
                 new StringReader((String) elvis(entity.getConfig(MySqlNode.CREATION_SCRIPT_CONTENTS), ""));
         getMachine().copyTo(creationScript, getRunDir() + "/creation-script.cnf");
diff --git a/software/database/src/main/java/brooklyn/entity/database/postgresql/PostgreSqlNodeChefImpl.java b/software/database/src/main/java/brooklyn/entity/database/postgresql/PostgreSqlNodeChefImpl.java
index 32bd06a..1bb8645 100644
--- a/software/database/src/main/java/brooklyn/entity/database/postgresql/PostgreSqlNodeChefImpl.java
+++ b/software/database/src/main/java/brooklyn/entity/database/postgresql/PostgreSqlNodeChefImpl.java
@@ -73,7 +73,7 @@
             String creationScript;
             String creationScriptUrl = entity().getConfig(PostgreSqlNode.CREATION_SCRIPT_URL);
             if (creationScriptUrl != null)
-                creationScript = new ResourceUtils(entity()).getResourceAsString(creationScriptUrl);
+                creationScript = ResourceUtils.create(entity()).getResourceAsString(creationScriptUrl);
             else creationScript = entity().getConfig(PostgreSqlNode.CREATION_SCRIPT_CONTENTS);
             entity().invoke(PostgreSqlNodeChefImpl.EXECUTE_SCRIPT, 
                     ConfigBag.newInstance().configure(ExecuteScriptEffectorBody.SCRIPT, creationScript).getAllConfig()).getUnchecked();
diff --git a/software/database/src/main/java/brooklyn/entity/database/postgresql/PostgreSqlSshDriver.java b/software/database/src/main/java/brooklyn/entity/database/postgresql/PostgreSqlSshDriver.java
index 617e177..8dda553 100644
--- a/software/database/src/main/java/brooklyn/entity/database/postgresql/PostgreSqlSshDriver.java
+++ b/software/database/src/main/java/brooklyn/entity/database/postgresql/PostgreSqlSshDriver.java
@@ -13,7 +13,6 @@
 
 import brooklyn.entity.basic.AbstractSoftwareProcessSshDriver;
 import brooklyn.location.basic.SshMachineLocation;
-import brooklyn.util.ResourceUtils;
 import brooklyn.util.collections.MutableList;
 import brooklyn.util.collections.MutableMap;
 import brooklyn.util.net.Urls;
@@ -129,7 +128,7 @@
         String creationScriptUrl = entity.getConfig(PostgreSqlNode.CREATION_SCRIPT_URL);
         Reader creationScript;
         if (creationScriptUrl != null)
-            creationScript = new InputStreamReader(new ResourceUtils(entity).getResourceFromUrl(creationScriptUrl));
+            creationScript = new InputStreamReader(resource.getResourceFromUrl(creationScriptUrl));
         else creationScript = new StringReader(entity.getConfig(PostgreSqlNode.CREATION_SCRIPT_CONTENTS));
 
         getMachine().copyTo(creationScript, getRunDir() + "/creation-script.sql");
diff --git a/software/database/src/main/java/brooklyn/entity/database/rubyrep/RubyRepSshDriver.java b/software/database/src/main/java/brooklyn/entity/database/rubyrep/RubyRepSshDriver.java
index 44c1b9e..a732263 100644
--- a/software/database/src/main/java/brooklyn/entity/database/rubyrep/RubyRepSshDriver.java
+++ b/software/database/src/main/java/brooklyn/entity/database/rubyrep/RubyRepSshDriver.java
@@ -1,6 +1,6 @@
 package brooklyn.entity.database.rubyrep;
 
-import static java.lang.String.format;
+import static java.lang.String.*;
 
 import java.io.InputStreamReader;
 import java.io.Reader;
@@ -18,7 +18,6 @@
 import brooklyn.entity.database.mysql.MySqlSshDriver;
 import brooklyn.entity.java.JavaSoftwareProcessSshDriver;
 import brooklyn.location.basic.SshMachineLocation;
-import brooklyn.util.ResourceUtils;
 import brooklyn.util.collections.MutableMap;
 import brooklyn.util.ssh.BashCommands;
 
@@ -68,7 +67,7 @@
         Reader configContents;
         if (configScriptUrl != null) {
             // If set accept as-is
-            configContents = new InputStreamReader(new ResourceUtils(entity).getResourceFromUrl(configScriptUrl));
+            configContents = new InputStreamReader(resource.getResourceFromUrl(configScriptUrl));
         } else {
             String configScriptContents = processTemplate(entity.getAttribute(RubyRepNode.TEMPLATE_CONFIGURATION_URL));
             configContents = new StringReader(configScriptContents);
diff --git a/software/messaging/src/main/java/brooklyn/entity/messaging/activemq/ActiveMQSshDriver.java b/software/messaging/src/main/java/brooklyn/entity/messaging/activemq/ActiveMQSshDriver.java
index 90d9739..7079b5a 100644
--- a/software/messaging/src/main/java/brooklyn/entity/messaging/activemq/ActiveMQSshDriver.java
+++ b/software/messaging/src/main/java/brooklyn/entity/messaging/activemq/ActiveMQSshDriver.java
@@ -1,6 +1,6 @@
 package brooklyn.entity.messaging.activemq;
 
-import static java.lang.String.format;
+import static java.lang.String.*;
 
 import java.io.ByteArrayInputStream;
 import java.util.LinkedList;
@@ -9,9 +9,7 @@
 
 import brooklyn.entity.drivers.downloads.DownloadResolver;
 import brooklyn.entity.java.JavaSoftwareProcessSshDriver;
-import brooklyn.entity.java.JmxSupport;
 import brooklyn.location.basic.SshMachineLocation;
-import brooklyn.util.ResourceUtils;
 import brooklyn.util.collections.MutableMap;
 import brooklyn.util.net.Networking;
 import brooklyn.util.ssh.BashCommands;
diff --git a/software/messaging/src/main/java/brooklyn/entity/messaging/qpid/QpidBrokerImpl.java b/software/messaging/src/main/java/brooklyn/entity/messaging/qpid/QpidBrokerImpl.java
index f8d259b..818cc57 100644
--- a/software/messaging/src/main/java/brooklyn/entity/messaging/qpid/QpidBrokerImpl.java
+++ b/software/messaging/src/main/java/brooklyn/entity/messaging/qpid/QpidBrokerImpl.java
@@ -93,6 +93,7 @@
 
     @Override
     protected void connectSensors() {
+        super.connectSensors();
         String serverInfoMBeanName = "org.apache.qpid:type=ServerInformation,name=ServerInformation";
 
         jmxFeed = JmxFeed.builder()
diff --git a/software/nosql/src/main/java/brooklyn/entity/nosql/cassandra/CassandraNodeSshDriver.java b/software/nosql/src/main/java/brooklyn/entity/nosql/cassandra/CassandraNodeSshDriver.java
index 25b25e7..1d54fad 100644
--- a/software/nosql/src/main/java/brooklyn/entity/nosql/cassandra/CassandraNodeSshDriver.java
+++ b/software/nosql/src/main/java/brooklyn/entity/nosql/cassandra/CassandraNodeSshDriver.java
@@ -167,7 +167,7 @@
             int lastSlashIndex = customSnitchJarUrl.lastIndexOf("/");
             String customSnitchJarName = (lastSlashIndex > 0) ? customSnitchJarUrl.substring(lastSlashIndex+1) : "customBrooklynSnitch.jar";
             String jarDestinationFile = String.format("%s/lib/%s", getRunDir(), customSnitchJarName);
-            InputStream customSnitchJarStream = checkNotNull(new ResourceUtils(entity).getResourceFromUrl(customSnitchJarUrl), "%s could not be loaded", customSnitchJarUrl);
+            InputStream customSnitchJarStream = checkNotNull(resource.getResourceFromUrl(customSnitchJarUrl), "%s could not be loaded", customSnitchJarUrl);
             try {
                 getMachine().copyTo(customSnitchJarStream, jarDestinationFile);
             } finally {
diff --git a/software/webapp/src/main/java/brooklyn/entity/dns/geoscaling/GeoscalingScriptGenerator.java b/software/webapp/src/main/java/brooklyn/entity/dns/geoscaling/GeoscalingScriptGenerator.java
index 518c885..b3b6bcb 100644
--- a/software/webapp/src/main/java/brooklyn/entity/dns/geoscaling/GeoscalingScriptGenerator.java
+++ b/software/webapp/src/main/java/brooklyn/entity/dns/geoscaling/GeoscalingScriptGenerator.java
@@ -11,7 +11,7 @@
 import brooklyn.util.javalang.JavaClassNames;
 import brooklyn.util.text.Strings;
 
-class GeoscalingScriptGenerator {
+public class GeoscalingScriptGenerator {
     
     private static final String PHP_SCRIPT_TEMPLATE_RESOURCE = JavaClassNames.resolveClasspathUrl(GeoscalingScriptGenerator.class, "template.php");
     private static final String HOSTS_DECLARATIONS_MARKER = "/* HOST DECLARATIONS TO BE SUBSTITUTED HERE */";
@@ -23,7 +23,7 @@
     }
     
     public static String generateScriptString(Date generationTime, Collection<HostGeoInfo> hosts) {
-        String template = new ResourceUtils(GeoscalingScriptGenerator.class).getResourceAsString(PHP_SCRIPT_TEMPLATE_RESOURCE);
+        String template = ResourceUtils.create(GeoscalingScriptGenerator.class).getResourceAsString(PHP_SCRIPT_TEMPLATE_RESOURCE);
         SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss 'UTC'");
         sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
         String datestamp = sdf.format(generationTime);
diff --git a/software/webapp/src/main/java/brooklyn/entity/proxy/nginx/NginxControllerImpl.java b/software/webapp/src/main/java/brooklyn/entity/proxy/nginx/NginxControllerImpl.java
index d30b95f..8f730de 100644
--- a/software/webapp/src/main/java/brooklyn/entity/proxy/nginx/NginxControllerImpl.java
+++ b/software/webapp/src/main/java/brooklyn/entity/proxy/nginx/NginxControllerImpl.java
@@ -208,14 +208,14 @@
         if (!Strings.isEmpty(ssl.getCertificateSourceUrl())) {
             String certificateDestination = Strings.isEmpty(ssl.getCertificateDestination()) ? driver.getRunDir() + "/conf/" + id + ".crt" : ssl.getCertificateDestination();
             driver.getMachine().copyTo(ImmutableMap.of("permissions", "0400"),
-                    new ResourceUtils(this).getResourceFromUrl(ssl.getCertificateSourceUrl()),
+                    ResourceUtils.create(this).getResourceFromUrl(ssl.getCertificateSourceUrl()),
                     certificateDestination);
         }
 
         if (!Strings.isEmpty(ssl.getKeySourceUrl())) {
             String keyDestination = Strings.isEmpty(ssl.getKeyDestination()) ? driver.getRunDir() + "/conf/" + id + ".key" : ssl.getKeyDestination();
             driver.getMachine().copyTo(ImmutableMap.of("permissions", "0400"),
-                    new ResourceUtils(this).getResourceFromUrl(ssl.getKeySourceUrl()),
+                    ResourceUtils.create(this).getResourceFromUrl(ssl.getKeySourceUrl()),
                     keyDestination);
         }
 
diff --git a/software/webapp/src/main/java/brooklyn/entity/webapp/jboss/JBoss7SshDriver.java b/software/webapp/src/main/java/brooklyn/entity/webapp/jboss/JBoss7SshDriver.java
index 62c9803..540fd4d 100644
--- a/software/webapp/src/main/java/brooklyn/entity/webapp/jboss/JBoss7SshDriver.java
+++ b/software/webapp/src/main/java/brooklyn/entity/webapp/jboss/JBoss7SshDriver.java
@@ -1,20 +1,6 @@
 package brooklyn.entity.webapp.jboss;
 
-import brooklyn.entity.basic.SoftwareProcess;
-import brooklyn.entity.drivers.downloads.DownloadResolver;
-import brooklyn.entity.webapp.JavaWebAppSshDriver;
-import brooklyn.location.basic.SshMachineLocation;
-import brooklyn.util.ResourceUtils;
-import brooklyn.util.collections.MutableMap;
-import brooklyn.util.net.Networking;
-import brooklyn.util.ssh.BashCommands;
-import com.google.common.base.Charsets;
-import com.google.common.base.Preconditions;
-import com.google.common.base.Strings;
-import com.google.common.hash.Hashing;
-import com.google.common.io.BaseEncoding;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import static java.lang.String.*;
 
 import java.io.ByteArrayInputStream;
 import java.io.InputStream;
@@ -23,17 +9,33 @@
 import java.util.Map;
 import java.util.UUID;
 
-import static java.lang.String.format;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import brooklyn.entity.basic.Entities;
+import brooklyn.entity.basic.SoftwareProcess;
+import brooklyn.entity.drivers.downloads.DownloadResolver;
+import brooklyn.entity.webapp.JavaWebAppSshDriver;
+import brooklyn.location.basic.SshMachineLocation;
+import brooklyn.util.collections.MutableMap;
+import brooklyn.util.net.Networking;
+import brooklyn.util.ssh.BashCommands;
+
+import com.google.common.base.Charsets;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Strings;
+import com.google.common.hash.Hashing;
+import com.google.common.io.BaseEncoding;
 
 public class JBoss7SshDriver extends JavaWebAppSshDriver implements JBoss7Driver {
 
     public static final Logger LOG = LoggerFactory.getLogger(JBoss7SshDriver.class);
 
     /*
-      * TODO
-      * - expose log file location, or even support accessing them dynamically
-      * - more configurability of config files, java memory, etc
-      */
+     * TODO
+     * - expose log file location, or even support accessing them dynamically
+     * - more configurability of config files, java memory, etc
+     */
 
     public static final String SERVER_TYPE = "standalone";
     private static final String CONFIG_FILE = "standalone-brooklyn.xml";
@@ -110,13 +112,13 @@
     }
     
     public void install() {
-        DownloadResolver resolver = entity.getManagementContext().getEntityDownloadsManager().newDownloader(this);
+        DownloadResolver resolver = Entities.newDownloader(this);
         List<String> urls = resolver.getTargets();
         String saveAs = resolver.getFilename();
         expandedInstallDir = getInstallDir()+"/"+resolver.getUnpackedDirectoryName(format("jboss-as-%s", getVersion()));
         
         List<String> commands = new LinkedList<String>();
-        commands.addAll(BashCommands.downloadUrlAs(urls, saveAs));
+        commands.addAll(BashCommands.commandsToDownloadUrlsAs(urls, saveAs));
         commands.add(BashCommands.INSTALL_TAR);
         commands.add("tar xzfv " + saveAs);
 
@@ -186,7 +188,7 @@
                 throw new NullPointerException("keystore URL must be specified if using HTTPS for "+entity);
             }
             String destinationSslKeystoreFile = getSslKeystoreFile();
-            InputStream keystoreStream = new ResourceUtils(this).getResourceFromUrl(keystoreUrl);
+            InputStream keystoreStream = resource.getResourceFromUrl(keystoreUrl);
             getMachine().copyTo(keystoreStream, destinationSslKeystoreFile);
         }
 
diff --git a/software/webapp/src/test/java/brooklyn/entity/dns/geoscaling/GeoscalingScriptGeneratorTest.groovy b/software/webapp/src/test/java/brooklyn/entity/dns/geoscaling/GeoscalingScriptGeneratorTest.groovy
index 16e925c..d296ce8 100644
--- a/software/webapp/src/test/java/brooklyn/entity/dns/geoscaling/GeoscalingScriptGeneratorTest.groovy
+++ b/software/webapp/src/test/java/brooklyn/entity/dns/geoscaling/GeoscalingScriptGeneratorTest.groovy
@@ -28,10 +28,10 @@
         Date generationTime = new Date(0);
         String generatedScript = GeoscalingScriptGenerator.generateScriptString(generationTime, HOSTS);
         assertTrue(generatedScript.contains("1.2.3"));
-        String expectedScript = new ResourceUtils(this).getResourceAsString("brooklyn/entity/dns/geoscaling/expectedScript.php");
+        String expectedScript = ResourceUtils.create(this).getResourceAsString("brooklyn/entity/dns/geoscaling/expectedScript.php");
         assertEquals(expectedScript, generatedScript);
         //also make sure leading slash is allowed
-        String expectedScript2 = new ResourceUtils(this).getResourceAsString("/brooklyn/entity/dns/geoscaling/expectedScript.php");
+        String expectedScript2 = ResourceUtils.create(this).getResourceAsString("/brooklyn/entity/dns/geoscaling/expectedScript.php");
         assertEquals(expectedScript, generatedScript);
     }
     
diff --git a/usage/cli/src/main/java/brooklyn/cli/Main.java b/usage/cli/src/main/java/brooklyn/cli/Main.java
index 0b30f4d..419de73 100644
--- a/usage/cli/src/main/java/brooklyn/cli/Main.java
+++ b/usage/cli/src/main/java/brooklyn/cli/Main.java
@@ -232,7 +232,7 @@
                 }
             }
 
-            ResourceUtils utils = new ResourceUtils(this);
+            ResourceUtils utils = ResourceUtils.create(this);
             ClassLoader parent = utils.getLoader();
             GroovyClassLoader loader = new GroovyClassLoader(parent);
 
diff --git a/usage/cli/src/test/java/brooklyn/cli/CliTest.java b/usage/cli/src/test/java/brooklyn/cli/CliTest.java
index d37f13b..658d85d 100644
--- a/usage/cli/src/test/java/brooklyn/cli/CliTest.java
+++ b/usage/cli/src/test/java/brooklyn/cli/CliTest.java
@@ -112,7 +112,7 @@
     
     private Object loadApplicationFromClasspathOrParse(String appName) throws Exception {
         LaunchCommand launchCommand = new Main.LaunchCommand();
-        ResourceUtils resourceUtils = new ResourceUtils(this);
+        ResourceUtils resourceUtils = ResourceUtils.create(this);
         GroovyClassLoader loader = new GroovyClassLoader(CliTest.class.getClassLoader());
         return launchCommand.loadApplicationFromClasspathOrParse(resourceUtils, loader, appName);
     }
@@ -136,7 +136,7 @@
             Files.write(contents.getBytes(), groovyFile);
 
             LaunchCommand launchCommand = new Main.LaunchCommand();
-            ResourceUtils resourceUtils = new ResourceUtils(this);
+            ResourceUtils resourceUtils = ResourceUtils.create(this);
             GroovyClassLoader loader = new GroovyClassLoader(CliTest.class.getClassLoader());
             launchCommand.execGroovyScript(resourceUtils, loader, groovyFile.toURI().toString());
             assertTrue(GROOVY_INVOKED.get());
diff --git a/usage/launcher/src/main/java/brooklyn/launcher/BrooklynWebServer.java b/usage/launcher/src/main/java/brooklyn/launcher/BrooklynWebServer.java
index 12d43ef..96b020a 100644
--- a/usage/launcher/src/main/java/brooklyn/launcher/BrooklynWebServer.java
+++ b/usage/launcher/src/main/java/brooklyn/launcher/BrooklynWebServer.java
@@ -400,7 +400,7 @@
         }
 
         try {
-            File tmpWarFile = ResourceUtils.writeToTempFile(new CustomResourceLocator(managementContext.getConfig(), new ResourceUtils(this)).getResourceFromUrl(warUrl), 
+            File tmpWarFile = ResourceUtils.writeToTempFile(new CustomResourceLocator(managementContext.getConfig(), ResourceUtils.create(this)).getResourceFromUrl(warUrl), 
                     isRoot ? "ROOT" : ("embedded-" + cleanPathSpec), ".war");
             context.setWar(tmpWarFile.getAbsolutePath());
         } catch (Exception e) {
diff --git a/usage/rest-server/src/main/java/brooklyn/rest/resources/CatalogResource.java b/usage/rest-server/src/main/java/brooklyn/rest/resources/CatalogResource.java
index 95773c8..f870709 100644
--- a/usage/rest-server/src/main/java/brooklyn/rest/resources/CatalogResource.java
+++ b/usage/rest-server/src/main/java/brooklyn/rest/resources/CatalogResource.java
@@ -125,7 +125,7 @@
             // paths (ie non-protocol) and 
             // NB, for security, file URL's are NOT served
             MediaType mime = WebResourceUtils.getImageMediaTypeFromExtension(Files.getFileExtension(url));
-            Object content = new ResourceUtils(brooklyn().getCatalog().getRootClassLoader()).getResourceFromUrl(url);
+            Object content = ResourceUtils.create(brooklyn().getCatalog().getRootClassLoader()).getResourceFromUrl(url);
             return Response.ok(content, mime).build();
         }
         
diff --git a/usage/rest-server/src/main/java/brooklyn/rest/resources/EntityResource.java b/usage/rest-server/src/main/java/brooklyn/rest/resources/EntityResource.java
index 7993495..628c9b4 100644
--- a/usage/rest-server/src/main/java/brooklyn/rest/resources/EntityResource.java
+++ b/usage/rest-server/src/main/java/brooklyn/rest/resources/EntityResource.java
@@ -84,7 +84,7 @@
           // paths (ie non-protocol) and 
           // NB, for security, file URL's are NOT served
           MediaType mime = WebResourceUtils.getImageMediaTypeFromExtension(Files.getFileExtension(url));
-          Object content = new ResourceUtils(brooklyn().getCatalog().getRootClassLoader()).getResourceFromUrl(url);
+          Object content = ResourceUtils.create(brooklyn().getCatalog().getRootClassLoader()).getResourceFromUrl(url);
           return Response.ok(content, mime).build();
       }