You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by he...@apache.org on 2014/11/17 16:23:01 UTC

[1/5] incubator-brooklyn git commit: provide a clean way for management and driver-mapping customizations

Repository: incubator-brooklyn
Updated Branches:
  refs/heads/master 60ccc2cc8 -> cb043e22a


provide a clean way for management and driver-mapping customizations

* custom launchers can customize launch and management
* new driver-naming strategies can be defined and added


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

Branch: refs/heads/master
Commit: 978d8fd7d20965b4647ff32dab500a3615dc2b80
Parents: de1d8bc
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Mon Nov 17 11:03:00 2014 +0000
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Mon Nov 17 11:03:00 2014 +0000

----------------------------------------------------------------------
 .../drivers/BasicEntityDriverManager.java       |   8 +
 .../drivers/ReflectiveEntityDriverFactory.java  | 218 ++++++++++++++++---
 .../ReflectiveEntityDriverFactoryTest.java      |  58 ++++-
 usage/cli/src/main/java/brooklyn/cli/Main.java  |   6 +
 .../brooklyn/launcher/BrooklynLauncher.java     |  12 +
 5 files changed, 262 insertions(+), 40 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/978d8fd7/core/src/main/java/brooklyn/entity/drivers/BasicEntityDriverManager.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/drivers/BasicEntityDriverManager.java b/core/src/main/java/brooklyn/entity/drivers/BasicEntityDriverManager.java
index 6dd92fc..268bee5 100644
--- a/core/src/main/java/brooklyn/entity/drivers/BasicEntityDriverManager.java
+++ b/core/src/main/java/brooklyn/entity/drivers/BasicEntityDriverManager.java
@@ -18,6 +18,8 @@
  */
 package brooklyn.entity.drivers;
 
