Merge pull request #1119 from richardcloudsoft/brooklynnode-fixes

BrooklynNode, various fixes
diff --git a/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNode.java b/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNode.java
index 2196e34..8a166ac 100644
--- a/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNode.java
+++ b/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNode.java
@@ -18,6 +18,7 @@
 import brooklyn.util.collections.MutableMap;
 import brooklyn.util.flags.SetFromFlag;
 
+import com.google.common.annotations.Beta;
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Function;
 import com.google.common.base.Functions;
@@ -146,8 +147,9 @@
             String.class, "brooklynnode.webconsole.bindAddress", "Specifies the IP address of the NIC to bind the Brooklyn Management Console to", null);
 
     @SetFromFlag("classpath")
-    public static final BasicAttributeSensorAndConfigKey<List<String>> CLASSPATH = new BasicAttributeSensorAndConfigKey(
-            List.class, "brooklynnode.classpath", "classpath to use, as list of URL entries", Lists.newArrayList());
+    @Beta // ideally this should be List<String>, but this will require support for defining lists in brooklyn.properties
+    public static final BasicAttributeSensorAndConfigKey<String> CLASSPATH = new BasicAttributeSensorAndConfigKey(
+            String.class, "brooklynnode.classpath", "classpath to use, as a string of semi-colon separated URL entries", null);
 
     @SetFromFlag("portMapper")
     public static final ConfigKey<Function<? super Integer, ? extends Integer>> PORT_MAPPER = (ConfigKey) ConfigKeys.newConfigKey(Function.class,
diff --git a/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNodeImpl.java b/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNodeImpl.java
index 68d9761..686e08c 100644
--- a/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNodeImpl.java
+++ b/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNodeImpl.java
@@ -3,6 +3,8 @@
 import java.net.URI;
 import java.util.List;
 
+import com.google.common.base.Splitter;
+import com.google.common.collect.ImmutableList;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -40,8 +42,14 @@
         return BrooklynNodeDriver.class;
     }
 
