Move o.a.b.sensor.ssh contents to core

- Moved SshCommandEffector (which strangely was in .sensor.ssh) to 
  o.a.b.core.effector.ssh
- Moved SshCommandSensor (which strangely was in .sensor.ssh) to 
  o.a.b.core.sensor.ssh
- Changed these classes to reference config in BrooklynConfigKeys, 
  rather than in SoftwareProcess.*
- Added BrooklynConfigKeys.SHELL_ENVIRONMENT, which SoftwareProcess
  now uses.
- Made SshCommandSensor.makeCommandExecutingInDirectory public and
  marked as @Beta (was previously static package-private, for use
  by SshCommandEffector as well).
- Split SshCommandIntegrationTest into two:
  - SshCommandEffectorIntegrationTest (and removed unnecessary tempFile
    creation)
  - SshCommandSensorIntegrationTest (and moved tempFile creation/deletion
    into setUp/tearDown)

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

Branch: refs/heads/master
Commit: 080d2de0806f5524886b4d6d8bfea7c71a45dbc7
Parents: 8e439bd
Author: Aled Sage <[email protected]>
Authored: Thu Aug 20 11:24:20 2015 +0100
Committer: Aled Sage <[email protected]>
Committed: Thu Aug 20 11:24:20 2015 +0100

----------------------------------------------------------------------
 .../core/effector/ssh/SshCommandEffector.java   | 102 +++++++++++++
 .../core/entity/BrooklynConfigKeys.java         |   9 ++
 .../core/sensor/ssh/SshCommandSensor.java       | 141 ++++++++++++++++++
 .../ssh/SshCommandEffectorIntegrationTest.java  |  94 ++++++++++++
 .../ssh/SshCommandSensorIntegrationTest.java    |  89 ++++++++++++
 .../entity/java/JmxAttributeSensor.java         |   2 +-
 .../entity/software/base/SoftwareProcess.java   |   7 +-
 .../brooklyn/sensor/ssh/SshCommandEffector.java | 103 --------------
 .../brooklyn/sensor/ssh/SshCommandSensor.java   | 142 -------------------
 .../test/ssh/SshCommandIntegrationTest.java     | 126 ----------------
 10 files changed, 439 insertions(+), 376 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/080d2de0/core/src/main/java/org/apache/brooklyn/core/effector/ssh/SshCommandEffector.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/brooklyn/core/effector/ssh/SshCommandEffector.java
 