+import com.google.common.annotations.Beta;
+
 import brooklyn.location.Location;
 
 public class BasicEntityDriverManager implements EntityDriverManager {
@@ -30,6 +32,12 @@ public class BasicEntityDriverManager implements EntityDriverManager {
         reflective = new ReflectiveEntityDriverFactory();
     }
     
+    /** driver override mechanism; experimental @since 0.7.0 */
+    @Beta
+    public ReflectiveEntityDriverFactory getReflectiveDriverFactory() {
+        return reflective;
+    }
+    
     public <D extends EntityDriver> void registerDriver(Class<D> driverInterface, Class<? extends Location> locationClazz, Class<? extends D> driverClazz) {
         registry.registerDriver(driverInterface, locationClazz, driverClazz);
     }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/978d8fd7/core/src/main/java/brooklyn/entity/drivers/ReflectiveEntityDriverFactory.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/drivers/ReflectiveEntityDriverFactory.java b/core/src/main/java/brooklyn/entity/drivers/ReflectiveEntityDriverFactory.java
index c2f19aa..4a52647 100644
--- a/core/src/main/java/brooklyn/entity/drivers/ReflectiveEntityDriverFactory.java
+++ b/core/src/main/java/brooklyn/entity/drivers/ReflectiveEntityDriverFactory.java
@@ -19,70 +19,216 @@
 package brooklyn.entity.drivers;
 
 import java.lang.reflect.Constructor;
-import java.lang.reflect.InvocationTargetException;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import brooklyn.location.Location;
 import brooklyn.location.basic.SshMachineLocation;
-
-import com.google.common.base.Throwables;
+import brooklyn.util.collections.MutableList;
+import brooklyn.util.collections.MutableMap;
+import brooklyn.util.exceptions.Exceptions;
+import brooklyn.util.exceptions.ReferenceWithError;
+import brooklyn.util.text.Strings;
 
 /**
- * Follows a class naming convention: the driver interface must end in "Driver", and the implementation 
+ * Follows a class naming convention: the driver interface typically ends in "Driver", and the implementation 
  * must match the driver interface name but with a suffix like "SshDriver" instead of "Driver".
+ * Other rules can be added using {@link #addRule(String, DriverInferenceRule)} or
+ * {@link #addClassFullNameMapping(String, String)}.
+ * <p>
+ * Reflectively instantiates and returns the driver, based on the location passed in,
+ * in {@link #build(DriverDependentEntity, Location)}.
  * 
- * Reflectively instantiates and returns the driver, based on the location passed in.
- * 
- * @author Peter Veentjer
+ * @author Peter Veentjer, Alex Heneveld
  */
 public class ReflectiveEntityDriverFactory {
 
     private static final Logger LOG = LoggerFactory.getLogger(ReflectiveEntityDriverFactory.class);
 
-    public <D extends EntityDriver> D build(DriverDependentEntity<D> entity, Location location){
-        Class<D> driverInterface = entity.getDriverInterface();
-        Class<? extends D> driverClass;
-        if (driverInterface.isInterface()) {
-            String driverClassName = inferClassName(driverInterface, location);
+    /** Rules, keyed by a unique identifier.  Executed in order of most-recently added first. */
+    protected final Map<String,DriverInferenceRule> rules = MutableMap.of();
+    
+    public ReflectiveEntityDriverFactory() {
+        addRule(DriverInferenceForSshLocation.DEFAULT_IDENTIFIER, new DriverInferenceForSshLocation());
+    }
+    
+    public interface DriverInferenceRule {
+        public <D extends EntityDriver> ReferenceWithError<Class<? extends D>> resolve(DriverDependentEntity<D> entity, Class<D> driverInterface, Location location);
+    }
+
+    public static abstract class AbstractDriverInferenceRule implements DriverInferenceRule {
+
+        @Override
+        public <D extends EntityDriver> ReferenceWithError<Class<? extends D>> resolve(DriverDependentEntity<D> entity, Class<D> driverInterface, Location location) {
             try {
-                driverClass = (Class<? extends D>) entity.getClass().getClassLoader().loadClass(driverClassName);
-            } catch (ClassNotFoundException e) {
-                throw Throwables.propagate(e);
+                String newName = inferDriverClassName(entity, driverInterface, location);
+                if (newName==null) return null;
+
+                return loadDriverClass(newName, entity, driverInterface);
+                
+            } catch (Exception e) {
+                Exceptions.propagateIfFatal(e);
+                return ReferenceWithError.newInstanceThrowingError(null, e);
             }
-        } else {
-            driverClass = driverInterface;
         }
 
-        Constructor<? extends D> constructor = getConstructor(driverClass);
-        try {
-            constructor.setAccessible(true);
-            return constructor.newInstance(entity, location);
-        } catch (InstantiationException e) {
-            throw Throwables.propagate(e);
-        } catch (IllegalAccessException e) {
-            throw Throwables.propagate(e);
-        } catch (InvocationTargetException e) {
-            throw Throwables.propagate(e);
+        public abstract <D extends EntityDriver> String inferDriverClassName(DriverDependentEntity<D> entity, Class<D> driverInterface, Location location);
+
+        protected <D extends EntityDriver> ReferenceWithError<Class<? extends D>> loadDriverClass(String className, DriverDependentEntity<D> entity, Class<D> driverInterface) {
+            ReferenceWithError<Class<? extends D>> r1 = loadClass(className, entity.getClass().getClassLoader());
+            if (!r1.hasError()) return r1;
+            ReferenceWithError<Class<? extends D>> r2 = loadClass(className, driverInterface.getClass().getClassLoader());
+            if (!r2.hasError()) return r2;
+            return r1;
+        }
+
+        @SuppressWarnings({ "unchecked", "rawtypes" })
+        protected <D extends EntityDriver> ReferenceWithError<Class<? extends D>> loadClass(String className, ClassLoader classLoader) {
+            try {
+                return (ReferenceWithError<Class<? extends D>>)(ReferenceWithError) ReferenceWithError.newInstanceWithoutError((Class<? extends EntityDriver>)classLoader.loadClass(className));
+            } catch (Exception e) {
+                Exceptions.propagateIfFatal(e);
+                return ReferenceWithError.newInstanceThrowingError(null, e);
+            }
         }
     }
+    
+    public static abstract class AbstractDriverInferenceRenamingInferenceRule extends AbstractDriverInferenceRule {
+
+        protected final String expectedPattern;
+        protected final String replacement;
 
-    private String inferClassName(Class<? extends EntityDriver> driverInterface, Location location) {
-        String driverInterfaceName = driverInterface.getName();
+        public AbstractDriverInferenceRenamingInferenceRule(String expectedPattern, String replacement) {
+            this.expectedPattern = expectedPattern;
+            this.replacement = replacement;
+        }
         
-        if (location instanceof SshMachineLocation) {
+        public String getIdentifier() {
+            return getClass().getName()+"["+expectedPattern+"]";
+        }
+        
+        @Override
+        public String toString() {
+            return getClass().getName()+"["+expectedPattern+"->"+replacement+"]";
+        }
+    }
+    
+    public static class DriverInferenceByRenamingClassFullName extends AbstractDriverInferenceRenamingInferenceRule {
+
+        public DriverInferenceByRenamingClassFullName(String expectedClassFullName, String newClassFullName) {
+            super(expectedClassFullName, newClassFullName);
+        }
+        
+        @Override
+        public <D extends EntityDriver> String inferDriverClassName(DriverDependentEntity<D> entity, Class<D> driverInterface, Location location) {
+            if (driverInterface.getName().equals(expectedPattern)) {
+                return replacement;
+            }
+            return null;
+        }
+    }
+    
+    public static class DriverInferenceByRenamingClassSimpleName extends AbstractDriverInferenceRenamingInferenceRule {
+
+        public DriverInferenceByRenamingClassSimpleName(String expectedClassSimpleName, String newClassSimpleName) {
+            super(expectedClassSimpleName, newClassSimpleName);
+        }
+        
+        @Override
+        public <D extends EntityDriver> String inferDriverClassName(DriverDependentEntity<D> entity, Class<D> driverInterface, Location location) {
+            if (driverInterface.getSimpleName().equals(expectedPattern)) { 
+                // i'd like to do away with drivers altogether, but if people *really* need to use this and suppress the warning,
+                // they can use the full class rename
+                LOG.warn("Using discouraged driver simple class rename to find "+replacement+" for "+expectedPattern+"; it is recommended to set getDriverInterface() or newDriver() appropriately");
+                return Strings.removeFromEnd(driverInterface.getName(), expectedPattern)+replacement;
+            }
+            return null;
+        }
+    }
+    
+    public static class DriverInferenceForSshLocation extends AbstractDriverInferenceRule {
+
+        public static final String DEFAULT_IDENTIFIER = "ssh-location-driver-inference-rule";
+
+        @Override
+        public <D extends EntityDriver> String inferDriverClassName(DriverDependentEntity<D> entity, Class<D> driverInterface, Location location) {
+            String driverInterfaceName = driverInterface.getName();
+            if (!(location instanceof SshMachineLocation)) return null;
             if (!driverInterfaceName.endsWith("Driver")) {
-                throw new RuntimeException(String.format("Driver name [%s] doesn't end with 'Driver'",driverInterfaceName));
+                throw new IllegalArgumentException(String.format("Driver name [%s] doesn't end with 'Driver'; cannot auto-detect SshDriver class name", driverInterfaceName));
             }
+            return Strings.removeFromEnd(driverInterfaceName, "Driver")+"SshDriver";
+        }
+    }
 
-            return driverInterfaceName.substring(0, driverInterfaceName.length()-"Driver".length())+"SshDriver";
+    /** adds a rule; possibly replacing an old one if one exists with the given identifier. the new rule is added after all previous ones.
+     * @return the replaced rule, or null if there was no old rule */
+    public DriverInferenceRule addRule(String identifier, DriverInferenceRule rule) {
+        DriverInferenceRule oldRule = rules.remove(identifier);
+        rules.put(identifier, rule);
+        LOG.debug("Added driver mapping rule "+rule);
+        return oldRule;
+    }
+    
+    public DriverInferenceRule addClassFullNameMapping(String expectedClassFullName, String newClassFullName) {
+        DriverInferenceByRenamingClassFullName rule = new DriverInferenceByRenamingClassFullName(expectedClassFullName, newClassFullName);
+        return addRule(rule.getIdentifier(), rule);
+    }
+    
+    public DriverInferenceRule addClassSimpleNameMapping(String expectedClassSimpleName, String newClassSimpleName) {
+        DriverInferenceByRenamingClassSimpleName rule = new DriverInferenceByRenamingClassSimpleName(expectedClassSimpleName, newClassSimpleName);
+        return addRule(rule.getIdentifier(), rule);
+    }
+    
+    public <D extends EntityDriver> D build(DriverDependentEntity<D> entity, Location location){
+        Class<D> driverInterface = entity.getDriverInterface();
+        Class<? extends D> driverClass = null;
+        List<Throwable> exceptions = MutableList.of();
+        if (driverInterface.isInterface()) {
+            List<DriverInferenceRule> ruleListInExecutionOrder = MutableList.copyOf(rules.values());
+            Collections.reverse(ruleListInExecutionOrder);
+            // above puts rules in order with most recently added first
+            for (DriverInferenceRule rule: ruleListInExecutionOrder) {
+                ReferenceWithError<Class<? extends D>> clazzR = rule.resolve(entity, driverInterface, location);
+                if (clazzR!=null) {
+                    if (!clazzR.hasError()) {
+                        Class<? extends D> clazz = clazzR.get();
+                        if (clazz!=null) {
+                            driverClass = clazz;
+                            break;
+                        }
+                    } else {
+                        exceptions.add(clazzR.getError());
+                    }
+                }
+            }
         } else {
-            //TODO: Improve
-            throw new RuntimeException("Currently only SshMachineLocation is supported, but location="+location+" for driver +"+driverInterface);
+            driverClass = driverInterface;
+        }
+        LOG.debug("Driver for "+driverInterface.getName()+" in "+location+" is: "+driverClass);
+        
+        if (driverClass==null) {
+            if (exceptions.isEmpty())
+                throw new RuntimeException("No drivers could be found for "+driverInterface.getName()+"; "
+                    + "currently only SshMachineLocation is supported for autodetection (location "+location+")");
+            else throw Exceptions.create("No drivers could be loaded for "+driverInterface.getName()+" in "+location, exceptions);
+        }
+
+        try {
+            Constructor<? extends D> constructor = getConstructor(driverClass);
+            constructor.setAccessible(true);
+            return constructor.newInstance(entity, location);
+        } catch (Exception e) {
+            LOG.warn("Unable to instantiate "+driverClass+" (rethrowing): "+e);
+            throw Exceptions.propagate(e);
         }
     }
     
+    @SuppressWarnings("unchecked")
     private <D extends EntityDriver> Constructor<D> getConstructor(Class<D> driverClass) {
         for (Constructor<?> constructor : driverClass.getConstructors()) {
             if (constructor.getParameterTypes().length == 2) {
@@ -90,7 +236,7 @@ public class ReflectiveEntityDriverFactory {
             }
         }
 
-        //TODO:
-        throw new RuntimeException(String.format("Class [%s] has no constructor with 2 arguments",driverClass.getName()));
+        throw new RuntimeException(String.format("Class [%s] has no constructor with 2 arguments", driverClass.getName()));
     }
+    
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/978d8fd7/core/src/test/java/brooklyn/entity/drivers/ReflectiveEntityDriverFactoryTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/entity/drivers/ReflectiveEntityDriverFactoryTest.java b/core/src/test/java/brooklyn/entity/drivers/ReflectiveEntityDriverFactoryTest.java
index 1eb97ab..a4a80f1 100644
--- a/core/src/test/java/brooklyn/entity/drivers/ReflectiveEntityDriverFactoryTest.java
+++ b/core/src/test/java/brooklyn/entity/drivers/ReflectiveEntityDriverFactoryTest.java
@@ -35,11 +35,13 @@ public class ReflectiveEntityDriverFactoryTest {
 
     private ReflectiveEntityDriverFactory factory;
     private SshMachineLocation sshLocation;
-
+    private DriverDependentEntity<MyDriver> entity;
+    
     @BeforeMethod
     public void setUp() throws Exception {
         factory = new ReflectiveEntityDriverFactory();
         sshLocation = new SshMachineLocation(MutableMap.of("address", "localhost"));
+        entity = new MyDriverDependentEntity<MyDriver>(MyDriver.class);
     }
 
     @AfterMethod
@@ -47,13 +49,55 @@ public class ReflectiveEntityDriverFactoryTest {
         // nothing to tear down; no management context created
     }
 
+    protected void assertDriverIs(Class<?> clazz) {
+        MyDriver driver = factory.build(entity, sshLocation);
+        assertTrue(driver.getClass().equals(clazz), "driver="+driver+"; should be "+clazz);
+    }
+    
     @Test
     public void testInstantiatesSshDriver() throws Exception {
-        DriverDependentEntity<MyDriver> entity = new MyDriverDependentEntity<MyDriver>(MyDriver.class);
-        MyDriver driver = factory.build(entity, sshLocation);
-        assertTrue(driver instanceof MySshDriver, "driver="+driver);
+        assertDriverIs(MySshDriver.class);
+    }
+
+    @Test
+    public void testFullNameMapping() throws Exception {
+        factory.addClassFullNameMapping(MyDriver.class.getName(), MyCustomDriver.class.getName());
+        assertDriverIs(MyCustomDriver.class);
+    }
+
+    @Test
+    public void testFullNameMappingMulti() throws Exception {
+        factory.addClassFullNameMapping(MyDriver.class.getName(), "X");
+        factory.addClassFullNameMapping(MyDriver.class.getName(), MyCustomDriver.class.getName());
+        assertDriverIs(MyCustomDriver.class);
+    }
+
+
+    @Test
+    public void testFullNameMappingFailure1() throws Exception {
+        factory.addClassFullNameMapping(MyDriver.class.getName()+"X", MyCustomDriver.class.getName());
+        assertDriverIs(MySshDriver.class);
+    }
+
+    @Test
+    public void testFullNameMappingFailure2() throws Exception {
+        factory.addClassFullNameMapping(MyDriver.class.getName(), MyCustomDriver.class.getName());
+        factory.addClassFullNameMapping(MyDriver.class.getName(), "X");
+        assertDriverIs(MySshDriver.class);
+    }
+
+    @Test
+    public void testSimpleNameMapping() throws Exception {
+        factory.addClassSimpleNameMapping(MyDriver.class.getSimpleName(), MyCustomDriver.class.getSimpleName());
+        assertDriverIs(MyCustomDriver.class);
     }
 
+    @Test
+    public void testSimpleNameMappingFailure() throws Exception {
+        factory.addClassSimpleNameMapping(MyDriver.class.getSimpleName()+"X", MyCustomDriver.class.getSimpleName());
+        assertDriverIs(MySshDriver.class);
+    }
+    
     public static class MyDriverDependentEntity<D extends EntityDriver> extends AbstractEntity implements DriverDependentEntity<D> {
         private final Class<D> clazz;
 
@@ -89,4 +133,10 @@ public class ReflectiveEntityDriverFactoryTest {
             throw new UnsupportedOperationException();
         }
     }
+    
+    public static class MyCustomDriver extends MySshDriver {
+        public MyCustomDriver(Entity entity, SshMachineLocation machine) {
+            super(entity, machine);
+        }
+    }
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/978d8fd7/usage/cli/src/main/java/brooklyn/cli/Main.java
----------------------------------------------------------------------
diff --git a/usage/cli/src/main/java/brooklyn/cli/Main.java b/usage/cli/src/main/java/brooklyn/cli/Main.java
index 3eb7fc0..8b9480a 100644
--- a/usage/cli/src/main/java/brooklyn/cli/Main.java
+++ b/usage/cli/src/main/java/brooklyn/cli/Main.java
@@ -379,6 +379,8 @@ public class Main extends AbstractMain {
                 
                 computeAndSetApp(launcher, utils, loader);
                 
+                customize(launcher);
+                
             } catch (FatalConfigurationRuntimeException e) {
                 throw e;
             } catch (Exception e) {
@@ -424,6 +426,10 @@ public class Main extends AbstractMain {
             return null;
         }
 
+        /** can be overridden by subclasses which need to customize the launcher and/or management */
+        protected void customize(BrooklynLauncher launcher) {
+        }
+        
         protected void computeLocations() {
             boolean hasLocations = !Strings.isBlank(locations);
             if (app != null) {

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/978d8fd7/usage/launcher/src/main/java/brooklyn/launcher/BrooklynLauncher.java
----------------------------------------------------------------------
diff --git a/usage/launcher/src/main/java/brooklyn/launcher/BrooklynLauncher.java b/usage/launcher/src/main/java/brooklyn/launcher/BrooklynLauncher.java
index 976db4c..64326c9 100644
--- a/usage/launcher/src/main/java/brooklyn/launcher/BrooklynLauncher.java
+++ b/usage/launcher/src/main/java/brooklyn/launcher/BrooklynLauncher.java
@@ -101,6 +101,7 @@ import brooklyn.util.text.Strings;
 import brooklyn.util.time.Duration;
 import brooklyn.util.time.Time;
 
+import com.google.common.base.Function;
 import com.google.common.base.Splitter;
 import com.google.common.base.Stopwatch;
 import com.google.common.collect.ImmutableList;
@@ -155,6 +156,8 @@ public class BrooklynLauncher {
     
     private StopWhichAppsOnShutdown stopWhichAppsOnShutdown = StopWhichAppsOnShutdown.THESE_IF_NOT_PERSISTED;
     
+    private Function<ManagementContext,Void> customizeManagement = null;
+    
     private PersistMode persistMode = PersistMode.DISABLED;
     private HighAvailabilityMode highAvailabilityMode = HighAvailabilityMode.DISABLED;
     private String persistenceDir;
@@ -402,6 +405,11 @@ public class BrooklynLauncher {
         return this;
     }
 
+    public BrooklynLauncher customizeManagement(Function<ManagementContext,Void> customizeManagement) {
+        this.customizeManagement = customizeManagement;
+        return this;
+    }
+
     public BrooklynLauncher shutdownOnExit(boolean val) {
         LOG.warn("Call to deprecated `shutdownOnExit`", new Throwable("source of deprecated call"));
         stopWhichAppsOnShutdown = StopWhichAppsOnShutdown.THESE_IF_NOT_PERSISTED;
@@ -638,6 +646,10 @@ public class BrooklynLauncher {
             brooklynProperties = ((ManagementContextInternal)managementContext).getBrooklynProperties();
             brooklynProperties.addFromMap(brooklynAdditionalProperties);
         }
+        
+        if (customizeManagement!=null) {
+            customizeManagement.apply(managementContext);
+        }
     }
 
     private boolean fileExists(String file) {


[2/5] incubator-brooklyn git commit: fix warning when deleting files

Posted by he...@apache.org.
fix warning when deleting files


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

Branch: refs/heads/master
Commit: bf14bb51c035765bc1cbd8f2b075780b714a3f7c
Parents: 978d8fd
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Mon Nov 17 11:06:00 2014 +0000
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Mon Nov 17 11:07:55 2014 +0000

----------------------------------------------------------------------
 .../rebind/persister/FileBasedStoreObjectAccessor.java   | 11 ++++++++---
 1 file changed, 8 insertions(+), 3 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/bf14bb51/core/src/main/java/brooklyn/entity/rebind/persister/FileBasedStoreObjectAccessor.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/rebind/persister/FileBasedStoreObjectAccessor.java b/core/src/main/java/brooklyn/entity/rebind/persister/FileBasedStoreObjectAccessor.java
index 9a74cca..acaf660 100644
--- a/core/src/main/java/brooklyn/entity/rebind/persister/FileBasedStoreObjectAccessor.java
+++ b/core/src/main/java/brooklyn/entity/rebind/persister/FileBasedStoreObjectAccessor.java
@@ -97,10 +97,15 @@ public class FileBasedStoreObjectAccessor implements PersistenceObjectStore.Stor
     @Override
     public void delete() {
         if (!file.delete()) {
-            LOG.error("Unable to delete " + file.getAbsolutePath() + ". Probably still locked.");
+            if (!file.exists()) {
+                LOG.debug("Unable to delete " + file.getAbsolutePath() + ". Probably did not exist.");
+            } else {
+                LOG.warn("Unable to delete " + file.getAbsolutePath() + ". Probably still locked.");
+            }
         }
-        if (!tmpFile.delete()) {
-            LOG.error("Unable to delete " + tmpFile.getAbsolutePath() + ". Probably still locked.");
+        if (tmpFile.exists() && !tmpFile.delete()) {
+            // tmpFile is probably already deleted, so don't even log debug if it does not exist
+            LOG.warn("Unable to delete " + tmpFile.getAbsolutePath() + ". Probably still locked.");
         }
     }
 


[4/5] incubator-brooklyn git commit: clean up ServerResource export persistence folder

Posted by he...@apache.org.
clean up ServerResource export persistence folder

previously made lots of messy web-persistence-XXX folders under ~/.brooklyn;
now there is a single `tmp` in there


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

Branch: refs/heads/master
Commit: 5a5773e5bb2d94a2e78fd59e33eb12025fc70849
Parents: 3573990
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Mon Nov 17 13:54:35 2014 +0000
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Mon Nov 17 13:57:27 2014 +0000

----------------------------------------------------------------------
 .../brooklyn/rest/resources/ServerResource.java     | 16 +++++++++++++++-
 1 file changed, 15 insertions(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5a5773e5/usage/rest-server/src/main/java/brooklyn/rest/resources/ServerResource.java
----------------------------------------------------------------------
diff --git a/usage/rest-server/src/main/java/brooklyn/rest/resources/ServerResource.java b/usage/rest-server/src/main/java/brooklyn/rest/resources/ServerResource.java
index 2a4a8c5..04424d4 100644
--- a/usage/rest-server/src/main/java/brooklyn/rest/resources/ServerResource.java
+++ b/usage/rest-server/src/main/java/brooklyn/rest/resources/ServerResource.java
@@ -19,6 +19,7 @@
 package brooklyn.rest.resources;
 
 import java.io.ByteArrayOutputStream;
+import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.ArrayList;
@@ -61,6 +62,7 @@ import brooklyn.util.ResourceUtils;
 import brooklyn.util.exceptions.Exceptions;
 import brooklyn.util.file.ArchiveBuilder;
 import brooklyn.util.flags.TypeCoercions;
+import brooklyn.util.os.Os;
 import brooklyn.util.text.Identifiers;
 import brooklyn.util.text.Strings;
 import brooklyn.util.time.CountdownTimer;
@@ -303,20 +305,32 @@ public class ServerResource extends AbstractBrooklynRestResource implements Serv
         if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_ALL_SERVER_INFO, null))
             throw WebResourceUtils.unauthorized("User '%s' is not authorized for this operation", Entitlements.getEntitlementContext().user());
 
+        File dir = null;
         try {
             String label = mgmt().getManagementNodeId()+"-"+Time.makeDateSimpleStampString();
             PersistenceObjectStore targetStore = BrooklynPersistenceUtils.newPersistenceObjectStore(mgmt(), null, 
-                "web-persistence-"+label+"-"+Identifiers.makeRandomId(4));
+                "tmp/web-persistence-"+label+"-"+Identifiers.makeRandomId(4));
+            dir = ((FileBasedObjectStore)targetStore).getBaseDir();
+            // only register the parent dir because that will prevent leaks for the random ID
+            Os.deleteOnExitEmptyParentsUpTo(dir.getParentFile(), dir.getParentFile());
             BrooklynPersistenceUtils.writeMemento(mgmt(), targetStore, preferredOrigin);            
             
             ByteArrayOutputStream baos = new ByteArrayOutputStream();
             ArchiveBuilder.zip().addDirContentsAt( ((FileBasedObjectStore)targetStore).getBaseDir(), ((FileBasedObjectStore)targetStore).getBaseDir().getName() ).stream(baos);
+            Os.deleteRecursively(dir);
             String filename = "brooklyn-state-"+label+".zip";
             return Response.ok(baos.toByteArray(), MediaType.APPLICATION_OCTET_STREAM_TYPE)
                 .header("Content-Disposition","attachment; filename = "+filename)
                 .build();
         } catch (Exception e) {
             log.warn("Unable to serve persistence data (rethrowing): "+e, e);
+            if (dir!=null) {
+                try {
+                    Os.deleteRecursively(dir);
+                } catch (Exception e2) {
+                    log.warn("Ignoring error deleting '"+dir+"' after another error, throwing original error ("+e+"); ignored error deleting is: "+e2);
+                }
+            }
             throw Exceptions.propagate(e);
         }
     }


[3/5] incubator-brooklyn git commit: support config key for osgi cache dir, which now defaults to under mgmt base dir

Posted by he...@apache.org.
support config key for osgi cache dir, which now defaults to under mgmt base dir

running in /tmp is not recommended. we attempt as best we can to delete it if it looks transient,
but on `kill -9` there may be junk leftover under osgi/cache/


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

Branch: refs/heads/master
Commit: 3573990142b8e045b95ec7f219576c5145b5a97b
Parents: bf14bb5
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Mon Nov 17 13:44:23 2014 +0000
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Mon Nov 17 13:44:23 2014 +0000

----------------------------------------------------------------------
 .../brooklyn/config/BrooklynServerConfig.java   |  12 ++
 .../brooklyn/config/BrooklynServerPaths.java    |  43 +++++-
 .../brooklyn/management/ha/OsgiManager.java     |  29 ++--
 .../internal/LocalManagementContext.java        |   2 +-
 .../brooklyn/util/text/TemplateProcessor.java   | 138 +++++++++++++++++--
 .../brooklyn/management/osgi/OsgiPathTest.java  | 105 ++++++++++++++
 .../util/text/TemplateProcessorTest.java        |  40 ++++--
 7 files changed, 334 insertions(+), 35 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/35739901/core/src/main/java/brooklyn/config/BrooklynServerConfig.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/config/BrooklynServerConfig.java b/core/src/main/java/brooklyn/config/BrooklynServerConfig.java
index 4fdf4c4..621dc10 100644
--- a/core/src/main/java/brooklyn/config/BrooklynServerConfig.java
+++ b/core/src/main/java/brooklyn/config/BrooklynServerConfig.java
@@ -109,8 +109,19 @@ public class BrooklynServerConfig {
             "The mode the management context should use to load the catalog when first starting",
             CatalogLoadMode.LOAD_BROOKLYN_CATALOG_URL);
 
+    /** string used in places where the management node ID is needed to resolve a path */
+    public static final String MANAGEMENT_NODE_ID_PROPERTY = "brooklyn.mgmt.node.id";
+    
     public static final ConfigKey<Boolean> USE_OSGI = ConfigKeys.newBooleanConfigKey("brooklyn.osgi.enabled",
         "Whether OSGi is enabled, defaulting to true", true);
+    public static final ConfigKey<String> OSGI_CACHE_DIR = ConfigKeys.newStringConfigKey("brooklyn.osgi.cache.dir",
+        "Directory to use for OSGi cache, potentially including Freemarker template variables "
+        + "${"+MGMT_BASE_DIR.getName()+"} (which is the default for relative paths), "
+        + "${"+Os.TmpDirFinder.BROOKLYN_OS_TMPDIR_PROPERTY+"} if it should be in the tmp dir space,  "
+        + "and ${"+MANAGEMENT_NODE_ID_PROPERTY+"} to include the management node ID (recommended if running multiple OSGi paths)",
+        "osgi/cache/${"+MANAGEMENT_NODE_ID_PROPERTY+"}/");
+    public static final ConfigKey<Boolean> OSGI_CACHE_CLEAN = ConfigKeys.newBooleanConfigKey("brooklyn.osgi.cache.clean",
+        "Whether to delete the OSGi directory before and after use; if unset, it will delete if the node ID forms part of the cache dir path (which by default it does) to avoid file leaks");
 
     public static final ConfigKey<CampPlatform> CAMP_PLATFORM = ConfigKeys.newConfigKey(CampPlatform.class, "brooklyn.camp.platform",
         "Config set at brooklyn management platform to find the CampPlatform instance (bi-directional)");
@@ -118,6 +129,7 @@ public class BrooklynServerConfig {
     public static final AttributeSensor<ManagementContext.PropertiesReloadListener> PROPERTIES_RELOAD_LISTENER = Sensors.newSensor(
             ManagementContext.PropertiesReloadListener.class, "brooklyn.management.propertiesReloadListenet", "Properties reload listener");
 
+
     /** @see BrooklynServerPaths#getMgmtBaseDir(ManagementContext) */
     public static String getMgmtBaseDir(ManagementContext mgmt) {
         return BrooklynServerPaths.getMgmtBaseDir(mgmt);

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/35739901/core/src/main/java/brooklyn/config/BrooklynServerPaths.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/config/BrooklynServerPaths.java b/core/src/main/java/brooklyn/config/BrooklynServerPaths.java
index 06c8033..ae56d96 100644
--- a/core/src/main/java/brooklyn/config/BrooklynServerPaths.java
+++ b/core/src/main/java/brooklyn/config/BrooklynServerPaths.java
@@ -29,12 +29,14 @@ import org.slf4j.LoggerFactory;
 
 import brooklyn.management.ManagementContext;
 import brooklyn.management.internal.ManagementContextInternal;
+import brooklyn.util.collections.MutableMap;
 import brooklyn.util.exceptions.Exceptions;
 import brooklyn.util.guava.Maybe;
 import brooklyn.util.net.Urls;
 import brooklyn.util.os.Os;
 import brooklyn.util.text.Identifiers;
 import brooklyn.util.text.Strings;
+import brooklyn.util.text.TemplateProcessor;
 import brooklyn.util.time.Time;
 
 import com.google.common.base.Objects;
@@ -234,6 +236,45 @@ public class BrooklynServerPaths {
         }
     }
 
-    // TODO OSGi
+    public static File getOsgiCacheDir(ManagementContext mgmt) {
+        StringConfigMap brooklynProperties = mgmt.getConfig();
+        String cacheDir = brooklynProperties.getConfig(BrooklynServerConfig.OSGI_CACHE_DIR);
+        
+        // note dir should be different for each instance if starting multiple instances
+        // hence default including management node ID
+        
+        cacheDir = TemplateProcessor.processTemplateContents(cacheDir, (ManagementContextInternal)mgmt, 
+            MutableMap.of(BrooklynServerConfig.MGMT_BASE_DIR.getName(), getMgmtBaseDir(mgmt),
+                BrooklynServerConfig.MANAGEMENT_NODE_ID_PROPERTY, mgmt.getManagementNodeId(),
+                Os.TmpDirFinder.BROOKLYN_OS_TMPDIR_PROPERTY, Os.tmp()));
+        cacheDir = resolveAgainstBaseDir(mgmt.getConfig(), cacheDir);
+        
+        return new File(cacheDir);
+    }
+
+    public static boolean isOsgiCacheForCleaning(ManagementContext mgmt, File cacheDir) {
+        StringConfigMap brooklynProperties = mgmt.getConfig();
+        Boolean clean = brooklynProperties.getConfig(BrooklynServerConfig.OSGI_CACHE_CLEAN);
+        if (clean==null) {
+            // as per javadoc on key, clean defaults to iff it is a node-id-specific directory
+            clean = cacheDir.getName().contains(mgmt.getManagementNodeId());
+        }
+        return clean;
+    }
     
+    public static File getOsgiCacheDirCleanedIfNeeded(ManagementContext mgmt) {
+        File cacheDirF = getOsgiCacheDir(mgmt);
+        boolean clean = isOsgiCacheForCleaning(mgmt, cacheDirF);
+        log.debug("OSGi cache dir computed as "+cacheDirF.getName()+" ("+
+            (cacheDirF.exists() ? "already exists" : "does not exist")+", "+
+            (clean ? "cleaning now (and on exit)" : "cleaning not requested"));
+
+        if (clean) {
+            Os.deleteRecursively(cacheDirF);
+            Os.deleteOnExitRecursively(cacheDirF);
+        }
+        
+        return cacheDirF;
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/35739901/core/src/main/java/brooklyn/management/ha/OsgiManager.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/management/ha/OsgiManager.java b/core/src/main/java/brooklyn/management/ha/OsgiManager.java
index 1042d1f..386af39 100644
--- a/core/src/main/java/brooklyn/management/ha/OsgiManager.java
+++ b/core/src/main/java/brooklyn/management/ha/OsgiManager.java
@@ -32,7 +32,9 @@ import org.slf4j.LoggerFactory;
 
 import brooklyn.catalog.CatalogItem.CatalogBundle;
 import brooklyn.config.BrooklynServerConfig;
+import brooklyn.config.BrooklynServerPaths;
 import brooklyn.config.ConfigKey;
+import brooklyn.management.ManagementContext;
 import brooklyn.util.collections.MutableMap;
 import brooklyn.util.exceptions.Exceptions;
 import brooklyn.util.guava.Maybe;
@@ -53,19 +55,24 @@ public class OsgiManager {
     
     /* see Osgis for info on starting framework etc */
     
+    protected ManagementContext mgmt;
     protected Framework framework;
-    protected File osgiTempDir;
+    protected File osgiCacheDir;
 
     // we could manage without this map but it is useful to validate what is a user-supplied url
     protected Map<String,VersionedName> urlToBundleIdentifier = MutableMap.of();
 
+
+    public OsgiManager(ManagementContext mgmt) {
+        this.mgmt = mgmt;
+    }
+
     public void start() {
         try {
-            // TODO any extra startup args?
-            // TODO dir to come from brooklyn properties;
-            // note dir must be different for each if starting multiple instances
-            osgiTempDir = Os.newTempDir("brooklyn-osgi-cache");
-            framework = Osgis.newFrameworkStarted(osgiTempDir.getAbsolutePath(), false, MutableMap.of());
+            osgiCacheDir = BrooklynServerPaths.getOsgiCacheDirCleanedIfNeeded(mgmt);
+            
+            // any extra OSGi startup args could go here
+            framework = Osgis.newFrameworkStarted(osgiCacheDir.getAbsolutePath(), false, MutableMap.of());
             
         } catch (Exception e) {
             throw Exceptions.propagate(e);
@@ -83,11 +90,13 @@ public class OsgiManager {
         } catch (InterruptedException e) {
             throw Exceptions.propagate(e);
         }
-        DeletionResult deleteRecursively = Os.deleteRecursively(osgiTempDir);
-        if (deleteRecursively.getThrowable()!=null) {
-            log.debug("Unable to delete "+osgiTempDir+" (possibly already deleted?): "+deleteRecursively.getThrowable());
+        if (BrooklynServerPaths.isOsgiCacheForCleaning(mgmt, osgiCacheDir)) {
+            DeletionResult deleteRecursively = Os.deleteRecursively(osgiCacheDir);
+            if (deleteRecursively.getThrowable()!=null) {
+                log.debug("Unable to delete "+osgiCacheDir+" (possibly already deleted?): "+deleteRecursively.getThrowable());
+            }
         }
-        osgiTempDir = null;
+        osgiCacheDir = null;
         framework = null;
     }
 

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/35739901/core/src/main/java/brooklyn/management/internal/LocalManagementContext.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/management/internal/LocalManagementContext.java b/core/src/main/java/brooklyn/management/internal/LocalManagementContext.java
index 098e3af..257e1f6 100644
--- a/core/src/main/java/brooklyn/management/internal/LocalManagementContext.java
+++ b/core/src/main/java/brooklyn/management/internal/LocalManagementContext.java
@@ -186,7 +186,7 @@ public class LocalManagementContext extends AbstractManagementContext {
         this.usageManager = new LocalUsageManager(this);
         
         if (configMap.getConfig(OsgiManager.USE_OSGI)) {
-            this.osgiManager = new OsgiManager();
+            this.osgiManager = new OsgiManager(this);
             osgiManager.start();
         }
         

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/35739901/core/src/main/java/brooklyn/util/text/TemplateProcessor.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/util/text/TemplateProcessor.java b/core/src/main/java/brooklyn/util/text/TemplateProcessor.java
index 25554a7..f6c35c4 100644
--- a/core/src/main/java/brooklyn/util/text/TemplateProcessor.java
+++ b/core/src/main/java/brooklyn/util/text/TemplateProcessor.java
@@ -28,11 +28,14 @@ import java.util.Map;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import brooklyn.entity.Entity;
 import brooklyn.entity.basic.ConfigKeys;
 import brooklyn.entity.basic.EntityInternal;
 import brooklyn.entity.drivers.EntityDriver;
+import brooklyn.location.Location;
 import brooklyn.management.ManagementContext;
 import brooklyn.management.internal.ManagementContextInternal;
+import brooklyn.util.collections.MutableMap;
 import brooklyn.util.exceptions.Exceptions;
 
 import com.google.common.base.Charsets;
@@ -47,11 +50,30 @@ import freemarker.template.TemplateHashModel;
 import freemarker.template.TemplateModel;
 import freemarker.template.TemplateModelException;
 
+/** A variety of methods to assist in Freemarker template processing,
+ * including passing in maps with keys flattened (dot-separated namespace),
+ * and accessing {@link ManagementContext} brooklyn.properties 
+ * and {@link Entity}, {@link EntityDriver}, and {@link Location} methods and config.
+ * <p>
+ * See {@link #processTemplateContents(String, ManagementContextInternal, Map)} for
+ * a description of how management access is done.
+ */
 public class TemplateProcessor {
 
     private static final Logger log = LoggerFactory.getLogger(TemplateProcessor.class);
 
+    protected static TemplateModel wrapAsTemplateModel(Object o) throws TemplateModelException {
+        if (o instanceof Map) return new DotSplittingTemplateModel((Map<?,?>)o);
+        return ObjectWrapper.DEFAULT_WRAPPER.wrap(o);
+    }
+    
+    /** @deprecated since 0.7.0 use {@link #processTemplateFile(String, Map)} */ @Deprecated
     public static String processTemplate(String templateFileName, Map<String, ? extends Object> substitutions) {
+        return processTemplateFile(templateFileName, substitutions);
+    }
+    
+    /** As per {@link #processTemplateContents(String, Map)}, but taking a file. */
+    public static String processTemplateFile(String templateFileName, Map<String, ? extends Object> substitutions) {
         String templateContents;
         try {
             templateContents = Files.toString(new File(templateFileName), Charsets.UTF_8);
@@ -62,7 +84,13 @@ public class TemplateProcessor {
         return processTemplateContents(templateContents, substitutions);
     }
 
+    /** @deprecated since 0.7.0 use {@link #processTemplateFile(String, EntityDriver, Map)} */ @Deprecated
     public static String processTemplate(String templateFileName, EntityDriver driver, Map<String, ? extends Object> extraSubstitutions) {
+        return processTemplateFile(templateFileName, driver, extraSubstitutions);
+    }
+    
+    /** Processes template contents according to {@link EntityAndMapTemplateModel}. */
+    public static String processTemplateFile(String templateFileName, EntityDriver driver, Map<String, ? extends Object> extraSubstitutions) {
         String templateContents;
         try {
             templateContents = Files.toString(new File(templateFileName), Charsets.UTF_8);
@@ -73,14 +101,85 @@ public class TemplateProcessor {
         return processTemplateContents(templateContents, driver, extraSubstitutions);
     }
 
+    /** Processes template contents according to {@link EntityAndMapTemplateModel}. */
     public static String processTemplateContents(String templateContents, EntityDriver driver, Map<String,? extends Object> extraSubstitutions) {
         return processTemplateContents(templateContents, new EntityAndMapTemplateModel(driver, extraSubstitutions));
     }
 
+    /** Processes template contents according to {@link EntityAndMapTemplateModel}. */
     public static String processTemplateContents(String templateContents, ManagementContextInternal managementContext, Map<String,? extends Object> extraSubstitutions) {
         return processTemplateContents(templateContents, new EntityAndMapTemplateModel(managementContext, extraSubstitutions));
     }
 
+    /**
+     * A Freemarker {@link TemplateHashModel} which will correctly handle entries of the form "a.b" in this map,
+     * matching against template requests for "${a.b}".
+     * <p>
+     * Freemarker requests "a" in a map when given such a request, and expects that to point to a map
+     * with a key "b". This model provides such maps even for "a.b" in a map.
+     * <p>
+     * However if "a" <b>and</b> "a.b" are in the map, this will <b>not</b> currently do the deep mapping.
+     * (It does not have enough contextual information from Freemarker to handle this case.) */
+    public static final class DotSplittingTemplateModel implements TemplateHashModel {
+        protected final Map<?,?> map;
+
+        protected DotSplittingTemplateModel(Map<?,?> map) {
+            this.map = map;
+        }
+
+        @Override
+        public boolean isEmpty() { return map!=null && map.isEmpty(); }
+
+        public boolean contains(String key) {
+            if (map==null) return false;
+            if (map.containsKey(key)) return true;
+            for (Map.Entry<?,?> entry: map.entrySet()) {
+                String k = Strings.toString(entry.getKey());
+                if (k.startsWith(key+".")) {
+                    // contains this prefix
+                    return true;
+                }
+            }
+            return false;
+        }
+        
+        @Override
+        public TemplateModel get(String key) throws TemplateModelException {
+            if (map==null) return null;
+            try {
+                if (map.containsKey(key)) 
+                    return wrapAsTemplateModel( map.get(key) );
+                
+                Map<String,Object> result = MutableMap.of();
+                for (Map.Entry<?,?> entry: map.entrySet()) {
+                    String k = Strings.toString(entry.getKey());
+                    if (k.startsWith(key+".")) {
+                        String k2 = Strings.removeFromStart(k, key+".");
+                        result.put(k2, entry.getValue());
+                    }
+                }
+                if (!result.isEmpty()) 
+                        return wrapAsTemplateModel( result );
+                
+            } catch (Exception e) {
+                Exceptions.propagateIfFatal(e);
+                throw new IllegalStateException("Error accessing config '"+key+"'"+": "+e, e);
+            }
+            
+            return null;
+        }
+        
+        @Override
+        public String toString() {
+            return getClass().getName()+"["+map+"]";
+        }
+    }
+    
+    /** FreeMarker {@link TemplateHashModel} which resolves keys inside the given entity or management context.
+     * Callers are required to include dots for dot-separated keys.
+     * Freemarker will only due this when in inside bracket notation in an outer map, as in <code>${outer['a.b.']}</code>; 
+     * as a result this is intended only for use by {@link EntityAndMapTemplateModel} where 
+     * a caller has used bracked notation, as in <code>${mgmt['key.subkey']}</code>. */
     protected static final class EntityConfigTemplateModel implements TemplateHashModel {
         protected final EntityInternal entity;
         protected final ManagementContext mgmt;
@@ -109,7 +208,7 @@ public class TemplateProcessor {
                     result = mgmt.getConfig().getConfig(ConfigKeys.builder(Object.class).name(key).build());
                 
                 if (result!=null)
-                    return ObjectWrapper.DEFAULT_WRAPPER.wrap( result );
+                    return wrapAsTemplateModel( result );
                 
             } catch (Exception e) {
                 Exceptions.propagateIfFatal(e);
@@ -126,31 +225,38 @@ public class TemplateProcessor {
         }
     }
 
+    /**
+     * Provides access to config on an entity or management context, using
+     * <code>${config['entity.config.key']}</code> or <code>${mgmt['brooklyn.properties.key']}</code> notation,
+     * and also allowing access to <code>getX()</code> methods on entity (interface) or driver
+     * using <code>${entity.x}</code> or <code><${driver.x}</code>.
+     * Optional extra properties can be supplied, treated as per {@link DotSplittingTemplateModel}.
+     */
     protected static final class EntityAndMapTemplateModel implements TemplateHashModel {
         protected final EntityInternal entity;
         protected final EntityDriver driver;
         protected final ManagementContext mgmt;
-        protected final Map<String,? extends Object> extraSubstitutions;
+        protected final DotSplittingTemplateModel extraSubstitutionsModel;
 
         protected EntityAndMapTemplateModel(ManagementContext mgmt, Map<String,? extends Object> extraSubstitutions) {
             this.entity = null;
             this.driver = null;
             this.mgmt = mgmt;
-            this.extraSubstitutions = extraSubstitutions;
+            this.extraSubstitutionsModel = new DotSplittingTemplateModel(extraSubstitutions);
         }
 
         protected EntityAndMapTemplateModel(EntityDriver driver, Map<String,? extends Object> extraSubstitutions) {
             this.driver = driver;
             this.entity = (EntityInternal) driver.getEntity();
             this.mgmt = entity.getManagementContext();
-            this.extraSubstitutions = extraSubstitutions;
+            this.extraSubstitutionsModel = new DotSplittingTemplateModel(extraSubstitutions);
         }
 
         protected EntityAndMapTemplateModel(EntityInternal entity, Map<String,? extends Object> extraSubstitutions) {
             this.entity = entity;
             this.driver = null;
             this.mgmt = entity.getManagementContext();
-            this.extraSubstitutions = extraSubstitutions;
+            this.extraSubstitutionsModel = new DotSplittingTemplateModel(extraSubstitutions);
         }
 
         @Override
@@ -158,11 +264,11 @@ public class TemplateProcessor {
 
         @Override
         public TemplateModel get(String key) throws TemplateModelException {
-            if (extraSubstitutions!=null && extraSubstitutions.containsKey(key))
-                return ObjectWrapper.DEFAULT_WRAPPER.wrap( extraSubstitutions.get(key) );
+            if (extraSubstitutionsModel.contains(key))
+                return wrapAsTemplateModel( extraSubstitutionsModel.get(key) );
 
             if ("entity".equals(key) && entity!=null)
-                return ObjectWrapper.DEFAULT_WRAPPER.wrap( entity );
+                return wrapAsTemplateModel( entity );
             if ("config".equals(key)) {
                 if (entity!=null)
                     return new EntityConfigTemplateModel(entity);
@@ -174,12 +280,12 @@ public class TemplateProcessor {
             }
 
             if ("driver".equals(key) && driver!=null)
-                return ObjectWrapper.DEFAULT_WRAPPER.wrap( driver );
+                return wrapAsTemplateModel( driver );
             if ("location".equals(key)) {
                 if (driver!=null && driver.getLocation()!=null)
-                    return ObjectWrapper.DEFAULT_WRAPPER.wrap( driver.getLocation() );
+                    return wrapAsTemplateModel( driver.getLocation() );
                 if (entity!=null)
-                    return ObjectWrapper.DEFAULT_WRAPPER.wrap( Iterables.getOnlyElement( entity.getLocations() ) );
+                    return wrapAsTemplateModel( Iterables.getOnlyElement( entity.getLocations() ) );
             }
             
             if (mgmt!=null) {
@@ -188,12 +294,12 @@ public class TemplateProcessor {
                 Object result = mgmt.getConfig().getConfig(ConfigKeys.builder(Object.class).name(key).build());
                 if (result!=null) { 
                     log.warn("Deprecated access of global brooklyn.properties value for "+key+"; should be qualified with 'mgmt.'");
-                    return ObjectWrapper.DEFAULT_WRAPPER.wrap( result );
+                    return wrapAsTemplateModel( result );
                 }
             }
             
             if ("javaSysProps".equals(key))
-                return ObjectWrapper.DEFAULT_WRAPPER.wrap( System.getProperties() );
+                return wrapAsTemplateModel( System.getProperties() );
 
             return null;
         }
@@ -204,15 +310,18 @@ public class TemplateProcessor {
         }
     }
 
+    /** Processes template contents with the given items in scope as per {@link EntityAndMapTemplateModel}. */
     public static String processTemplateContents(String templateContents, final EntityInternal entity, Map<String,? extends Object> extraSubstitutions) {
         return processTemplateContents(templateContents, new EntityAndMapTemplateModel(entity, extraSubstitutions));
     }
     
+    /** Processes template contents using the given map, passed to freemarker,
+     * with dot handling as per {@link DotSplittingTemplateModel}. */
     public static String processTemplateContents(String templateContents, final Map<String, ? extends Object> substitutions) {
         TemplateHashModel root;
         try {
             root = substitutions != null
-                ? (TemplateHashModel)ObjectWrapper.DEFAULT_WRAPPER.wrap(substitutions)
+                ? (TemplateHashModel)wrapAsTemplateModel(substitutions)
                 : null;
         } catch (TemplateModelException e) {
             throw new IllegalStateException("Unable to set up TemplateHashModel to parse template, given "+substitutions+": "+e, e);
@@ -221,6 +330,7 @@ public class TemplateProcessor {
         return processTemplateContents(templateContents, root);
     }
     
+    /** Processes template contents against the given {@link TemplateHashModel}. */
     public static String processTemplateContents(String templateContents, final TemplateHashModel substitutions) {
         try {
             Configuration cfg = new Configuration();

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/35739901/core/src/test/java/brooklyn/management/osgi/OsgiPathTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/management/osgi/OsgiPathTest.java b/core/src/test/java/brooklyn/management/osgi/OsgiPathTest.java
new file mode 100644
index 0000000..f4ac674
--- /dev/null
+++ b/core/src/test/java/brooklyn/management/osgi/OsgiPathTest.java
@@ -0,0 +1,105 @@
+/*
+ * 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 brooklyn.management.osgi;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.osgi.framework.BundleException;
+import org.testng.Assert;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.Test;
+
+import brooklyn.config.BrooklynProperties;
+import brooklyn.config.BrooklynServerConfig;
+import brooklyn.config.BrooklynServerPaths;
+import brooklyn.entity.basic.Entities;
+import brooklyn.management.internal.LocalManagementContext;
+import brooklyn.test.entity.LocalManagementContextForTests;
+import brooklyn.util.os.Os;
+import brooklyn.util.text.Identifiers;
+
+
+/** 
+ * Tests that OSGi entities load correctly and have the right catalog information set.
+ * Note further tests done elsewhere using CAMP YAML (referring to methods in this class).
+ */
+public class OsgiPathTest {
+   
+    protected LocalManagementContext mgmt = null;
+
+    @AfterMethod(alwaysRun=true)
+    public void tearDown() throws BundleException, IOException, InterruptedException {
+        if (mgmt!=null) Entities.destroyAll(mgmt);
+    }
+    
+    @Test(groups="Integration") // integration only because OSGi takes ~200ms
+    public void testOsgiPathDefault() {
+        mgmt = LocalManagementContextForTests.builder(true).disableOsgi(false).build();
+        String path = BrooklynServerPaths.getOsgiCacheDir(mgmt).getAbsolutePath();
+        Assert.assertTrue(path.startsWith(BrooklynServerPaths.getMgmtBaseDir(mgmt)), path);
+        Assert.assertTrue(path.contains(mgmt.getManagementNodeId()), path);
+        
+        assertExistsThenIsCleaned(path);
+    }
+
+    @Test(groups="Integration") // integration only because OSGi takes ~200ms
+    public void testOsgiPathCustom() {
+        BrooklynProperties bp = BrooklynProperties.Factory.newEmpty();
+        String randomSeg = "osgi-test-"+Identifiers.makeRandomId(4);
+        bp.put(BrooklynServerConfig.OSGI_CACHE_DIR, "${brooklyn.os.tmpdir}"+"/"+randomSeg+"/"+"${brooklyn.mgmt.node.id}");
+        mgmt = LocalManagementContextForTests.builder(true).disableOsgi(false).useProperties(bp).build();
+        String path = BrooklynServerPaths.getOsgiCacheDir(mgmt).getAbsolutePath();
+        Os.deleteOnExitRecursivelyAndEmptyParentsUpTo(new File(path), new File(Os.tmp()+"/"+randomSeg));
+        
+        Assert.assertTrue(path.startsWith(Os.tmp()), path);
+        Assert.assertTrue(path.contains(mgmt.getManagementNodeId()), path);
+        
+        assertExistsThenIsCleaned(path);
+    }
+
+    @Test(groups="Integration") // integration only because OSGi takes ~200ms
+    public void testOsgiPathCustomWithoutNodeIdNotCleaned() {
+        BrooklynProperties bp = BrooklynProperties.Factory.newEmpty();
+        String randomSeg = "osgi-test-"+Identifiers.makeRandomId(4);
+        bp.put(BrooklynServerConfig.OSGI_CACHE_DIR, "${brooklyn.os.tmpdir}"+"/"+randomSeg+"/"+"sample");
+        mgmt = LocalManagementContextForTests.builder(true).disableOsgi(false).useProperties(bp).build();
+        String path = BrooklynServerPaths.getOsgiCacheDir(mgmt).getAbsolutePath();
+        Os.deleteOnExitRecursivelyAndEmptyParentsUpTo(new File(path), new File(Os.tmp()+"/"+randomSeg));
+        
+        Assert.assertTrue(path.startsWith(Os.tmp()), path);
+        Assert.assertFalse(path.contains(mgmt.getManagementNodeId()), path);
+        
+        assertExistsThenCorrectCleanedBehaviour(path, false);
+    }
+
+    private void assertExistsThenIsCleaned(String path) {
+        assertExistsThenCorrectCleanedBehaviour(path, true);
+    }
+    private void assertExistsThenCorrectCleanedBehaviour(String path, boolean shouldBeCleanAfterwards) {
+        Assert.assertTrue(new File(path).exists(), "OSGi cache "+path+" should exist when in use");
+        Entities.destroyAll(mgmt);
+        mgmt = null;
+        if (shouldBeCleanAfterwards)
+            Assert.assertFalse(new File(path).exists(), "OSGi cache "+path+" should be cleaned after");
+        else
+            Assert.assertTrue(new File(path).exists(), "OSGi cache "+path+" should NOT be cleaned after");
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/35739901/core/src/test/java/brooklyn/util/text/TemplateProcessorTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/util/text/TemplateProcessorTest.java b/core/src/test/java/brooklyn/util/text/TemplateProcessorTest.java
index ef9c41a..030e8c6 100644
--- a/core/src/test/java/brooklyn/util/text/TemplateProcessorTest.java
+++ b/core/src/test/java/brooklyn/util/text/TemplateProcessorTest.java
@@ -28,6 +28,7 @@ import org.testng.annotations.Test;
 import brooklyn.entity.BrooklynAppUnitTestSupport;
 import brooklyn.entity.proxying.EntitySpec;
 import brooklyn.event.basic.DependentConfiguration;
+import brooklyn.management.internal.ManagementContextInternal;
 import brooklyn.test.FixedLocaleTest;
 import brooklyn.test.entity.TestApplication;
 import brooklyn.test.entity.TestEntity;
@@ -109,14 +110,6 @@ public class TemplateProcessorTest extends BrooklynAppUnitTestSupport {
     }
     
     @Test
-    public void testManagementContextConfigWithDot() {
-        mgmt.getBrooklynProperties().put("global.mykey", "myval");
-        String templateContents = "${mgmt['global.mykey']}";
-        String result = TemplateProcessor.processTemplateContents(templateContents, app, ImmutableMap.<String,Object>of());
-        assertEquals(result, "myval");
-    }
-    
-    @Test
     public void testManagementContextDefaultValue() {
         String templateContents = "${(missing)!\"defval\"}";
         Object result = TemplateProcessor.processTemplateContents(templateContents, app, ImmutableMap.<String,Object>of());
@@ -131,9 +124,17 @@ public class TemplateProcessorTest extends BrooklynAppUnitTestSupport {
     }
     
     @Test
+    public void testManagementContextConfigWithDot() {
+        mgmt.getBrooklynProperties().put("global.mykey", "myval");
+        String templateContents = "${mgmt['global.mykey']}";
+        String result = TemplateProcessor.processTemplateContents(templateContents, app, ImmutableMap.<String,Object>of());
+        assertEquals(result, "myval");
+    }
+    
+    @Test
     public void testManagementContextErrors() {
         try {
-            // NB: dot has special meaning so this should fail
+            // NB: dot has special meaning so this should fail; must be accessed using bracket notation as above
             mgmt.getBrooklynProperties().put("global.mykey", "myval");
             String templateContents = "${mgmt.global.mykey}";
             TemplateProcessor.processTemplateContents(templateContents, app, ImmutableMap.<String,Object>of());
@@ -154,4 +155,25 @@ public class TemplateProcessorTest extends BrooklynAppUnitTestSupport {
         String result = TemplateProcessor.processTemplateContents(templateContents, entity, ImmutableMap.<String,Object>of());
         assertEquals(result, "myval");
     }
+    
+    @Test
+    public void testDotSeparatedKey() {
+        String templateContents = "${a.b}";
+        String result = TemplateProcessor.processTemplateContents(templateContents, (ManagementContextInternal)null, 
+            ImmutableMap.<String,Object>of("a.b", "myval"));
+        assertEquals(result, "myval");
+    }
+    
+    @Test
+    public void testDotSeparatedKeyCollisionFailure() {
+        String templateContents = "${aaa.bbb}";
+        try {
+            TemplateProcessor.processTemplateContents(templateContents, (ManagementContextInternal)null, 
+                ImmutableMap.<String,Object>of("aaa.bbb", "myval", "aaa", "blocker"));
+            Assert.fail("Should not have found value with intermediate dot where prefix is overridden");
+        } catch (Exception e) {
+            Assert.assertTrue(e.toString().contains("aaa"), "Should have mentioned missing key 'aaa' in error");
+        }
+    }
+
 }


[5/5] incubator-brooklyn git commit: This closes #340

Posted by he...@apache.org.
This closes #340


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

Branch: refs/heads/master
Commit: cb043e22a349b95e4963adcfe4175b5d5a649152
Parents: 60ccc2c 5a5773e
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Mon Nov 17 15:22:49 2014 +0000
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Mon Nov 17 15:22:49 2014 +0000

----------------------------------------------------------------------
 .../brooklyn/config/BrooklynServerConfig.java   |  12 +
 .../brooklyn/config/BrooklynServerPaths.java    |  43 +++-
 .../drivers/BasicEntityDriverManager.java       |   8 +
 .../drivers/ReflectiveEntityDriverFactory.java  | 218 ++++++++++++++++---
 .../persister/FileBasedStoreObjectAccessor.java |  11 +-
 .../brooklyn/management/ha/OsgiManager.java     |  29 ++-
 .../internal/LocalManagementContext.java        |   2 +-
 .../brooklyn/util/text/TemplateProcessor.java   | 138 ++++++++++--
 .../ReflectiveEntityDriverFactoryTest.java      |  58 ++++-
 .../brooklyn/management/osgi/OsgiPathTest.java  | 105 +++++++++
 .../util/text/TemplateProcessorTest.java        |  40 +++-
 usage/cli/src/main/java/brooklyn/cli/Main.java  |   6 +
 .../brooklyn/launcher/BrooklynLauncher.java     |  12 +
 .../brooklyn/rest/resources/ServerResource.java |  16 +-
 14 files changed, 619 insertions(+), 79 deletions(-)
----------------------------------------------------------------------