-    public List<String> getClasspath() {
-        return getConfig(CLASSPATH);
+    public Iterable<String> getClasspath() {
+        String classpath = getConfig(CLASSPATH);
+        if (Strings.isBlank(classpath)) {
+            classpath = getManagementContext().getConfig().getConfig(CLASSPATH);
+        }
+        return Strings.isNonBlank(classpath)
+                ? Splitter.on(';').trimResults().omitEmptyStrings().split(classpath)
+                : ImmutableList.<String>of();
     }
     
     protected List<String> getEnabledHttpProtocols() {
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 536ef24..86fee36 100644
--- a/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNodeSshDriver.java
+++ b/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynNodeSshDriver.java
@@ -99,6 +99,10 @@
         newScript(CUSTOMIZING)
                 .failOnNonZeroResultCode()
                 .body.append(
+                        // workaround for AMP distribution placing everything in the root of this archive, but
+                        // brooklyn distribution placing everything in a subdirectory: check to see if subdirectory
+                        // with expect name exists; symlink to same directory if it doesn't
+                        format("[ -d %1$s/brooklyn-%2%s ] || ln -s . %1$s/brooklyn-%2$s", getInstallDir(), getVersion()),
                         format("cp -R %s/brooklyn-%s/{bin,conf} .", getInstallDir(), getVersion()),
                         "mkdir -p ./lib/")
                 .execute();
diff --git a/usage/rest-server/src/main/java/brooklyn/rest/resources/EntityConfigResource.java b/usage/rest-server/src/main/java/brooklyn/rest/resources/EntityConfigResource.java
index a379393..0abf9fd 100644
--- a/usage/rest-server/src/main/java/brooklyn/rest/resources/EntityConfigResource.java
+++ b/usage/rest-server/src/main/java/brooklyn/rest/resources/EntityConfigResource.java
@@ -53,7 +53,7 @@
     EntityLocal entity = brooklyn().getEntity(application, entityToken);
     ConfigKey<?> ck = entity.getEntityType().getConfigKey(configKeyName);
     if (ck==null) ck = new BasicConfigKey<Object>(Object.class, configKeyName);
-    return getValueForDisplay(entity, ((AbstractEntity)entity).getConfigMap().getRawConfig(ck));
+    return getValueForDisplay(entity, entity.getConfig(ck));
   }
 
   private String getValueForDisplay(EntityLocal entity, Object value) {
diff --git a/usage/rest-server/src/test/java/brooklyn/rest/resources/EntityConfigResourceTest.java b/usage/rest-server/src/test/java/brooklyn/rest/resources/EntityConfigResourceTest.java
new file mode 100644
index 0000000..c6e0094
--- /dev/null
+++ b/usage/rest-server/src/test/java/brooklyn/rest/resources/EntityConfigResourceTest.java
@@ -0,0 +1,101 @@
+package brooklyn.rest.resources;
+
+import brooklyn.rest.domain.ApplicationSpec;
+import brooklyn.rest.domain.EntityConfigSummary;
+import brooklyn.rest.domain.EntitySpec;
+import brooklyn.rest.testing.BrooklynRestResourceTest;
+import brooklyn.rest.testing.mocks.RestMockSimpleEntity;
+import com.google.common.base.Optional;
+import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.sun.jersey.api.client.ClientResponse;
+import com.sun.jersey.api.client.GenericType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import javax.annotation.Nullable;
+import java.net.URI;
+import java.util.List;
+import java.util.Map;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertTrue;
+
+@Test(singleThreaded = true)
+public class EntityConfigResourceTest extends BrooklynRestResourceTest {
+    
+    private final static Logger log = LoggerFactory.getLogger(EntityConfigResourceTest.class);
+    private URI application;
+
+    @Override
+    protected void setUpResources() throws Exception {
+        addResources();
+    }
+
+    @BeforeClass
+    @Override
+    public void setUp() throws Exception {
+        super.setUp(); // We require that the superclass setup is done first, as we will be calling out to Jersey
+
+        // Deploy an application that we'll use to read the configuration of
+        final ApplicationSpec simpleSpec = ApplicationSpec.builder().name("simple-app").
+                  entities(ImmutableSet.of(new EntitySpec("simple-ent", RestMockSimpleEntity.class.getName(), ImmutableMap.of("install.version", "1.0.0")))).
+                  locations(ImmutableSet.of("localhost")).
+                  build();
+        
+        ClientResponse response = client().resource("/v1/applications").post(ClientResponse.class, simpleSpec);
+        application = response.getLocation();
+        waitForApplicationToBeRunning(application);
+    }
+
+    @Test
+    public void testList() throws Exception {
+        List<EntityConfigSummary> entityConfigSummaries = client().resource(
+                URI.create("/v1/applications/simple-app/entities/simple-ent/config"))
+                .get(new GenericType<List<EntityConfigSummary>>() {
+                });
+        
+        // Default entities have over a dozen config entries, but it's unnecessary to test them all; just pick one
+        // representative config key
+        Optional<EntityConfigSummary> configKeyOptional = Iterables.tryFind(entityConfigSummaries, new Predicate<EntityConfigSummary>() {
+            @Override
+            public boolean apply(@Nullable EntityConfigSummary input) {
+                return input != null && "install.version".equals(input.getName());
+            }
+        });
+        assertTrue(configKeyOptional.isPresent());
+        
+        assertEquals(configKeyOptional.get().getType(), "java.lang.String");
+        assertEquals(configKeyOptional.get().getDescription(), "Suggested version");
+        assertFalse(configKeyOptional.get().isReconfigurable());
+        assertNull(configKeyOptional.get().getDefaultValue());
+        assertNull(configKeyOptional.get().getLabel());
+        assertNull(configKeyOptional.get().getPriority());
+    }
+
+    @Test
+    public void testBatchConfigRead() throws Exception {
+        Map<String, Object> currentState = client().resource(
+                URI.create("/v1/applications/simple-app/entities/simple-ent/config/current-state"))
+                .get(new GenericType<Map<String, Object>>() {
+                });
+        assertTrue(currentState.containsKey("install.version"));
+        assertEquals(currentState.get("install.version"), "1.0.0");
+    }
+
+    @Test
+    public void testGet() throws Exception {
+        String configValue = client().resource(
+                URI.create("/v1/applications/simple-app/entities/simple-ent/config/install.version"))
+                .get(String.class);
+        assertEquals(configValue, "1.0.0");
+    }
+
+}