b/core/src/main/java/org/apache/brooklyn/core/effector/ssh/SshCommandEffector.java
new file mode 100644
index 0000000..b22f717
--- /dev/null
+++ 
b/core/src/main/java/org/apache/brooklyn/core/effector/ssh/SshCommandEffector.java
@@ -0,0 +1,102 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.core.effector.ssh;
+
+import java.util.Map;
+
+import org.apache.brooklyn.api.effector.Effector;
+import org.apache.brooklyn.api.effector.ParameterType;
+import org.apache.brooklyn.config.ConfigKey;
+import org.apache.brooklyn.core.config.ConfigKeys;
+import org.apache.brooklyn.core.effector.AddEffector;
+import org.apache.brooklyn.core.effector.EffectorBody;
+import org.apache.brooklyn.core.effector.Effectors;
+import org.apache.brooklyn.core.effector.Effectors.EffectorBuilder;
+import org.apache.brooklyn.core.entity.BrooklynConfigKeys;
+import org.apache.brooklyn.core.sensor.ssh.SshCommandSensor;
+import org.apache.brooklyn.util.collections.MutableMap;
+import org.apache.brooklyn.util.core.config.ConfigBag;
+import org.apache.brooklyn.util.text.Strings;
+
+import com.google.common.base.Preconditions;
+
+public final class SshCommandEffector extends AddEffector {
+    
+    public static final ConfigKey<String> EFFECTOR_COMMAND = 
ConfigKeys.newStringConfigKey("command");
+    public static final ConfigKey<String> EFFECTOR_EXECUTION_DIR = 
SshCommandSensor.SENSOR_EXECUTION_DIR;
+    
+    public SshCommandEffector(ConfigBag params) {
+        super(newEffectorBuilder(params).build());
+    }
+    
+    public SshCommandEffector(Map<String,String> params) {
+        this(ConfigBag.newInstance(params));
+    }
+
+    public static EffectorBuilder<String> newEffectorBuilder(ConfigBag params) 
{
+        EffectorBuilder<String> eff = 
AddEffector.newEffectorBuilder(String.class, params);
+        eff.impl(new Body(eff.buildAbstract(), params));
+        return eff;
+    }
+
+
+    protected static class Body extends EffectorBody<String> {
+        private final Effector<?> effector;
+        private final String command;
+        private final String executionDir;
+
+        public Body(Effector<?> eff, ConfigBag params) {
+            this.effector = eff;
+            this.command = 
Preconditions.checkNotNull(params.get(EFFECTOR_COMMAND), "command must be 
supplied when defining this effector");
+            this.executionDir = params.get(EFFECTOR_EXECUTION_DIR);
+            // TODO could take a custom "env" aka effectorShellEnv
+        }
+
+        @Override
+        public String call(ConfigBag params) {
+            String command = this.command;
+            
+            command = 
SshCommandSensor.makeCommandExecutingInDirectory(command, executionDir, 
entity());
+            
+            MutableMap<String, String> env = MutableMap.of();
+            // first set all declared parameters, including default values
+            for (ParameterType<?> param: effector.getParameters()) {
+                env.addIfNotNull(param.getName(), Strings.toString( 
params.get(Effectors.asConfigKey(param)) ));
+            }
+            
+            // then set things from the entities defined shell environment, if 
applicable
+            
env.putAll(Strings.toStringMap(entity().getConfig(BrooklynConfigKeys.SHELL_ENVIRONMENT),
 ""));
+            
+            // if we wanted to resolve the surrounding environment in real 
time -- see above
+//            Map<String,Object> paramsResolved = (Map<String, Object>) 
Tasks.resolveDeepValue(effectorShellEnv, Map.class, 
entity().getExecutionContext());
+            
+            // finally set the parameters we've been passed; this will repeat 
declared parameters but to no harm,
+            // it may pick up additional values (could be a flag defining 
whether this is permitted or not)
+            env.putAll(Strings.toStringMap(params.getAllConfig()));
+            
+            SshEffectorTasks.SshEffectorTaskFactory<String> t = 
SshEffectorTasks.ssh(command)
+                .requiringZeroAndReturningStdout()
+                .summary("effector "+effector.getName())
+                .environmentVariables(env);
+            return queue(t).get();
+        }
+        
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/080d2de0/core/src/main/java/org/apache/brooklyn/core/entity/BrooklynConfigKeys.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/brooklyn/core/entity/BrooklynConfigKeys.java 
b/core/src/main/java/org/apache/brooklyn/core/entity/BrooklynConfigKeys.java
index cedccb8..c1ad143 100644
--- a/core/src/main/java/org/apache/brooklyn/core/entity/BrooklynConfigKeys.java
+++ b/core/src/main/java/org/apache/brooklyn/core/entity/BrooklynConfigKeys.java
@@ -26,15 +26,18 @@ import static 
org.apache.brooklyn.core.config.ConfigKeys.newStringConfigKey;
 import org.apache.brooklyn.api.location.Location;
 import org.apache.brooklyn.config.ConfigKey;
 import org.apache.brooklyn.core.config.ConfigKeys;
+import org.apache.brooklyn.core.config.MapConfigKey;
 import org.apache.brooklyn.core.entity.trait.Startable;
 import org.apache.brooklyn.core.sensor.AttributeSensorAndConfigKey;
 import 
org.apache.brooklyn.core.sensor.TemplatedStringAttributeSensorAndConfigKey;
 import org.apache.brooklyn.core.server.BrooklynServerConfig;
+import org.apache.brooklyn.util.core.flags.SetFromFlag;
 import org.apache.brooklyn.util.core.internal.ssh.ShellTool;
 import org.apache.brooklyn.util.core.internal.ssh.SshTool;
 import org.apache.brooklyn.util.time.Duration;
 
 import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableMap;
 
 /** Commonly used config keys, for use in entities. Similar to {@link 
Attributes}.
  * See also {@link BrooklynServerConfig} for config keys for controlling the 
server. */
@@ -103,6 +106,12 @@ public class BrooklynConfigKeys {
     public static final ConfigKey<String> POST_LAUNCH_COMMAND = 
ConfigKeys.newStringConfigKey("post.launch.command",
             "Command to be run after the launch method being called on the 
driver");
 
+    public static final MapConfigKey<Object> SHELL_ENVIRONMENT = new 
MapConfigKey<Object>(
+            Object.class,
+            "shell.env", 
+            "Map of environment variables to pass to the runtime shell", 
+            ImmutableMap.<String,Object>of());
+
     public static final AttributeSensorAndConfigKey<String, String> 
INSTALL_DIR = new TemplatedStringAttributeSensorAndConfigKey("install.dir", 
"Directory for this software to be installed in",
             "${" +
             "config['"+ONBOX_BASE_DIR.getName()+"']!" +

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/080d2de0/core/src/main/java/org/apache/brooklyn/core/sensor/ssh/SshCommandSensor.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/brooklyn/core/sensor/ssh/SshCommandSensor.java 
b/core/src/main/java/org/apache/brooklyn/core/sensor/ssh/SshCommandSensor.java
new file mode 100644
index 0000000..c418f93
--- /dev/null
+++ 
b/core/src/main/java/org/apache/brooklyn/core/sensor/ssh/SshCommandSensor.java
@@ -0,0 +1,141 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.core.sensor.ssh;
+
+import java.util.Map;
+
+import org.apache.brooklyn.api.entity.EntityInitializer;
+import org.apache.brooklyn.api.entity.EntityLocal;
+import org.apache.brooklyn.config.ConfigKey;
+import org.apache.brooklyn.core.config.ConfigKeys;
+import org.apache.brooklyn.core.effector.AddSensor;
+import org.apache.brooklyn.core.entity.BrooklynConfigKeys;
+import org.apache.brooklyn.core.sensor.HttpRequestSensor;
+import org.apache.brooklyn.feed.ssh.SshFeed;
+import org.apache.brooklyn.feed.ssh.SshPollConfig;
+import org.apache.brooklyn.feed.ssh.SshValueFunctions;
+import org.apache.brooklyn.util.collections.MutableMap;
+import org.apache.brooklyn.util.core.config.ConfigBag;
+import org.apache.brooklyn.util.core.flags.TypeCoercions;
+import org.apache.brooklyn.util.os.Os;
+import org.apache.brooklyn.util.text.Strings;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.Function;
+import com.google.common.base.Functions;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Supplier;
+
+/** 
+ * Configurable {@link EntityInitializer} which adds an SSH sensor feed 
running the <code>command</code> supplied
+ * in order to populate the sensor with the indicated <code>name</code>. Note 
that the <code>targetType</code> is ignored,
+ * and always set to {@link String}.
+ *
+ * @see HttpRequestSensor
+ */
+@Beta
+public final class SshCommandSensor<T> extends AddSensor<T> {
+
+    private static final Logger LOG = 
LoggerFactory.getLogger(SshCommandSensor.class);
+
+    public static final ConfigKey<String> SENSOR_COMMAND = 
ConfigKeys.newStringConfigKey("command", "SSH command to execute for sensor");
+    public static final ConfigKey<String> SENSOR_EXECUTION_DIR = 
ConfigKeys.newStringConfigKey("executionDir", "Directory where the command 
should run; "
+        + "if not supplied, executes in the entity's run dir (or home dir if 
no run dir is defined); "
+        + "use '~' to always execute in the home dir, or 'custom-feed/' to 
execute in a custom-feed dir relative to the run dir");
+
+    protected final String command;
+    protected final String executionDir;
+
+    public SshCommandSensor(final ConfigBag params) {
+        super(params);
+
+        // TODO create a supplier for the command string to support attribute 
embedding
+        command = Preconditions.checkNotNull(params.get(SENSOR_COMMAND), 
"command");
+        
+        executionDir = params.get(SENSOR_EXECUTION_DIR);
+    }
+
+    @Override
+    public void apply(final EntityLocal entity) {
+        super.apply(entity);
+
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("Adding SSH sensor {} to {}", name, entity);
+        }
+
+        Supplier<Map<String,String>> envSupplier = new 
Supplier<Map<String,String>>() {
+            @Override
+            public Map<String, String> get() {
+                return 
MutableMap.copyOf(Strings.toStringMap(entity.getConfig(BrooklynConfigKeys.SHELL_ENVIRONMENT),
 ""));
+            }
+        };
+
+        Supplier<String> commandSupplier = new Supplier<String>() {
+            @Override
+            public String get() {
+                return makeCommandExecutingInDirectory(command, executionDir, 
entity);
+            }
+        };
+
+        SshPollConfig<T> pollConfig = new SshPollConfig<T>(sensor)
+                .period(period)
+                .env(envSupplier)
+                .command(commandSupplier)
+                .checkSuccess(SshValueFunctions.exitStatusEquals(0))
+                .onFailureOrException(Functions.constant((T) null))
+                .onSuccess(Functions.compose(new Function<String, T>() {
+                        @Override
+                        public T apply(String input) {
+                            return TypeCoercions.coerce(input, getType(type));
+                        }}, SshValueFunctions.stdout()));
+
+        SshFeed.builder()
+                .entity(entity)
+                .onlyIfServiceUp()
+                .poll(pollConfig)
+                .build();
+    }
+
+    @Beta
+    public static String makeCommandExecutingInDirectory(String command, 
String executionDir, EntityLocal entity) {
+        String finalCommand = command;
+        String execDir = executionDir;
+        if (Strings.isBlank(execDir)) {
+            // default to run dir
+            execDir = entity.getAttribute(BrooklynConfigKeys.RUN_DIR);
+            // if no run dir, default to home
+            if (Strings.isBlank(execDir)) {
+                execDir = "~";
+            }
+        } else if (!Os.isAbsolutish(execDir)) {
+            // relative paths taken wrt run dir
+            String runDir = entity.getAttribute(BrooklynConfigKeys.RUN_DIR);
+            if (!Strings.isBlank(runDir)) {
+                execDir = Os.mergePaths(runDir, execDir);
+            }
+        }
+        if (!"~".equals(execDir)) {
+            finalCommand = "mkdir -p '"+execDir+"' && cd '"+execDir+"' && 
"+finalCommand;
+        }
+        return finalCommand;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/080d2de0/core/src/test/java/org/apache/brooklyn/core/effector/ssh/SshCommandEffectorIntegrationTest.java
----------------------------------------------------------------------
diff --git 
a/core/src/test/java/org/apache/brooklyn/core/effector/ssh/SshCommandEffectorIntegrationTest.java
 
b/core/src/test/java/org/apache/brooklyn/core/effector/ssh/SshCommandEffectorIntegrationTest.java
new file mode 100644
index 0000000..7b5dd9a
--- /dev/null
+++ 
b/core/src/test/java/org/apache/brooklyn/core/effector/ssh/SshCommandEffectorIntegrationTest.java
@@ -0,0 +1,94 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.core.effector.ssh;
+
+import org.apache.brooklyn.api.effector.Effector;
+import org.apache.brooklyn.api.entity.EntityLocal;
+import org.apache.brooklyn.api.entity.EntitySpec;
+import org.apache.brooklyn.api.location.Location;
+import org.apache.brooklyn.api.sensor.AttributeSensor;
+import org.apache.brooklyn.core.effector.Effectors;
+import org.apache.brooklyn.core.entity.Entities;
+import org.apache.brooklyn.core.sensor.Sensors;
+import org.apache.brooklyn.core.test.entity.TestApplication;
+import org.apache.brooklyn.core.test.entity.TestEntity;
+import org.apache.brooklyn.location.ssh.SshMachineLocation;
+import org.apache.brooklyn.util.collections.MutableMap;
+import org.apache.brooklyn.util.core.config.ConfigBag;
+import org.testng.Assert;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableList;
+
+public class SshCommandEffectorIntegrationTest {
+
+    final static AttributeSensor<String> SENSOR_STRING = 
Sensors.newStringSensor("aString", "");
+    final static AttributeSensor<Integer> SENSOR_INT = 
Sensors.newIntegerSensor("aLong", "");
+    final static Effector<String> EFFECTOR_SAY_HI = 
Effectors.effector(String.class, "sayHi").buildAbstract();
+
+    private TestApplication app;
+    private SshMachineLocation machine;
+    private EntityLocal entity;
+    
+    @BeforeMethod(alwaysRun=true)
+    public void setUp() throws Exception {
+        app = TestApplication.Factory.newManagedInstanceForTests();
+        machine = app.newLocalhostProvisioningLocation().obtain();
+        entity = 
app.createAndManageChild(EntitySpec.create(TestEntity.class).location(machine));
+        app.start(ImmutableList.<Location>of());
+    }
+
+    @AfterMethod(alwaysRun=true)
+    public void tearDown() throws Exception {
+        if (app != null) Entities.destroyAll(app.getManagementContext());
+    }
+    
+    @Test(groups="Integration")
+    public void testSshEffector() throws Exception {
+        new SshCommandEffector(ConfigBag.newInstance()
+                .configure(SshCommandEffector.EFFECTOR_NAME, "sayHi")
+                .configure(SshCommandEffector.EFFECTOR_COMMAND, "echo hi"))
+            .apply(entity);
+        
+        String val = entity.invoke(EFFECTOR_SAY_HI, 
MutableMap.<String,String>of()).get();
+        Assert.assertEquals(val.trim(), "hi", "val="+val);
+    }
+
+    @Test(groups="Integration")
+    public void testSshEffectorWithParameters() throws Exception {
+        new SshCommandEffector(ConfigBag.newInstance()
+                .configure(SshCommandEffector.EFFECTOR_NAME, "sayHi")
+                .configure(SshCommandEffector.EFFECTOR_COMMAND, "echo $foo")
+                .configure(SshCommandEffector.EFFECTOR_PARAMETER_DEFS, 
+                    MutableMap.<String,Object>of("foo", 
MutableMap.of("defaultValue", "hi"))))
+            .apply(entity);
+        
+        String val;
+        // explicit value
+        val = entity.invoke(EFFECTOR_SAY_HI, 
MutableMap.<String,String>of("foo", "bar")).get();
+        Assert.assertEquals(val.trim(), "bar", "val="+val);
+        
+        // default value
+        val = entity.invoke(EFFECTOR_SAY_HI, 
MutableMap.<String,String>of()).get();
+        Assert.assertEquals(val.trim(), "hi", "val="+val);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/080d2de0/core/src/test/java/org/apache/brooklyn/core/sensor/ssh/SshCommandSensorIntegrationTest.java
----------------------------------------------------------------------
diff --git 
a/core/src/test/java/org/apache/brooklyn/core/sensor/ssh/SshCommandSensorIntegrationTest.java
 
b/core/src/test/java/org/apache/brooklyn/core/sensor/ssh/SshCommandSensorIntegrationTest.java
new file mode 100644
index 0000000..bdb4a81
--- /dev/null
+++ 
b/core/src/test/java/org/apache/brooklyn/core/sensor/ssh/SshCommandSensorIntegrationTest.java
@@ -0,0 +1,89 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.core.sensor.ssh;
+
+import static org.testng.Assert.assertTrue;
+
+import java.io.File;
+
+import org.apache.brooklyn.api.effector.Effector;
+import org.apache.brooklyn.api.entity.EntityLocal;
+import org.apache.brooklyn.api.entity.EntitySpec;
+import org.apache.brooklyn.api.location.Location;
+import org.apache.brooklyn.api.sensor.AttributeSensor;
+import org.apache.brooklyn.core.effector.Effectors;
+import org.apache.brooklyn.core.entity.Attributes;
+import org.apache.brooklyn.core.entity.Entities;
+import org.apache.brooklyn.core.sensor.Sensors;
+import org.apache.brooklyn.core.test.entity.TestApplication;
+import org.apache.brooklyn.core.test.entity.TestEntity;
+import org.apache.brooklyn.location.ssh.SshMachineLocation;
+import org.apache.brooklyn.test.EntityTestUtils;
+import org.apache.brooklyn.util.core.config.ConfigBag;
+import org.apache.brooklyn.util.time.Duration;
+import org.testng.Assert;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableList;
+
+public class SshCommandSensorIntegrationTest {
+
+    final static AttributeSensor<String> SENSOR_STRING = 
Sensors.newStringSensor("aString", "");
+    final static AttributeSensor<Integer> SENSOR_INT = 
Sensors.newIntegerSensor("aLong", "");
+    final static Effector<String> EFFECTOR_SAY_HI = 
Effectors.effector(String.class, "sayHi").buildAbstract();
+
+    private TestApplication app;
+    private SshMachineLocation machine;
+    private EntityLocal entity;
+    private File tempFile;
+    
+    @BeforeMethod(alwaysRun=true)
+    public void setUp() throws Exception {
+        app = TestApplication.Factory.newManagedInstanceForTests();
+        machine = app.newLocalhostProvisioningLocation().obtain();
+        entity = 
app.createAndManageChild(EntitySpec.create(TestEntity.class).location(machine));
+        app.start(ImmutableList.<Location>of());
+        tempFile = File.createTempFile("testSshCommand", ".txt");
+    }
+
+    @AfterMethod(alwaysRun=true)
+    public void tearDown() throws Exception {
+        if (app != null) Entities.destroyAll(app.getManagementContext());
+        if (tempFile != null) tempFile.delete();
+    }
+    
+    @Test(groups="Integration")
+    public void testSshSensor() throws Exception {
+        new SshCommandSensor<String>(ConfigBag.newInstance()
+                .configure(SshCommandSensor.SENSOR_PERIOD, 
Duration.millis(100))
+                .configure(SshCommandSensor.SENSOR_NAME, 
SENSOR_STRING.getName())
+                .configure(SshCommandSensor.SENSOR_COMMAND, "echo foo > 
"+tempFile.getAbsolutePath()+"\n"
+                    + "wc "+tempFile.getAbsolutePath()))
+            .apply(entity);
+        entity.setAttribute(Attributes.SERVICE_UP, true);
+
+        String val = EntityTestUtils.assertAttributeEventuallyNonNull(entity, 
SENSOR_STRING);
+        assertTrue(val.contains("1"), "val="+val);
+        String[] counts = val.trim().split("\\s+");
+        Assert.assertEquals(counts.length, 4, "val="+val);
+        Assert.assertEquals(counts[0], "1", "val="+val);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/080d2de0/software/base/src/main/java/org/apache/brooklyn/entity/java/JmxAttributeSensor.java
----------------------------------------------------------------------
diff --git 
a/software/base/src/main/java/org/apache/brooklyn/entity/java/JmxAttributeSensor.java
 
b/software/base/src/main/java/org/apache/brooklyn/entity/java/JmxAttributeSensor.java
index ae80754..595547e 100644
--- 
a/software/base/src/main/java/org/apache/brooklyn/entity/java/JmxAttributeSensor.java
+++ 
b/software/base/src/main/java/org/apache/brooklyn/entity/java/JmxAttributeSensor.java
@@ -30,10 +30,10 @@ import org.apache.brooklyn.core.config.ConfigKeys;
 import org.apache.brooklyn.core.effector.AddSensor;
 import org.apache.brooklyn.core.sensor.DependentConfiguration;
 import org.apache.brooklyn.core.sensor.HttpRequestSensor;
+import org.apache.brooklyn.core.sensor.ssh.SshCommandSensor;
 import org.apache.brooklyn.feed.jmx.JmxAttributePollConfig;
 import org.apache.brooklyn.feed.jmx.JmxFeed;
 import org.apache.brooklyn.feed.jmx.JmxHelper;
-import org.apache.brooklyn.sensor.ssh.SshCommandSensor;
 import org.apache.brooklyn.util.core.config.ConfigBag;
 import org.apache.brooklyn.util.core.task.DynamicTasks;
 import org.apache.brooklyn.util.core.task.Tasks;

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/080d2de0/software/base/src/main/java/org/apache/brooklyn/entity/software/base/SoftwareProcess.java
----------------------------------------------------------------------
diff --git 
a/software/base/src/main/java/org/apache/brooklyn/entity/software/base/SoftwareProcess.java
 
b/software/base/src/main/java/org/apache/brooklyn/entity/software/base/SoftwareProcess.java
index cee2d40..b14d6d8 100644
--- 
a/software/base/src/main/java/org/apache/brooklyn/entity/software/base/SoftwareProcess.java
+++ 
b/software/base/src/main/java/org/apache/brooklyn/entity/software/base/SoftwareProcess.java
@@ -102,6 +102,9 @@ public interface SoftwareProcess extends Entity, Startable {
     @SetFromFlag("postLaunchCommand")
     ConfigKey<String> POST_LAUNCH_COMMAND = 
BrooklynConfigKeys.POST_LAUNCH_COMMAND;
 
+    @SetFromFlag("env")
+    MapConfigKey<Object> SHELL_ENVIRONMENT = 
BrooklynConfigKeys.SHELL_ENVIRONMENT;
+
     @SetFromFlag("version")
     ConfigKey<String> SUGGESTED_VERSION = BrooklynConfigKeys.SUGGESTED_VERSION;
 
@@ -218,10 +221,6 @@ public interface SoftwareProcess extends Entity, Startable 
{
     ConfigKey<Map<String, String>> RUNTIME_TEMPLATES = 
ConfigKeys.newConfigKey(new TypeToken<Map<String, String>>() { },
             "templates.runtime", "Mapping of templates, to be filled in and 
copied before customisation, to destination name relative to runDir");
 
-    @SetFromFlag("env")
-    MapConfigKey<Object> SHELL_ENVIRONMENT = new 
MapConfigKey<Object>(Object.class,
-            "shell.env", "Map of environment variables to pass to the runtime 
shell", MutableMap.<String,Object>of());
-
     @SetFromFlag("provisioningProperties")
     MapConfigKey<Object> PROVISIONING_PROPERTIES = new 
MapConfigKey<Object>(Object.class,
             "provisioning.properties", "Custom properties to be passed in when 
provisioning a new machine", MutableMap.<String,Object>of());

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/080d2de0/software/base/src/main/java/org/apache/brooklyn/sensor/ssh/SshCommandEffector.java
----------------------------------------------------------------------
diff --git 
a/software/base/src/main/java/org/apache/brooklyn/sensor/ssh/SshCommandEffector.java
 
b/software/base/src/main/java/org/apache/brooklyn/sensor/ssh/SshCommandEffector.java
deleted file mode 100644
index 9e3d885..0000000
--- 
a/software/base/src/main/java/org/apache/brooklyn/sensor/ssh/SshCommandEffector.java
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.brooklyn.sensor.ssh;
-
-import java.util.Map;
-
-import org.apache.brooklyn.api.effector.Effector;
-import org.apache.brooklyn.api.effector.ParameterType;
-import org.apache.brooklyn.config.ConfigKey;
-import org.apache.brooklyn.core.config.ConfigKeys;
-import org.apache.brooklyn.core.effector.AddEffector;
-import org.apache.brooklyn.core.effector.EffectorBody;
-import org.apache.brooklyn.core.effector.Effectors;
-import org.apache.brooklyn.core.effector.Effectors.EffectorBuilder;
-import org.apache.brooklyn.core.effector.ssh.SshEffectorTasks;
-import 
org.apache.brooklyn.core.effector.ssh.SshEffectorTasks.SshEffectorTaskFactory;
-import org.apache.brooklyn.entity.software.base.SoftwareProcess;
-import org.apache.brooklyn.util.collections.MutableMap;
-import org.apache.brooklyn.util.core.config.ConfigBag;
-import org.apache.brooklyn.util.text.Strings;
-
-import com.google.common.base.Preconditions;
-
-public final class SshCommandEffector extends AddEffector {
-    
-    public static final ConfigKey<String> EFFECTOR_COMMAND = 
ConfigKeys.newStringConfigKey("command");
-    public static final ConfigKey<String> EFFECTOR_EXECUTION_DIR = 
SshCommandSensor.SENSOR_EXECUTION_DIR;
-    
-    public SshCommandEffector(ConfigBag params) {
-        super(newEffectorBuilder(params).build());
-    }
-    
-    public SshCommandEffector(Map<String,String> params) {
-        this(ConfigBag.newInstance(params));
-    }
-
-    public static EffectorBuilder<String> newEffectorBuilder(ConfigBag params) 
{
-        EffectorBuilder<String> eff = 
AddEffector.newEffectorBuilder(String.class, params);
-        eff.impl(new Body(eff.buildAbstract(), params));
-        return eff;
-    }
-
-
-    protected static class Body extends EffectorBody<String> {
-        private final Effector<?> effector;
-        private final String command;
-        private final String executionDir;
-
-        public Body(Effector<?> eff, ConfigBag params) {
-            this.effector = eff;
-            this.command = 
Preconditions.checkNotNull(params.get(EFFECTOR_COMMAND), "command must be 
supplied when defining this effector");
-            this.executionDir = params.get(EFFECTOR_EXECUTION_DIR);
-            // TODO could take a custom "env" aka effectorShellEnv
-        }
-
-        @Override
-        public String call(ConfigBag params) {
-            String command = this.command;
-            
-            command = 
SshCommandSensor.makeCommandExecutingInDirectory(command, executionDir, 
entity());
-            
-            MutableMap<String, String> env = MutableMap.of();
-            // first set all declared parameters, including default values
-            for (ParameterType<?> param: effector.getParameters()) {
-                env.addIfNotNull(param.getName(), Strings.toString( 
params.get(Effectors.asConfigKey(param)) ));
-            }
-            
-            // then set things from the entities defined shell environment, if 
applicable
-            
env.putAll(Strings.toStringMap(entity().getConfig(SoftwareProcess.SHELL_ENVIRONMENT),
 ""));
-            
-            // if we wanted to resolve the surrounding environment in real 
time -- see above
-//            Map<String,Object> paramsResolved = (Map<String, Object>) 
Tasks.resolveDeepValue(effectorShellEnv, Map.class, 
entity().getExecutionContext());
-            
-            // finally set the parameters we've been passed; this will repeat 
declared parameters but to no harm,
-            // it may pick up additional values (could be a flag defining 
whether this is permitted or not)
-            env.putAll(Strings.toStringMap(params.getAllConfig()));
-            
-            SshEffectorTaskFactory<String> t = SshEffectorTasks.ssh(command)
-                .requiringZeroAndReturningStdout()
-                .summary("effector "+effector.getName())
-                .environmentVariables(env);
-            return queue(t).get();
-        }
-        
-    }
-    
-}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/080d2de0/software/base/src/main/java/org/apache/brooklyn/sensor/ssh/SshCommandSensor.java
----------------------------------------------------------------------
diff --git 
a/software/base/src/main/java/org/apache/brooklyn/sensor/ssh/SshCommandSensor.java
 
b/software/base/src/main/java/org/apache/brooklyn/sensor/ssh/SshCommandSensor.java
deleted file mode 100644
index 31c116f..0000000
--- 
a/software/base/src/main/java/org/apache/brooklyn/sensor/ssh/SshCommandSensor.java
+++ /dev/null
@@ -1,142 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.brooklyn.sensor.ssh;
-
-import java.util.Map;
-
-import org.apache.brooklyn.api.entity.EntityInitializer;
-import org.apache.brooklyn.api.entity.EntityLocal;
-import org.apache.brooklyn.config.ConfigKey;
-import org.apache.brooklyn.core.config.ConfigKeys;
-import org.apache.brooklyn.core.effector.AddSensor;
-import org.apache.brooklyn.core.sensor.HttpRequestSensor;
-import org.apache.brooklyn.entity.java.JmxAttributeSensor;
-import org.apache.brooklyn.entity.software.base.SoftwareProcess;
-import org.apache.brooklyn.feed.ssh.SshFeed;
-import org.apache.brooklyn.feed.ssh.SshPollConfig;
-import org.apache.brooklyn.feed.ssh.SshValueFunctions;
-import org.apache.brooklyn.util.collections.MutableMap;
-import org.apache.brooklyn.util.core.config.ConfigBag;
-import org.apache.brooklyn.util.core.flags.TypeCoercions;
-import org.apache.brooklyn.util.os.Os;
-import org.apache.brooklyn.util.text.Strings;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.google.common.annotations.Beta;
-import com.google.common.base.Function;
-import com.google.common.base.Functions;
-import com.google.common.base.Preconditions;
-import com.google.common.base.Supplier;
-
-/** 
- * Configurable {@link EntityInitializer} which adds an SSH sensor feed 
running the <code>command</code> supplied
- * in order to populate the sensor with the indicated <code>name</code>. Note 
that the <code>targetType</code> is ignored,
- * and always set to {@link String}.
- *
- * @see HttpRequestSensor
- * @see JmxAttributeSensor
- */
-@Beta
-public final class SshCommandSensor<T> extends AddSensor<T> {
-
-    private static final Logger LOG = 
LoggerFactory.getLogger(SshCommandSensor.class);
-
-    public static final ConfigKey<String> SENSOR_COMMAND = 
ConfigKeys.newStringConfigKey("command", "SSH command to execute for sensor");
-    public static final ConfigKey<String> SENSOR_EXECUTION_DIR = 
ConfigKeys.newStringConfigKey("executionDir", "Directory where the command 
should run; "
-        + "if not supplied, executes in the entity's run dir (or home dir if 
no run dir is defined); "
-        + "use '~' to always execute in the home dir, or 'custom-feed/' to 
execute in a custom-feed dir relative to the run dir");
-
-    protected final String command;
-    protected final String executionDir;
-
-    public SshCommandSensor(final ConfigBag params) {
-        super(params);
-
-        // TODO create a supplier for the command string to support attribute 
embedding
-        command = Preconditions.checkNotNull(params.get(SENSOR_COMMAND), 
"command");
-        
-        executionDir = params.get(SENSOR_EXECUTION_DIR);
-    }
-
-    @Override
-    public void apply(final EntityLocal entity) {
-        super.apply(entity);
-
-        if (LOG.isDebugEnabled()) {
-            LOG.debug("Adding SSH sensor {} to {}", name, entity);
-        }
-
-        Supplier<Map<String,String>> envSupplier = new 
Supplier<Map<String,String>>() {
-            @Override
-            public Map<String, String> get() {
-                return 
MutableMap.copyOf(Strings.toStringMap(entity.getConfig(SoftwareProcess.SHELL_ENVIRONMENT),
 ""));
-            }
-        };
-
-        Supplier<String> commandSupplier = new Supplier<String>() {
-            @Override
-            public String get() {
-                return makeCommandExecutingInDirectory(command, executionDir, 
entity);
-            }
-        };
-
-        SshPollConfig<T> pollConfig = new SshPollConfig<T>(sensor)
-                .period(period)
-                .env(envSupplier)
-                .command(commandSupplier)
-                .checkSuccess(SshValueFunctions.exitStatusEquals(0))
-                .onFailureOrException(Functions.constant((T) null))
-                .onSuccess(Functions.compose(new Function<String, T>() {
-                        @Override
-                        public T apply(String input) {
-                            return TypeCoercions.coerce(input, getType(type));
-                        }}, SshValueFunctions.stdout()));
-
-        SshFeed.builder()
-                .entity(entity)
-                .onlyIfServiceUp()
-                .poll(pollConfig)
-                .build();
-    }
-
-    static String makeCommandExecutingInDirectory(String command, String 
executionDir, EntityLocal entity) {
-        String finalCommand = command;
-        String execDir = executionDir;
-        if (Strings.isBlank(execDir)) {
-            // default to run dir
-            execDir = entity.getAttribute(SoftwareProcess.RUN_DIR);
-            // if no run dir, default to home
-            if (Strings.isBlank(execDir)) {
-                execDir = "~";
-            }
-        } else if (!Os.isAbsolutish(execDir)) {
-            // relative paths taken wrt run dir
-            String runDir = entity.getAttribute(SoftwareProcess.RUN_DIR);
-            if (!Strings.isBlank(runDir)) {
-                execDir = Os.mergePaths(runDir, execDir);
-            }
-        }
-        if (!"~".equals(execDir)) {
-            finalCommand = "mkdir -p '"+execDir+"' && cd '"+execDir+"' && 
"+finalCommand;
-        }
-        return finalCommand;
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/080d2de0/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/ssh/SshCommandIntegrationTest.java
----------------------------------------------------------------------
diff --git 
a/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/ssh/SshCommandIntegrationTest.java
 
b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/ssh/SshCommandIntegrationTest.java
deleted file mode 100644
index c57711f..0000000
--- 
a/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/ssh/SshCommandIntegrationTest.java
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.brooklyn.entity.software.base.test.ssh;
-
-import static org.testng.Assert.assertTrue;
-
-import java.io.File;
-
-import org.apache.brooklyn.api.effector.Effector;
-import org.apache.brooklyn.api.entity.EntityLocal;
-import org.apache.brooklyn.api.entity.EntitySpec;
-import org.apache.brooklyn.api.location.Location;
-import org.apache.brooklyn.api.sensor.AttributeSensor;
-import org.apache.brooklyn.core.effector.Effectors;
-import org.apache.brooklyn.core.entity.Attributes;
-import org.apache.brooklyn.core.entity.Entities;
-import org.apache.brooklyn.core.sensor.Sensors;
-import org.apache.brooklyn.core.test.entity.TestApplication;
-import org.apache.brooklyn.core.test.entity.TestEntity;
-import org.apache.brooklyn.sensor.ssh.SshCommandEffector;
-import org.apache.brooklyn.sensor.ssh.SshCommandSensor;
-import org.apache.brooklyn.test.EntityTestUtils;
-import org.apache.brooklyn.util.collections.MutableMap;
-import org.apache.brooklyn.util.core.config.ConfigBag;
-import org.apache.brooklyn.util.time.Duration;
-import org.testng.Assert;
-import org.testng.annotations.AfterMethod;
-import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.Test;
-import org.apache.brooklyn.location.ssh.SshMachineLocation;
-
-import com.google.common.collect.ImmutableList;
-
-public class SshCommandIntegrationTest {
-
-    final static AttributeSensor<String> SENSOR_STRING = 
Sensors.newStringSensor("aString", "");
-    final static AttributeSensor<Integer> SENSOR_INT = 
Sensors.newIntegerSensor("aLong", "");
-    final static Effector<String> EFFECTOR_SAY_HI = 
Effectors.effector(String.class, "sayHi").buildAbstract();
-
-    private TestApplication app;
-    private SshMachineLocation machine;
-    private EntityLocal entity;
-    
-    @BeforeMethod(alwaysRun=true)
-    public void setUp() throws Exception {
-        app = TestApplication.Factory.newManagedInstanceForTests();
-        machine = app.newLocalhostProvisioningLocation().obtain();
-        entity = 
app.createAndManageChild(EntitySpec.create(TestEntity.class).location(machine));
-        app.start(ImmutableList.<Location>of());
-    }
-
-    @AfterMethod(alwaysRun=true)
-    public void tearDown() throws Exception {
-        if (app != null) Entities.destroyAll(app.getManagementContext());
-    }
-    
-    @Test(groups="Integration")
-    public void testSshSensor() throws Exception {
-        File tempFile = File.createTempFile("testSshCommand", "txt");
-        tempFile.deleteOnExit();
-        new SshCommandSensor<String>(ConfigBag.newInstance()
-                .configure(SshCommandSensor.SENSOR_PERIOD, 
Duration.millis(100))
-                .configure(SshCommandSensor.SENSOR_NAME, 
SENSOR_STRING.getName())
-                .configure(SshCommandSensor.SENSOR_COMMAND, "echo foo > 
"+tempFile.getAbsolutePath()+"\n"
-                    + "wc "+tempFile.getAbsolutePath()))
-            .apply(entity);
-        entity.setAttribute(Attributes.SERVICE_UP, true);
-
-        String val = EntityTestUtils.assertAttributeEventuallyNonNull(entity, 
SENSOR_STRING);
-        assertTrue(val.contains("1"), "val="+val);
-        String[] counts = val.trim().split("\\s+");
-        Assert.assertEquals(counts.length, 4, "val="+val);
-        Assert.assertEquals(counts[0], "1", "val="+val);
-    }
-
-    @Test(groups="Integration")
-    public void testSshEffector() throws Exception {
-        File tempFile = File.createTempFile("testSshCommand", "txt");
-        tempFile.deleteOnExit();
-        new SshCommandEffector(ConfigBag.newInstance()
-                .configure(SshCommandEffector.EFFECTOR_NAME, "sayHi")
-                .configure(SshCommandEffector.EFFECTOR_COMMAND, "echo hi"))
-            .apply(entity);
-        
-        String val = entity.invoke(EFFECTOR_SAY_HI, 
MutableMap.<String,String>of()).get();
-        Assert.assertEquals(val.trim(), "hi", "val="+val);
-    }
-
-    @Test(groups="Integration")
-    public void testSshEffectorWithParameters() throws Exception {
-        File tempFile = File.createTempFile("testSshCommand", "txt");
-        tempFile.deleteOnExit();
-        new SshCommandEffector(ConfigBag.newInstance()
-                .configure(SshCommandEffector.EFFECTOR_NAME, "sayHi")
-                .configure(SshCommandEffector.EFFECTOR_COMMAND, "echo $foo")
-                .configure(SshCommandEffector.EFFECTOR_PARAMETER_DEFS, 
-                    MutableMap.<String,Object>of("foo", 
MutableMap.of("defaultValue", "hi"))))
-            .apply(entity);
-        
-        String val;
-        // explicit value
-        val = entity.invoke(EFFECTOR_SAY_HI, 
MutableMap.<String,String>of("foo", "bar")).get();
-        Assert.assertEquals(val.trim(), "bar", "val="+val);
-        
-        // default value
-        val = entity.invoke(EFFECTOR_SAY_HI, 
MutableMap.<String,String>of()).get();
-        Assert.assertEquals(val.trim(), "hi", "val="+val);
-    }
-
-}


Reply via email to