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 2015/08/20 00:53:57 UTC

[09/36] incubator-brooklyn git commit: Rename o.a.b.effector.core to o.a.b.core.effector

Rename o.a.b.effector.core to o.a.b.core.effector

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

Branch: refs/heads/master
Commit: 8dbb0e4b2de44840f23f6cce2862dc41ae9ff307
Parents: 4820fa4
Author: Aled Sage <al...@gmail.com>
Authored: Wed Aug 19 22:39:55 2015 +0100
Committer: Aled Sage <al...@gmail.com>
Committed: Wed Aug 19 22:39:55 2015 +0100

----------------------------------------------------------------------
 .../core/effector/AbstractEffector.java         |  90 ++++
 .../core/effector/AddChildrenEffector.java      | 117 +++++
 .../brooklyn/core/effector/AddEffector.java     | 116 +++++
 .../brooklyn/core/effector/AddSensor.java       | 126 ++++++
 .../core/effector/BasicParameterType.java       | 116 +++++
 .../brooklyn/core/effector/EffectorAndBody.java |  60 +++
 .../brooklyn/core/effector/EffectorBase.java    | 106 +++++
 .../brooklyn/core/effector/EffectorBody.java    | 100 +++++
 .../brooklyn/core/effector/EffectorTasks.java   | 229 ++++++++++
 .../core/effector/EffectorWithBody.java         |  32 ++
 .../brooklyn/core/effector/Effectors.java       | 202 +++++++++
 .../core/effector/ExplicitEffector.java         |  74 ++++
 .../brooklyn/core/effector/MethodEffector.java  | 180 ++++++++
 .../core/effector/ssh/SshEffectorTasks.java     | 335 ++++++++++++++
 .../apache/brooklyn/core/entity/Entities.java   |   2 +-
 .../brooklyn/core/entity/EntityDynamicType.java |  14 +-
 .../core/entity/trait/MemberReplaceable.java    |   2 +-
 .../brooklyn/core/entity/trait/Resizable.java   |   2 +-
 .../brooklyn/core/entity/trait/Startable.java   |   6 +-
 .../core/entity/trait/StartableMethods.java     |   2 +-
 .../core/mgmt/EntityManagementUtils.java        |   2 +-
 .../core/mgmt/internal/EffectorUtils.java       |   2 +-
 .../mgmt/internal/LocalManagementContext.java   |   2 +-
 .../core/mgmt/persist/XmlMementoSerializer.java |   8 +-
 .../core/objs/proxy/EntityProxyImpl.java        |   2 +-
 .../effector/core/AbstractEffector.java         |  90 ----
 .../effector/core/AddChildrenEffector.java      | 117 -----
 .../brooklyn/effector/core/AddEffector.java     | 116 -----
 .../brooklyn/effector/core/AddSensor.java       | 126 ------
 .../effector/core/BasicParameterType.java       | 116 -----
 .../brooklyn/effector/core/EffectorAndBody.java |  60 ---
 .../brooklyn/effector/core/EffectorBase.java    | 106 -----
 .../brooklyn/effector/core/EffectorBody.java    | 100 -----
 .../brooklyn/effector/core/EffectorTasks.java   | 229 ----------
 .../effector/core/EffectorWithBody.java         |  32 --
 .../brooklyn/effector/core/Effectors.java       | 202 ---------
 .../effector/core/ExplicitEffector.java         |  74 ----
 .../brooklyn/effector/core/MethodEffector.java  | 180 --------
 .../effector/core/ssh/SshEffectorTasks.java     | 334 --------------
 .../brooklyn/entity/group/DynamicCluster.java   |   2 +-
 .../entity/group/DynamicClusterImpl.java        |   2 +-
 .../entity/group/DynamicFabricImpl.java         |   2 +-
 .../brooklyn/entity/group/DynamicGroup.java     |   2 +-
 .../entity/group/DynamicRegionsFabric.java      |   2 +-
 .../entity/group/QuarantineGroupImpl.java       |   2 +-
 .../brooklyn/sensor/core/HttpRequestSensor.java |   2 +-
 .../brooklyn/sensor/core/StaticSensor.java      |   2 +-
 .../windows/WindowsPerformanceCounterFeed.java  |   2 +-
 .../brooklyn/util/core/task/ssh/SshTasks.java   |   2 +-
 .../core/effector/EffectorBasicTest.java        | 183 ++++++++
 .../core/effector/EffectorConcatenateTest.java  | 241 ++++++++++
 .../core/effector/EffectorMetadataTest.java     | 166 +++++++
 .../effector/EffectorSayHiGroovyTest.groovy     | 182 ++++++++
 .../core/effector/EffectorSayHiTest.java        | 173 ++++++++
 .../core/effector/EffectorTaskTest.java         | 437 +++++++++++++++++++
 .../core/effector/ssh/SshEffectorTasksTest.java | 265 +++++++++++
 .../brooklyn/core/entity/DynamicEntityTest.java |   2 +-
 .../brooklyn/core/entity/EntityTypeTest.java    |   2 +-
 .../brooklyn/core/entity/hello/HelloEntity.java |   2 +-
 .../mgmt/osgi/OsgiVersionMoreEntityTest.java    |   2 +-
 .../rebind/RebindEntityDynamicTypeInfoTest.java |   4 +-
 .../brooklyn/core/test/entity/TestEntity.java   |   2 +-
 .../effector/core/EffectorBasicTest.java        | 183 --------
 .../effector/core/EffectorConcatenateTest.java  | 241 ----------
 .../effector/core/EffectorMetadataTest.java     | 166 -------
 .../core/EffectorSayHiGroovyTest.groovy         | 179 --------
 .../effector/core/EffectorSayHiTest.java        | 173 --------
 .../effector/core/EffectorTaskTest.java         | 437 -------------------
 .../effector/core/ssh/SshEffectorTasksTest.java | 264 -----------
 .../location/ssh/SshMachineLocationTest.java    |   6 +-
 .../util/core/task/ssh/SshTasksTest.java        |   2 +-
 .../brooklyn/demo/CumulusRDFApplication.java    |   6 +-
 .../brooklyn/policy/loadbalancing/Movable.java  |   2 +-
 .../loadbalancing/MockContainerEntity.java      |   2 +-
 .../postgresql/PostgreSqlNodeSaltImpl.java      |   6 +-
 .../entity/salt/SaltLifecycleEffectorTasks.java |   2 +-
 .../apache/brooklyn/entity/salt/SaltTasks.java  |   5 +-
 .../postgresql/PostgreSqlSaltLiveTest.java      |   4 +-
 .../entity/brooklynnode/BrooklynCluster.java    |   2 +-
 .../brooklynnode/BrooklynEntityMirrorImpl.java  |   2 +-
 .../entity/brooklynnode/BrooklynNode.java       |   2 +-
 .../entity/brooklynnode/BrooklynNodeImpl.java   |   4 +-
 .../brooklynnode/BrooklynNodeSshDriver.java     |   2 +-
 .../brooklynnode/RemoteEffectorBuilder.java     |   4 +-
 .../BrooklynClusterUpgradeEffectorBody.java     |   4 +-
 .../BrooklynNodeUpgradeEffectorBody.java        |   6 +-
 .../effector/SelectMasterEffectorBody.java      |   4 +-
 .../SetHighAvailabilityModeEffectorBody.java    |   4 +-
 ...SetHighAvailabilityPriorityEffectorBody.java |   4 +-
 .../entity/chef/ChefLifecycleEffectorTasks.java |   2 +-
 .../brooklyn/entity/chef/ChefSoloTasks.java     |   2 +-
 .../apache/brooklyn/entity/chef/ChefTasks.java  |   4 +-
 .../entity/chef/KnifeConvergeTaskFactory.java   |   2 +-
 .../java/JavaSoftwareProcessSshDriver.java      |   4 +-
 .../entity/java/JmxAttributeSensor.java         |   2 +-
 .../brooklyn/entity/machine/MachineEntity.java  |   2 +-
 .../entity/machine/MachineEntityImpl.java       |   2 +-
 .../entity/machine/pool/ServerPool.java         |   2 +-
 .../entity/machine/pool/ServerPoolImpl.java     |   2 +-
 .../base/AbstractSoftwareProcessSshDriver.java  |   4 +-
 .../MachineLifecycleEffectorTasks.java          |   6 +-
 .../system_service/InitdServiceInstaller.java   |   2 +-
 .../system_service/SystemServiceEnricher.java   |   2 +-
 .../brooklyn/sensor/ssh/SshCommandEffector.java |  12 +-
 .../brooklyn/sensor/ssh/SshCommandSensor.java   |   2 +-
 .../brooklynnode/SelectMasterEffectorTest.java  |   2 +-
 .../ChefSoloDriverMySqlEntityLiveTest.java      |   2 +-
 .../mysql/ChefSoloDriverToyMySqlEntity.java     |   2 +-
 .../software/base/SoftwareEffectorTest.java     |   6 +-
 .../base/SoftwareProcessEntityTest.java         |   2 +-
 .../base/SoftwareProcessSubclassTest.java       |   4 +-
 .../test/mysql/AbstractToyMySqlEntityTest.java  |   2 +-
 .../mysql/DynamicToyMySqlEntityBuilder.java     |   2 +-
 .../test/ssh/SshCommandIntegrationTest.java     |   2 +-
 .../SystemServiceEnricherTest.java              |   2 +-
 .../entity/database/DatastoreMixins.java        |   2 +-
 .../database/mariadb/MariaDbNodeImpl.java       |   2 +-
 .../database/mariadb/MariaDbSshDriver.java      |   2 +-
 .../entity/database/mysql/MySqlNode.java        |   2 +-
 .../entity/database/mysql/MySqlNodeImpl.java    |   2 +-
 .../entity/database/mysql/MySqlSshDriver.java   |   2 +-
 .../database/postgresql/PostgreSqlNode.java     |   2 +-
 .../PostgreSqlNodeChefImplFromScratch.java      |   6 +-
 .../database/postgresql/PostgreSqlNodeImpl.java |   2 +-
 .../postgresql/PostgreSqlSshDriver.java         |   2 +-
 .../database/postgresql/PostgreSqlChefTest.java |   4 +-
 .../nosql/cassandra/CassandraDatacenter.java    |   4 +-
 .../cassandra/CassandraDatacenterImpl.java      |   2 +-
 .../entity/nosql/cassandra/CassandraFabric.java |   2 +-
 .../nosql/cassandra/CassandraNodeImpl.java      |   2 +-
 .../nosql/cassandra/CassandraNodeSshDriver.java |   2 +-
 .../nosql/couchbase/CouchbaseClusterImpl.java   |   2 +-
 .../entity/nosql/couchbase/CouchbaseNode.java   |   4 +-
 .../nosql/couchbase/CouchbaseNodeImpl.java      |   2 +-
 .../nosql/couchbase/CouchbaseNodeSshDriver.java |   2 +-
 .../entity/nosql/mongodb/MongoDBClient.java     |   2 +-
 .../brooklyn/entity/nosql/riak/RiakNode.java    |   2 +-
 .../entity/nosql/riak/RiakNodeSshDriver.java    |   2 +-
 .../entity/osgi/karaf/KarafContainer.java       |   2 +-
 .../entity/osgi/karaf/KarafContainerTest.java   |   2 +-
 .../brooklyn/entity/proxy/LoadBalancer.java     |   2 +-
 .../entity/proxy/nginx/NginxController.java     |   2 +-
 .../brooklyn/entity/proxy/nginx/UrlMapping.java |   2 +-
 .../entity/webapp/DynamicWebAppClusterImpl.java |   2 +-
 .../entity/webapp/JavaWebAppService.java        |   2 +-
 .../spi/dsl/BrooklynDslDeferredSupplier.java    |   2 +-
 .../camp/brooklyn/EntitiesYamlTest.java         |   2 +-
 .../TestSensorAndEffectorInitializer.java       |   4 +-
 .../brooklyn/VanillaBashNetcatYamlTest.java     |   2 +-
 .../brooklyn/test/lite/CampYamlLiteTest.java    |   4 +-
 .../qa/load/SimulatedMySqlNodeImpl.java         |   2 +-
 .../testing/mocks/RestMockSimpleEntity.java     |   2 +-
 .../test/osgi/entities/more/MoreEntity.java     |   2 +-
 .../test/osgi/entities/more/MoreEntityImpl.java |   2 +-
 .../test/osgi/entities/more/MoreEntity.java     |   2 +-
 .../test/osgi/entities/more/MoreEntityImpl.java |   2 +-
 .../test/osgi/entities/more/MoreEntity.java     |   2 +-
 .../test/osgi/entities/more/MoreEntityImpl.java |   2 +-
 158 files changed, 3694 insertions(+), 3690 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/8dbb0e4b/core/src/main/java/org/apache/brooklyn/core/effector/AbstractEffector.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/AbstractEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/AbstractEffector.java
new file mode 100644
index 0000000..4acd0e0
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/core/effector/AbstractEffector.java
@@ -0,0 +1,90 @@
+/*
+ * 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;
+
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Callable;
+
+import org.apache.brooklyn.api.effector.Effector;
+import org.apache.brooklyn.api.effector.ParameterType;
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.mgmt.Task;
+import org.apache.brooklyn.core.effector.EffectorTasks.EffectorTaskFactory;
+import org.apache.brooklyn.core.mgmt.internal.EffectorUtils;
+import org.apache.brooklyn.util.core.config.ConfigBag;
+import org.apache.brooklyn.util.core.task.DynamicSequentialTask;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.collect.ImmutableMap;
+
+/**
+ * The abstract {@link Effector} implementation.
+ * 
+ * The concrete subclass (often anonymous) will supply the {@link #call(Entity, Map)} implementation,
+ * and the fields in the constructor.
+ */
+public abstract class AbstractEffector<T> extends EffectorBase<T> implements EffectorWithBody<T> {
+
+    private static final long serialVersionUID = 1832435915652457843L;
+    
+    @SuppressWarnings("unused")
+    private static final Logger LOG = LoggerFactory.getLogger(AbstractEffector.class);
+
+    public AbstractEffector(String name, Class<T> returnType, List<ParameterType<?>> parameters, String description) {
+        super(name, returnType, parameters, description);
+    }
+
+    public abstract T call(Entity entity, @SuppressWarnings("rawtypes") Map parameters);
+
+    /** Convenience for named-parameter syntax (needs map in first argument) */
+    public T call(Entity entity) { return call(ImmutableMap.of(), entity); }
+
+    /** Convenience for named-parameter syntax (needs map in first argument) */
+    public T call(@SuppressWarnings("rawtypes") Map parameters, Entity entity) { return call(entity, parameters); }
+
+    /** @deprecated since 0.7.0 use {@link #getFlagsForTaskInvocationAt(Entity, Effector, ConfigBag)} */ @Deprecated
+    protected final Map<Object,Object> getFlagsForTaskInvocationAt(Entity entity) {
+        return getFlagsForTaskInvocationAt(entity, this, null);
+    }
+    /** subclasses may override to add additional flags, but they should include the flags returned here 
+     * unless there is very good reason not to */
+    protected Map<Object,Object> getFlagsForTaskInvocationAt(Entity entity, Effector<T> effector, ConfigBag parameters) {
+        return EffectorUtils.getTaskFlagsForEffectorInvocation(entity, effector, parameters);
+    }
+    
+    /** not meant for overriding; subclasses should override the abstract {@link #call(Entity, Map)} method in this class */
+    @Override
+    public final EffectorTaskFactory<T> getBody() {
+        return new EffectorTaskFactory<T>() {
+            @Override
+            public Task<T> newTask(final Entity entity, final Effector<T> effector, final ConfigBag parameters) {
+                return new DynamicSequentialTask<T>(
+                        getFlagsForTaskInvocationAt(entity, AbstractEffector.this, parameters),
+                        new Callable<T>() {
+                            @Override public T call() {
+                                return AbstractEffector.this.call(parameters.getAllConfig(), entity);
+                            }
+                        });
+            }
+        };
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/8dbb0e4b/core/src/main/java/org/apache/brooklyn/core/effector/AddChildrenEffector.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/AddChildrenEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/AddChildrenEffector.java
new file mode 100644
index 0000000..f2730ca
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/core/effector/AddChildrenEffector.java
@@ -0,0 +1,117 @@
+/*
+ * 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;
+
+import java.util.List;
+import java.util.Map;
+
+import org.apache.brooklyn.api.effector.Effector;
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.config.ConfigKey;
+import org.apache.brooklyn.core.config.ConfigKeys;
+import org.apache.brooklyn.core.effector.Effectors.EffectorBuilder;
+import org.apache.brooklyn.core.mgmt.EntityManagementUtils;
+import org.apache.brooklyn.core.mgmt.EntityManagementUtils.CreationResult;
+import org.apache.brooklyn.util.core.config.ConfigBag;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.annotations.Beta;
+import com.google.gson.Gson;
+
+/** Entity initializer which defines an effector which adds a child blueprint to an entity.
+ * <p>
+ * One of the config keys {@link #BLUEPRINT_YAML} (containing a YAML blueprint (map or string)) 
+ * or {@link #BLUEPRINT_TYPE} (containing a string referring to a catalog type) should be supplied, but not both.
+ * Parameters defined here are supplied as config during the entity creation.
+ * 
+ * @since 0.7.0 */
+@Beta
+public class AddChildrenEffector extends AddEffector {
+    
+    private static final Logger log = LoggerFactory.getLogger(AddChildrenEffector.class);
+    
+    public static final ConfigKey<Object> BLUEPRINT_YAML = ConfigKeys.newConfigKey(Object.class, "blueprint_yaml");
+    public static final ConfigKey<String> BLUEPRINT_TYPE = ConfigKeys.newStringConfigKey("blueprint_type");
+    public static final ConfigKey<Boolean> AUTO_START = ConfigKeys.newBooleanConfigKey("auto_start");
+    
+    public AddChildrenEffector(ConfigBag params) {
+        super(newEffectorBuilder(params).build());
+    }
+    
+    public AddChildrenEffector(Map<String,String> params) {
+        this(ConfigBag.newInstance(params));
+    }
+
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    public static EffectorBuilder<List<String>> newEffectorBuilder(ConfigBag params) {
+        EffectorBuilder<List<String>> eff = (EffectorBuilder) AddEffector.newEffectorBuilder(List.class, params);
+        eff.impl(new Body(eff.buildAbstract(), params));
+        return eff;
+    }
+
+    protected static class Body extends EffectorBody<List<String>> {
+
+        private final Effector<?> effector;
+        private final String blueprintBase;
+        private final Boolean autostart;
+
+        public Body(Effector<?> eff, ConfigBag params) {
+            this.effector = eff;
+            String newBlueprint = null;
+            Object yaml = params.get(BLUEPRINT_YAML);
+            if (yaml instanceof Map) {
+                newBlueprint = new Gson().toJson(yaml);
+            } else if (yaml instanceof String) {
+                newBlueprint = (String) yaml;
+            } else if (yaml!=null) {
+                throw new IllegalArgumentException(this+" requires map or string in "+BLUEPRINT_YAML+"; not "+yaml.getClass()+" ("+yaml+")");
+            }
+            String blueprintType = params.get(BLUEPRINT_TYPE);
+            if (blueprintType!=null) {
+                if (newBlueprint!=null) {
+                    throw new IllegalArgumentException(this+" cannot take both "+BLUEPRINT_TYPE+" and "+BLUEPRINT_YAML);
+                }
+                newBlueprint = "services: [ { type: "+blueprintType+" } ]";
+            }
+            if (newBlueprint==null) {
+                throw new IllegalArgumentException(this+" requires either "+BLUEPRINT_TYPE+" or "+BLUEPRINT_YAML);
+            }
+            blueprintBase = newBlueprint;
+            autostart = params.get(AUTO_START);
+        }
+
+        @Override
+        public List<String> call(ConfigBag params) {
+            params = getMergedParams(effector, params);
+            
+            String blueprint = blueprintBase;
+            if (!params.isEmpty()) { 
+                blueprint = blueprint+"\n"+"brooklyn.config: "+
+                    new Gson().toJson(params.getAllConfig());
+            }
+
+            log.debug(this+" adding children to "+entity()+":\n"+blueprint);
+            CreationResult<List<Entity>, List<String>> result = EntityManagementUtils.addChildren(entity(), blueprint, autostart);
+            log.debug(this+" added children to "+entity()+": "+result.get());
+            return result.task().getUnchecked();
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/8dbb0e4b/core/src/main/java/org/apache/brooklyn/core/effector/AddEffector.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/AddEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/AddEffector.java
new file mode 100644
index 0000000..9590bcf
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/core/effector/AddEffector.java
@@ -0,0 +1,116 @@
+/*
+ * 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;
+
+import java.util.Collections;
+import java.util.Map;
+
+import org.apache.brooklyn.api.effector.Effector;
+import org.apache.brooklyn.api.effector.ParameterType;
+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.config.MapConfigKey;
+import org.apache.brooklyn.core.effector.Effectors.EffectorBuilder;
+import org.apache.brooklyn.core.entity.EntityInternal;
+import org.apache.brooklyn.util.core.config.ConfigBag;
+import org.apache.brooklyn.util.text.Strings;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.Preconditions;
+
+/** 
+ * Entity initializer which adds an effector to an entity.
+ * <p>
+ * This instance provides a {@link #newEffectorBuilder(Class, ConfigBag)} 
+ * which returns an abstract (body-less) effector defining:
+ * <li> the name from {@link #EFFECTOR_NAME};
+ * <li> the description from {@link #EFFECTOR_DESCRIPTION}
+ * <li> the parameters from {@link #EFFECTOR_PARAMETER_DEFS}
+ * <p>
+ * Callers should pass the effector to instantiate into the constructor.
+ * Often subclasses will supply a constructor which takes a ConfigBag of parameters,
+ * and a custom {@link #newEffectorBuilder(Class, ConfigBag)} which adds the body
+ * before passing to this class.
+ * <p>
+ * Note that the parameters passed to the call method in the body of the effector implementation
+ * are only those supplied by a user at runtime; in order to merge with default
+ * values, use {@link #getMergedParams(Effector, ConfigBag)}.
+ *  
+ * @since 0.7.0 */
+@Beta
+public class AddEffector implements EntityInitializer {
+    
+    public static final ConfigKey<String> EFFECTOR_NAME = ConfigKeys.newStringConfigKey("name");
+    public static final ConfigKey<String> EFFECTOR_DESCRIPTION = ConfigKeys.newStringConfigKey("description");
+    
+    public static final ConfigKey<Map<String,Object>> EFFECTOR_PARAMETER_DEFS = new MapConfigKey<Object>(Object.class, "parameters");
+
+    final Effector<?> effector;
+    
+    public AddEffector(Effector<?> effector) {
+        this.effector = Preconditions.checkNotNull(effector, "effector");
+    }
+    
+    @Override
+    public void apply(EntityLocal entity) {
+        ((EntityInternal)entity).getMutableEntityType().addEffector(effector);
+    }
+    
+    public static <T> EffectorBuilder<T> newEffectorBuilder(Class<T> type, ConfigBag params) {
+        String name = Preconditions.checkNotNull(params.get(EFFECTOR_NAME), "name must be supplied when defining an effector: %s", params);
+        EffectorBuilder<T> eff = Effectors.effector(type, name);
+        eff.description(params.get(EFFECTOR_DESCRIPTION));
+        
+        Map<String, Object> paramDefs = params.get(EFFECTOR_PARAMETER_DEFS);
+        if (paramDefs!=null) {
+            for (Map.Entry<String, Object> paramDef: paramDefs.entrySet()){
+                if (paramDef!=null) {
+                    String paramName = paramDef.getKey();
+                    Object value = paramDef.getValue();
+                    if (value==null) value = Collections.emptyMap();
+                    if (!(value instanceof Map)) {
+                        if (value instanceof CharSequence && Strings.isBlank((CharSequence) value)) 
+                            value = Collections.emptyMap();
+                    }
+                    if (!(value instanceof Map))
+                        throw new IllegalArgumentException("Illegal argument of type "+value.getClass()+" value '"+value+"' supplied as parameter definition "
+                            + "'"+paramName);
+                    eff.parameter(ConfigKeys.DynamicKeys.newNamedInstance(paramName, (Map<?, ?>) value));
+                }
+            }
+        }
+        
+        return eff;
+    }
+
+    /** returns a ConfigBag containing the merger of the supplied parameters with default values on the effector-defined parameters */
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    public static ConfigBag getMergedParams(Effector<?> eff, ConfigBag params) {
+        ConfigBag result = ConfigBag.newInstanceCopying(params);
+        for (ParameterType<?> param: eff.getParameters()) {
+            ConfigKey key = Effectors.asConfigKey(param);
+            if (!result.containsKey(key))
+                result.configure(key, params.get(key));
+        }
+        return result;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/8dbb0e4b/core/src/main/java/org/apache/brooklyn/core/effector/AddSensor.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/AddSensor.java b/core/src/main/java/org/apache/brooklyn/core/effector/AddSensor.java
new file mode 100644
index 0000000..3c08831
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/core/effector/AddSensor.java
@@ -0,0 +1,126 @@
+/*
+ * 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;
+
+import java.util.Map;
+
+import org.apache.brooklyn.api.entity.EntityInitializer;
+import org.apache.brooklyn.api.entity.EntityLocal;
+import org.apache.brooklyn.api.sensor.AttributeSensor;
+import org.apache.brooklyn.config.ConfigKey;
+import org.apache.brooklyn.core.config.ConfigKeys;
+import org.apache.brooklyn.core.entity.EntityInternal;
+import org.apache.brooklyn.sensor.core.Sensors;
+import org.apache.brooklyn.util.core.config.ConfigBag;
+import org.apache.brooklyn.util.guava.Maybe;
+import org.apache.brooklyn.util.javalang.Boxing;
+import org.apache.brooklyn.util.time.Duration;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.Preconditions;
+
+/**
+ * Creates a new {@link AttributeSensor} on an entity.
+ * <p>
+ * The configuration can include the sensor {@code name}, {@code period} and {@code targetType}.
+ * For the targetType, currently this only supports classes on the initial classpath, not those in
+ * OSGi bundles added at runtime.
+ *
+ * @since 0.7.0
+ */
+@Beta
+public class AddSensor<T> implements EntityInitializer {
+
+    public static final ConfigKey<String> SENSOR_NAME = ConfigKeys.newStringConfigKey("name", "The name of the sensor to create");
+    public static final ConfigKey<Duration> SENSOR_PERIOD = ConfigKeys.newConfigKey(Duration.class, "period", "Period, including units e.g. 1m or 5s or 200ms; default 5 minutes", Duration.FIVE_MINUTES);
+    public static final ConfigKey<String> SENSOR_TYPE = ConfigKeys.newStringConfigKey("targetType", "Target type for the value; default String", "java.lang.String");
+
+    protected final String name;
+    protected final Duration period;
+    protected final String type;
+    protected final AttributeSensor<T> sensor;
+
+    public AddSensor(Map<String, String> params) {
+        this(ConfigBag.newInstance(params));
+    }
+
+    public AddSensor(final ConfigBag params) {
+        this.name = Preconditions.checkNotNull(params.get(SENSOR_NAME), "Name must be supplied when defining a sensor");
+        this.period = params.get(SENSOR_PERIOD);
+        this.type = params.get(SENSOR_TYPE);
+        this.sensor = newSensor();
+    }
+
+    @Override
+    public void apply(EntityLocal entity) {
+        ((EntityInternal) entity).getMutableEntityType().addSensor(sensor);
+    }
+
+    private AttributeSensor<T> newSensor() {
+        String className = getFullClassName(type);
+        Class<T> clazz = getType(className);
+        return Sensors.newSensor(clazz, name);
+    }
+
+    @SuppressWarnings("unchecked")
+    protected Class<T> getType(String className) {
+        try {
+            // TODO use OSGi loader (low priority however); also ensure that allows primitives
+            Maybe<Class<?>> primitive = Boxing.getPrimitiveType(className);
+            if (primitive.isPresent()) return (Class<T>) primitive.get();
+            return (Class<T>) Class.forName(className);
+        } catch (ClassNotFoundException e) {
+            if (!className.contains(".")) {
+                // could be assuming "java.lang" package; try again with that
+                try {
+                    return (Class<T>) Class.forName("java.lang."+className);
+                } catch (ClassNotFoundException e2) {
+                    throw new IllegalArgumentException("Invalid target type for sensor "+name+": " + className+" (also tried java.lang."+className+")");
+                }
+            } else {
+                throw new IllegalArgumentException("Invalid target type for sensor "+name+": " + className);
+            }
+        }
+    }
+
+    protected String getFullClassName(String className) {
+        if (className.equalsIgnoreCase("string")) {
+            return "java.lang.String";
+        } else if (className.equalsIgnoreCase("int") || className.equalsIgnoreCase("integer")) {
+            return "java.lang.Integer";
+        } else if (className.equalsIgnoreCase("long")) {
+            return "java.lang.Long";
+        } else if (className.equalsIgnoreCase("float")) {
+            return "java.lang.Float";
+        } else if (className.equalsIgnoreCase("double")) {
+            return "java.lang.Double";
+        } else if (className.equalsIgnoreCase("bool") || className.equalsIgnoreCase("boolean")) {
+            return "java.lang.Boolean";
+        } else if (className.equalsIgnoreCase("byte")) {
+            return "java.lang.Byte";
+        } else if (className.equalsIgnoreCase("char") || className.equalsIgnoreCase("character")) {
+            return "java.lang.Character";
+        } else if (className.equalsIgnoreCase("object")) {
+            return "java.lang.Object";
+        } else {
+            return className;
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/8dbb0e4b/core/src/main/java/org/apache/brooklyn/core/effector/BasicParameterType.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/BasicParameterType.java b/core/src/main/java/org/apache/brooklyn/core/effector/BasicParameterType.java
new file mode 100644
index 0000000..eb0417f
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/core/effector/BasicParameterType.java
@@ -0,0 +1,116 @@
+/*
+ * 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;
+
+import java.util.Collections;
+import java.util.Map;
+
+import org.apache.brooklyn.api.effector.ParameterType;
+
+import com.google.common.base.Objects;
+
+public class BasicParameterType<T> implements ParameterType<T> {
+    private static final long serialVersionUID = -5521879180483663919L;
+    
+    private String name;
+    private Class<T> type;
+    private String description;
+    private Boolean hasDefaultValue = null;
+    private T defaultValue = null;
+
+    public BasicParameterType() {
+        this(Collections.emptyMap());
+    }
+    
+    @SuppressWarnings("unchecked")
+    public BasicParameterType(Map<?, ?> arguments) {
+        if (arguments.containsKey("name")) name = (String) arguments.get("name");
+        if (arguments.containsKey("type")) type = (Class<T>) arguments.get("type");
+        if (arguments.containsKey("description")) description = (String) arguments.get("description");
+        if (arguments.containsKey("defaultValue")) defaultValue = (T) arguments.get("defaultValue");
+    }
+
+    public BasicParameterType(String name, Class<T> type) {
+        this(name, type, null, null, false);
+    }
+    
+    public BasicParameterType(String name, Class<T> type, String description) {
+        this(name, type, description, null, false);
+    }
+    
+    public BasicParameterType(String name, Class<T> type, String description, T defaultValue) {
+        this(name, type, description, defaultValue, true);
+    }
+    
+    public BasicParameterType(String name, Class<T> type, String description, T defaultValue, boolean hasDefaultValue) {
+        this.name = name;
+        this.type = type;
+        this.description = description;
+        this.defaultValue = defaultValue;
+        if (defaultValue!=null && !defaultValue.getClass().equals(Object.class)) {
+            // if default value is null (or is an Object, which is ambiguous on resolution to to rebind), 
+            // don't bother to set this as it creates noise in the persistence files
+            this.hasDefaultValue = hasDefaultValue;
+        }
+    }
+
+    @Override
+    public String getName() { return name; }
+
+    @Override
+    public Class<T> getParameterClass() { return type; }
+    
+    @Override
+    public String getParameterClassName() { return type.getCanonicalName(); }
+
+    @Override
+    public String getDescription() { return description; }
+
+    @Override
+    public T getDefaultValue() {
+        return hasDefaultValue() ? defaultValue : null;
+    }
+
+    public boolean hasDefaultValue() {
+        // a new Object() was previously used to indicate no default value, but that doesn't work well across serialization boundaries!
+        return hasDefaultValue!=null ? hasDefaultValue : defaultValue!=null && !defaultValue.getClass().equals(Object.class);
+    }
+    
+    @Override
+    public String toString() {
+        return Objects.toStringHelper(this).omitNullValues()
+                .add("name", name).add("description", description).add("type", getParameterClassName())
+                .add("defaultValue", defaultValue)
+                .toString();
+    }
+    
+    @Override
+    public int hashCode() {
+        return Objects.hashCode(name, description, type, defaultValue);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        return (obj instanceof ParameterType) &&
+                Objects.equal(name, ((ParameterType<?>)obj).getName()) &&
+                Objects.equal(description, ((ParameterType<?>)obj).getDescription()) &&
+                Objects.equal(type, ((ParameterType<?>)obj).getParameterClass()) &&
+                Objects.equal(defaultValue, ((ParameterType<?>)obj).getDefaultValue());
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/8dbb0e4b/core/src/main/java/org/apache/brooklyn/core/effector/EffectorAndBody.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/EffectorAndBody.java b/core/src/main/java/org/apache/brooklyn/core/effector/EffectorAndBody.java
new file mode 100644
index 0000000..49e85b8
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/core/effector/EffectorAndBody.java
@@ -0,0 +1,60 @@
+/*
+ * 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;
+
+import java.util.List;
+
+import org.apache.brooklyn.api.effector.Effector;
+import org.apache.brooklyn.api.effector.ParameterType;
+import org.apache.brooklyn.core.effector.EffectorTasks.EffectorTaskFactory;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.Objects;
+
+@Beta // added in 0.6.0
+public class EffectorAndBody<T> extends EffectorBase<T> implements EffectorWithBody<T> {
+
+    private static final long serialVersionUID = -6023389678748222968L;
+    private final EffectorTaskFactory<T> body;
+
+    public EffectorAndBody(Effector<T> original, EffectorTaskFactory<T> body) {
+        this(original.getName(), original.getReturnType(), original.getParameters(), original.getDescription(), body);
+    }
+    
+    public EffectorAndBody(String name, Class<T> returnType, List<ParameterType<?>> parameters, String description, EffectorTaskFactory<T> body) {
+        super(name, returnType, parameters, description);
+        this.body = body;
+    }
+
+    @Override
+    public EffectorTaskFactory<T> getBody() {
+        return body;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hashCode(super.hashCode(), getBody());
+    }
+    
+    @Override
+    public boolean equals(Object other) {
+        return super.equals(other) && Objects.equal(getBody(), ((EffectorAndBody<?>)other).getBody());
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/8dbb0e4b/core/src/main/java/org/apache/brooklyn/core/effector/EffectorBase.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/EffectorBase.java b/core/src/main/java/org/apache/brooklyn/core/effector/EffectorBase.java
new file mode 100644
index 0000000..68132c4
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/core/effector/EffectorBase.java
@@ -0,0 +1,106 @@
+/*
+ * 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;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.brooklyn.api.effector.Effector;
+import org.apache.brooklyn.api.effector.ParameterType;
+import org.apache.brooklyn.core.effector.EffectorTasks.EffectorTaskFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Joiner;
+import com.google.common.base.Objects;
+
+/** concrete implementation of Effector interface, 
+ * but not (at this level of the hirarchy) defining an implementation 
+ * (see {@link EffectorTaskFactory} and {@link EffectorWithBody}) */
+public class EffectorBase<T> implements Effector<T> {
+
+    @SuppressWarnings("unused")
+    private static final Logger log = LoggerFactory.getLogger(EffectorBase.class);
+    
+    private static final long serialVersionUID = -4153962199078384835L;
+    
+    private final String name;
+    private final Class<T> returnType;
+    private final List<ParameterType<?>> parameters;
+    private final String description;
+
+    public EffectorBase(String name, Class<T> returnType, List<ParameterType<?>> parameters, String description) {
+        this.name = name;
+        this.returnType = returnType;
+        this.parameters = new ArrayList<ParameterType<?>>(parameters);
+        this.description = description;
+    }
+
+    @Override
+    public String getName() {
+        return name;
+    }
+
+    @Override
+    public Class<T> getReturnType() {
+        return returnType;
+    }
+
+    @Override
+    public String getReturnTypeName() {
+        return returnType.getCanonicalName();
+    }
+
+    @Override
+    public List<ParameterType<?>> getParameters() {
+        return parameters;
+    }
+
+    @Override
+    public String getDescription() {
+        return description;
+    }
+
+    @Override
+    public String toString() {
+        List<String> parameterNames = new ArrayList<String>(parameters.size());
+        for (ParameterType<?> parameter: parameters) {
+            String parameterName = (parameter.getName() != null) ? parameter.getName() : "<unknown>";
+            parameterNames.add(parameterName);
+        }
+        return name+"["+Joiner.on(",").join(parameterNames)+"]";
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hashCode(name, returnType, parameters, description);
+    }
+    
+    @Override
+    public boolean equals(Object other) {
+        if (!(other instanceof EffectorBase)) return false;
+        if (!(other.getClass().equals(getClass()))) return false;
+        if (!Objects.equal(hashCode(), other.hashCode())) return false;
+        return Objects.equal(getName(), ((EffectorBase<?>)other).getName()) &&
+            Objects.equal(getReturnType(), ((EffectorBase<?>)other).getReturnType()) &&
+            Objects.equal(getParameters(), ((EffectorBase<?>)other).getParameters()) &&
+            Objects.equal(getDescription(), ((EffectorBase<?>)other).getDescription());
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/8dbb0e4b/core/src/main/java/org/apache/brooklyn/core/effector/EffectorBody.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/EffectorBody.java b/core/src/main/java/org/apache/brooklyn/core/effector/EffectorBody.java
new file mode 100644
index 0000000..b1643ba
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/core/effector/EffectorBody.java
@@ -0,0 +1,100 @@
+/*
+ * 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;
+
+import org.apache.brooklyn.api.mgmt.Task;
+import org.apache.brooklyn.api.mgmt.TaskAdaptable;
+import org.apache.brooklyn.api.mgmt.TaskFactory;
+import org.apache.brooklyn.core.entity.EntityInternal;
+import org.apache.brooklyn.core.mgmt.BrooklynTaskTags;
+import org.apache.brooklyn.util.core.config.ConfigBag;
+import org.apache.brooklyn.util.core.flags.TypeCoercions;
+import org.apache.brooklyn.util.core.task.DynamicSequentialTask;
+import org.apache.brooklyn.util.core.task.DynamicTasks;
+import org.apache.brooklyn.util.core.task.Tasks;
+
+import com.google.common.annotations.Beta;
+
+/** Typical implementations override {@link #main(ConfigBag)} to do the work of the effector
+ * <p>
+ * See also {@link EffectorTasks}: possibly this will be deleted in preference for an approach based on {@link EffectorTasks}. 
+ * 
+ * @since 0.6.0
+ **/
+@Beta
+public abstract class EffectorBody<T> {
+    /** Does the work of the effector, either in place, or (better) by building up
+     * subtasks, which can by added using {@link DynamicTasks} methods
+     * (and various convenience methods which do that automatically; see subclasses of EffectorBody 
+     * for more info on usage; or see {@link DynamicSequentialTask} for details of the threading model
+     * by which added tasks are placed in a secondary thread)
+     * <p>
+     * The associated entity can be accessed through the {@link #entity()} method.
+     */
+    public abstract T call(ConfigBag parameters);
+    
+    // NB: we could also support an 'init' method which is done at creation,
+    // as a place where implementers can describe the structure of the task before it executes
+    // (and init gets invoked in EffectorBodyTaskFactory.newTask _before_ the task is submitted and main is called)
+    
+    
+    // ---- convenience method(s) for implementers of main -- see subclasses and *Tasks statics for more
+    
+    protected EntityInternal entity() {
+        return (EntityInternal) BrooklynTaskTags.getTargetOrContextEntity(Tasks.current());
+    }
+    
+    protected <V extends TaskAdaptable<?>> V queue(V task) {
+        return DynamicTasks.queue(task);
+    }
+
+    protected <V extends TaskAdaptable<?>> void queue(V task1, V task2, V ...tasks) {
+        DynamicTasks.queue(task1);
+        DynamicTasks.queue(task2);
+        for (V task: tasks)
+            DynamicTasks.queue(task);
+    }
+
+    protected <V extends TaskFactory<?>> void queue(V task1, V task2, V ...tasks) {
+        DynamicTasks.queue(task1.newTask());
+        DynamicTasks.queue(task2.newTask());
+        for (V task: tasks)
+            DynamicTasks.queue(task.newTask());
+    }
+    
+    protected <U extends TaskAdaptable<?>> U queue(TaskFactory<U> task) {
+        return DynamicTasks.queue(task.newTask());
+    }
+    
+    /** see {@link DynamicTasks#waitForLast()} */
+    protected Task<?> waitForLast() {
+        return DynamicTasks.waitForLast();
+    }
+
+    /** Returns the result of the last task queued in this context, coerced to the given type */
+    protected <V> V last(Class<V> type) {
+        Task<?> last = waitForLast();
+        if (last==null)
+            throw new IllegalStateException("No last task available (in "+DynamicTasks.getTaskQueuingContext()+")");
+        if (!Tasks.isQueuedOrSubmitted(last))
+            throw new IllegalStateException("Last task "+last+" has not been queued or submitted; will not block on its result");
+        
+        return TypeCoercions.coerce(last.getUnchecked(), type);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/8dbb0e4b/core/src/main/java/org/apache/brooklyn/core/effector/EffectorTasks.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/EffectorTasks.java b/core/src/main/java/org/apache/brooklyn/core/effector/EffectorTasks.java
new file mode 100644
index 0000000..39eb79b
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/core/effector/EffectorTasks.java
@@ -0,0 +1,229 @@
+/*
+ * 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;
+
+import java.util.Map;
+import java.util.concurrent.Callable;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.apache.brooklyn.api.effector.Effector;
+import org.apache.brooklyn.api.effector.ParameterType;
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.mgmt.Task;
+import org.apache.brooklyn.api.mgmt.TaskAdaptable;
+import org.apache.brooklyn.config.ConfigKey;
+import org.apache.brooklyn.core.config.ConfigKeys;
+import org.apache.brooklyn.core.location.Machines;
+import org.apache.brooklyn.core.mgmt.BrooklynTaskTags;
+import org.apache.brooklyn.core.mgmt.internal.EffectorUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.apache.brooklyn.location.ssh.SshMachineLocation;
+import org.apache.brooklyn.location.winrm.WinRmMachineLocation;
+import org.apache.brooklyn.util.core.config.ConfigBag;
+import org.apache.brooklyn.util.core.task.DynamicSequentialTask;
+import org.apache.brooklyn.util.core.task.DynamicTasks;
+import org.apache.brooklyn.util.core.task.TaskBuilder;
+import org.apache.brooklyn.util.core.task.Tasks;
+import org.apache.brooklyn.util.javalang.Reflections;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.Preconditions;
+
+/**
+ * Miscellaneous tasks which are useful in effectors.
+ * @since 0.6.0
+ */
+@Beta
+public class EffectorTasks {
+
+    @SuppressWarnings("unused")
+    private static final Logger log = LoggerFactory.getLogger(EffectorTasks.class);
+    
+    public interface EffectorTaskFactory<T> {
+        public abstract TaskAdaptable<T> newTask(Entity entity, Effector<T> effector, ConfigBag parameters);
+    }
+    
+    /** wrapper for {@link EffectorBody} which simply runs that body on each invocation;
+     * the body must be thread safe and ideally stateless */
+    public static class EffectorBodyTaskFactory<T> implements EffectorTaskFactory<T> {
+        private final EffectorBody<T> effectorBody;
+        public EffectorBodyTaskFactory(EffectorBody<T> effectorBody) {
+            this.effectorBody = effectorBody;
+        }
+        
+        @Override
+        public Task<T> newTask(final Entity entity, final org.apache.brooklyn.api.effector.Effector<T> effector, final ConfigBag parameters) {
+            final AtomicReference<DynamicSequentialTask<T>> dst = new AtomicReference<DynamicSequentialTask<T>>();
+
+            dst.set(new DynamicSequentialTask<T>(
+                    getFlagsForTaskInvocationAt(entity, effector, parameters), 
+                    new Callable<T>() {
+                        @Override
+                        public T call() throws Exception {
+                            try {
+                                DynamicTasks.setTaskQueueingContext(dst.get());
+                                return effectorBody.call(parameters);
+                            } finally {
+                                DynamicTasks.removeTaskQueueingContext();
+                            }
+                        }
+                    }) {
+                        @Override
+                        public void handleException(Throwable throwable) throws Exception {
+                            EffectorUtils.handleEffectorException(entity, effector, throwable);
+                        }
+                    });
+            return dst.get();
+        };
+
+        /** @deprecated since 0.7.0 use {@link #getFlagsForTaskInvocationAt(Entity, Effector, ConfigBag)} */ @Deprecated
+        protected final Map<Object,Object> getFlagsForTaskInvocationAt(Entity entity, Effector<?> effector) {
+            return getFlagsForTaskInvocationAt(entity, effector, null);
+        }
+        /** subclasses may override to add additional flags, but they should include the flags returned here 
+         * unless there is very good reason not to; default impl returns a MutableMap */
+        protected Map<Object,Object> getFlagsForTaskInvocationAt(Entity entity, Effector<?> effector, ConfigBag parameters) {
+            return EffectorUtils.getTaskFlagsForEffectorInvocation(entity, effector, parameters);
+        }
+    }
+    
+    /** wrapper for {@link EffectorTaskFactory} which ensures effector task tags are applied to it if needed
+     * (wrapping in a task if needed); without this, {@link EffectorBody}-based effectors get it by
+     * virtue of the call to {@link #getFlagsForTaskInvocationAt(Entity,Effector,ConfigBag)} therein
+     * but {@link EffectorTaskFactory}-based effectors generate a task without the right tags
+     * to be able to tell using {@link BrooklynTaskTags} the effector-context of the task 
+     * <p>
+     * this gets applied automatically so marked as package-private */
+    static class EffectorMarkingTaskFactory<T> implements EffectorTaskFactory<T> {
+        private final EffectorTaskFactory<T> effectorTaskFactory;
+        public EffectorMarkingTaskFactory(EffectorTaskFactory<T> effectorTaskFactory) {
+            this.effectorTaskFactory = effectorTaskFactory;
+        }
+        
+        @Override
+        public Task<T> newTask(final Entity entity, final org.apache.brooklyn.api.effector.Effector<T> effector, final ConfigBag parameters) {
+            if (effectorTaskFactory instanceof EffectorBodyTaskFactory)
+                return effectorTaskFactory.newTask(entity, effector, parameters).asTask();
+            // if we're in an effector context for this effector already, then also pass through
+            if (BrooklynTaskTags.isInEffectorTask(Tasks.current(), entity, effector, false))
+                return effectorTaskFactory.newTask(entity, effector, parameters).asTask();
+            // otherwise, create the task inside an appropriate effector body so tags, name, etc are set correctly
+            return new EffectorBodyTaskFactory<T>(new EffectorBody<T>() {
+                @Override
+                public T call(ConfigBag parameters) {
+                    TaskAdaptable<T> t = DynamicTasks.queue(effectorTaskFactory.newTask(entity, effector, parameters));
+                    return t.asTask().getUnchecked();
+                }
+            }).newTask(entity, effector, parameters);
+        }
+    }
+    
+    public static <T> ConfigKey<T> asConfigKey(ParameterType<T> t) {
+        return ConfigKeys.newConfigKey(t.getParameterClass(), t.getName());
+    }
+    
+    public static <T> ParameterTask<T> parameter(ParameterType<T> t) {
+        return new ParameterTask<T>(asConfigKey(t)).
+                name("parameter "+t);
+    }
+    public static <T> ParameterTask<T> parameter(Class<T> type, String name) {
+        return new ParameterTask<T>(ConfigKeys.newConfigKey(type, name)).
+                name("parameter "+name+" ("+type+")");
+    }
+    public static <T> ParameterTask<T> parameter(final ConfigKey<T> p) {
+        return new ParameterTask<T>(p);
+    }
+    public static class ParameterTask<T> implements EffectorTaskFactory<T> {
+        final ConfigKey<T> p;
+        private TaskBuilder<T> builder;
+        public ParameterTask(ConfigKey<T> p) {
+            this.p = p;
+            this.builder = Tasks.<T>builder().name("parameter "+p);
+        }
+        public ParameterTask<T> name(String taskName) {
+            builder.name(taskName);
+            return this;
+        }
+        @Override
+        public Task<T> newTask(Entity entity, Effector<T> effector, final ConfigBag parameters) {
+            return builder.body(new Callable<T>() {
+                @Override
+                public T call() throws Exception {
+                    return parameters.get(p);
+                }
+                
+            }).build();
+        }
+        
+    }
+
+    public static <T> EffectorTaskFactory<T> of(final Task<T> task) {
+        return new EffectorTaskFactory<T>() {
+            @Override
+            public Task<T> newTask(Entity entity, Effector<T> effector, ConfigBag parameters) {
+                return task;
+            }
+        };
+    }
+
+    /** Finds the entity where this task is running
+     * @throws NullPointerException if there is none (no task, or no context entity for that task) */
+    public static Entity findEntity() {
+        return Preconditions.checkNotNull(BrooklynTaskTags.getTargetOrContextEntity(Tasks.current()),
+                "This must be executed in a task whose execution context has a target or context entity " +
+                "(i.e. it must be run from within an effector)");
+    }
+
+    /** Finds the entity where this task is running, casted to the given Entity subtype
+     * @throws NullPointerException if there is none
+     * @throws IllegalArgumentException if it is not of the indicated type */
+    public static <T extends Entity> T findEntity(Class<T> type) {
+        Entity t = findEntity();
+        return Reflections.cast(t, type);
+    }
+
+    /** Finds a unique {@link SshMachineLocation} attached to the entity 
+     * where this task is running
+     * @throws NullPointerException if {@link #findEntity()} fails
+     * @throws IllegalStateException if call to {@link #getSshMachine(Entity)} fails */
+    public static SshMachineLocation findSshMachine() {
+        return getSshMachine(findEntity());
+    }
+
+    /** Finds a unique {@link SshMachineLocation} attached to the supplied entity 
+     * @throws IllegalStateException if there is not a unique such {@link SshMachineLocation} */
+    public static SshMachineLocation getSshMachine(Entity entity) {
+        try {
+            return Machines.findUniqueSshMachineLocation(entity.getLocations()).get();
+        } catch (Exception e) {
+            throw new IllegalStateException("Entity "+entity+" (in "+Tasks.current()+") requires a single SshMachineLocation, but has "+entity.getLocations(), e);
+        }
+    }
+
+    /** Finds a unique {@link WinRmMachineLocation} attached to the supplied entity
+     * @throws IllegalStateException if there is not a unique such {@link WinRmMachineLocation} */
+    public static WinRmMachineLocation getWinRmMachine(Entity entity) {
+        try {
+            return Machines.findUniqueWinRmMachineLocation(entity.getLocations()).get();
+        } catch (Exception e) {
+            throw new IllegalStateException("Entity "+entity+" (in "+Tasks.current()+") requires a single WinRmMachineLocation, but has "+entity.getLocations(), e);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/8dbb0e4b/core/src/main/java/org/apache/brooklyn/core/effector/EffectorWithBody.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/EffectorWithBody.java b/core/src/main/java/org/apache/brooklyn/core/effector/EffectorWithBody.java
new file mode 100644
index 0000000..67dba14
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/core/effector/EffectorWithBody.java
@@ -0,0 +1,32 @@
+/*
+ * 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;
+
+import org.apache.brooklyn.api.effector.Effector;
+import org.apache.brooklyn.core.effector.EffectorTasks.EffectorTaskFactory;
+
+import com.google.common.annotations.Beta;
+
+@Beta // added in 0.6.0
+public interface EffectorWithBody<T> extends Effector<T> {
+
+    /** returns the body of the effector, i.e. a factory which can generate tasks which can run */
+    public EffectorTaskFactory<T> getBody();
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/8dbb0e4b/core/src/main/java/org/apache/brooklyn/core/effector/Effectors.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/Effectors.java b/core/src/main/java/org/apache/brooklyn/core/effector/Effectors.java
new file mode 100644
index 0000000..63ea52d
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/core/effector/Effectors.java
@@ -0,0 +1,202 @@
+/*
+ * 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;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.annotation.Nullable;
+
+import org.apache.brooklyn.api.effector.Effector;
+import org.apache.brooklyn.api.effector.ParameterType;
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.entity.EntityLocal;
+import org.apache.brooklyn.api.mgmt.TaskAdaptable;
+import org.apache.brooklyn.config.ConfigKey;
+import org.apache.brooklyn.core.config.ConfigKeys;
+import org.apache.brooklyn.core.effector.EffectorTasks.EffectorBodyTaskFactory;
+import org.apache.brooklyn.core.effector.EffectorTasks.EffectorMarkingTaskFactory;
+import org.apache.brooklyn.core.effector.EffectorTasks.EffectorTaskFactory;
+import org.apache.brooklyn.core.entity.Entities;
+import org.apache.brooklyn.core.entity.EntityInternal;
+import org.apache.brooklyn.util.collections.MutableMap;
+import org.apache.brooklyn.util.core.config.ConfigBag;
+import org.apache.brooklyn.util.core.task.Tasks;
+import org.apache.brooklyn.util.text.Strings;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Objects;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+public class Effectors {
+
+    private static final Logger log = LoggerFactory.getLogger(Effectors.class);
+    
+    public static class EffectorBuilder<T> {
+        private Class<T> returnType;
+        private String effectorName;
+        private String description;
+        private Map<String,ParameterType<?>> parameters = new LinkedHashMap<String,ParameterType<?>>();
+        private EffectorTaskFactory<T> impl;
+        
+        private EffectorBuilder(Class<T> returnType, String effectorName) {
+            this.returnType = returnType;
+            this.effectorName = effectorName;
+        }
+        public EffectorBuilder<T> description(String description) {
+            this.description = description;
+            return this;                
+        }
+        public EffectorBuilder<T> parameter(Class<?> paramType, String paramName) {
+            return parameter(paramType, paramName, null, null);
+        }
+        public EffectorBuilder<T> parameter(Class<?> paramType, String paramName, String paramDescription) {
+            return parameter(paramType, paramName, paramDescription, null);                
+        }
+        public <V> EffectorBuilder<T> parameter(Class<V> paramType, String paramName, String paramDescription, V defaultValue) {
+            return parameter(new BasicParameterType<V>(paramName, paramType, paramDescription, defaultValue));
+        }
+        public <V> EffectorBuilder<T> parameter(ConfigKey<V> key) {
+            return parameter(asParameterType(key));
+        }
+        public EffectorBuilder<T> parameter(ParameterType<?> p) {
+            // allow redeclaring, e.g. for the case where we are overriding an existing effector
+            parameters.put(p.getName(), p);
+            return this;
+        }
+        public EffectorBuilder<T> impl(EffectorTaskFactory<T> taskFactory) {
+            this.impl = new EffectorMarkingTaskFactory<T>(taskFactory);
+            return this;
+        }
+        public EffectorBuilder<T> impl(EffectorBody<T> effectorBody) {
+            this.impl = new EffectorBodyTaskFactory<T>(effectorBody);
+            return this;
+        }
+        /** returns the effector, with an implementation (required); @see {@link #buildAbstract()} */
+        public Effector<T> build() {
+             Preconditions.checkNotNull(impl, "Cannot create effector %s with no impl (did you forget impl? or did you mean to buildAbstract?)", effectorName);
+             return new EffectorAndBody<T>(effectorName, returnType, ImmutableList.copyOf(parameters.values()), description, impl);
+        }
+        
+        /** returns an abstract effector, where the body will be defined later/elsewhere 
+         * (impl must not be set) */
+        public Effector<T> buildAbstract() {
+            Preconditions.checkArgument(impl==null, "Cannot create abstract effector {} as an impl is defined", effectorName);
+            return new EffectorBase<T>(effectorName, returnType, ImmutableList.copyOf(parameters.values()), description);
+        }
+    }
+
+    /** creates a new effector builder with the given name and return type */
+    public static <T> EffectorBuilder<T> effector(Class<T> returnType, String effectorName) {
+        return new EffectorBuilder<T>(returnType, effectorName);
+    }
+
+    /** creates a new effector builder to _override_ the given effector */
+    public static <T> EffectorBuilder<T> effector(Effector<T> base) {
+        EffectorBuilder<T> builder = new EffectorBuilder<T>(base.getReturnType(), base.getName());
+        for (ParameterType<?> p: base.getParameters())
+            builder.parameter(p);
+        builder.description(base.getDescription());
+        if (base instanceof EffectorWithBody)
+            builder.impl(((EffectorWithBody<T>) base).getBody());
+        return builder;
+    }
+
+    /** as {@link #invocation(Entity, Effector, Map)} but convenience for passing a {@link ConfigBag} */
+    public static <T> TaskAdaptable<T> invocation(Entity entity, Effector<T> eff, ConfigBag parameters) {
+        return invocation(entity, eff, parameters==null ? ImmutableMap.of() : parameters.getAllConfig());
+    }
+    
+    /** returns an unsubmitted task which invokes the given effector; use {@link Entities#invokeEffector(EntityLocal, Entity, Effector, Map)} for a submitted variant */
+    public static <T> TaskAdaptable<T> invocation(Entity entity, Effector<T> eff, @Nullable Map<?,?> parameters) {
+        @SuppressWarnings("unchecked")
+        Effector<T> eff2 = (Effector<T>) ((EntityInternal)entity).getEffector(eff.getName());
+        if (log.isTraceEnabled()) {
+            Object eff1Body = (eff instanceof EffectorWithBody<?> ? ((EffectorWithBody<?>) eff).getBody() : "bodyless");
+            String message = String.format("Invoking %s/%s on entity %s", eff, eff1Body, entity);
+            if (eff != eff2) {
+                Object eff2Body = (eff2 instanceof EffectorWithBody<?> ? ((EffectorWithBody<?>) eff2).getBody() : "bodyless");
+                message += String.format(" (actually %s/%s)", eff2, eff2Body);
+            }
+            log.trace(message);
+        }
+        if (eff2 != null) {
+            if (eff2 != eff) {
+                if (eff2 instanceof EffectorWithBody) {
+                    log.debug("Replacing invocation of {} on {} with {} which is the impl defined at that entity", new Object[] { eff, entity, eff2 });
+                    return ((EffectorWithBody<T>)eff2).getBody().newTask(entity, eff2, ConfigBag.newInstance().putAll(parameters));
+                } else {
+                    log.warn("Effector {} defined on {} has no body; invoking caller-supplied {} instead", new Object[] { eff2, entity, eff });
+                }
+            }
+        } else {
+            log.debug("Effector {} does not exist on {}; attempting to invoke anyway", new Object[] { eff, entity });
+        }
+        
+        if (eff instanceof EffectorWithBody) {
+            return ((EffectorWithBody<T>)eff).getBody().newTask(entity, eff, ConfigBag.newInstance().putAll(parameters));
+        }
+        
+        throw new UnsupportedOperationException("No implementation registered for effector "+eff+" on "+entity);
+    }    
+
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    public static <V> ParameterType<V> asParameterType(ConfigKey<V> key) {
+        return key.hasDefaultValue()
+            ? new BasicParameterType<V>(key.getName(), (Class)key.getType(), key.getDescription(), key.getDefaultValue())
+            : new BasicParameterType<V>(key.getName(), (Class)key.getType(), key.getDescription());
+    }
+    
+    public static <V> ConfigKey<V> asConfigKey(ParameterType<V> paramType) {
+        return ConfigKeys.newConfigKey(paramType.getParameterClass(), paramType.getName(), paramType.getDescription(), paramType.getDefaultValue());
+    }
+
+    /** returns an unsubmitted task which will invoke the given effector on the given entities;
+     * return type is Task<List<T>> (but haven't put in the blood sweat toil and tears to make the generics work) */
+    public static TaskAdaptable<List<?>> invocation(Effector<?> eff, Map<?,?> params, Iterable<? extends Entity> entities) {
+        List<TaskAdaptable<?>> tasks = new ArrayList<TaskAdaptable<?>>();
+        for (Entity e: entities) tasks.add(invocation(e, eff, params));
+        return Tasks.parallel("invoking "+eff+" on "+tasks.size()+" node"+(Strings.s(tasks.size())), tasks.toArray(new TaskAdaptable[tasks.size()]));
+    }
+
+    /** returns an unsubmitted task which will invoke the given effector on the given entities
+     * (this form of method is a convenience for {@link #invocation(Effector, Map, Iterable)}) */
+    public static TaskAdaptable<List<?>> invocation(Effector<?> eff, MutableMap<?, ?> params, Entity ...entities) {
+        return invocation(eff, params, Arrays.asList(entities));
+    }
+    
+    public static boolean sameSignature(Effector<?> e1, Effector<?> e2) {
+        return Objects.equal(e1.getName(), e2.getName()) &&
+                Objects.equal(e1.getParameters(), e2.getParameters()) &&
+                Objects.equal(e1.getReturnType(), e2.getReturnType());
+    }
+    
+    // TODO sameSignatureAndBody
+    
+    public static boolean sameInstance(Effector<?> e1, Effector<?> e2) {
+        return e1 == e2;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/8dbb0e4b/core/src/main/java/org/apache/brooklyn/core/effector/ExplicitEffector.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/ExplicitEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/ExplicitEffector.java
new file mode 100644
index 0000000..65c1f0c
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/core/effector/ExplicitEffector.java
@@ -0,0 +1,74 @@
+/*
+ * 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;
+
+import groovy.lang.Closure;
+
+import java.util.List;
+import java.util.Map;
+
+import org.apache.brooklyn.api.effector.ParameterType;
+import org.apache.brooklyn.api.entity.Entity;
+
+import com.google.common.base.Objects;
+import com.google.common.collect.ImmutableList;
+
+public abstract class ExplicitEffector<I,T> extends AbstractEffector<T> {
+    public ExplicitEffector(String name, Class<T> type, String description) {
+        this(name, type, ImmutableList.<ParameterType<?>>of(), description);
+    }
+    public ExplicitEffector(String name, Class<T> type, List<ParameterType<?>> parameters, String description) {
+        super(name, type, parameters, description);
+    }
+
+    public T call(Entity entity, Map parameters) {
+        return invokeEffector((I) entity, (Map<String,?>)parameters );
+    }
+
+    public abstract T invokeEffector(I trait, Map<String,?> parameters);
+    
+    /** convenience to create an effector supplying a closure; annotations are preferred,
+     * and subclass here would be failback, but this is offered as 
+     * workaround for bug GROOVY-5122, as discussed in test class CanSayHi 
+     */
+    public static <I,T> ExplicitEffector<I,T> create(String name, Class<T> type, List<ParameterType<?>> parameters, String description, Closure body) {
+        return new ExplicitEffectorFromClosure<I,T>(name, type, parameters, description, body);
+    }
+    
+    private static class ExplicitEffectorFromClosure<I,T> extends ExplicitEffector<I,T> {
+        private static final long serialVersionUID = -5771188171702382236L;
+        final Closure<T> body;
+        public ExplicitEffectorFromClosure(String name, Class<T> type, List<ParameterType<?>> parameters, String description, Closure<T> body) {
+            super(name, type, parameters, description);
+            this.body = body;
+        }
+        public T invokeEffector(I trait, Map<String,?> parameters) { return body.call(trait, parameters); }
+        
+        @Override
+        public int hashCode() {
+            return Objects.hashCode(super.hashCode(), body);
+        }
+        
+        @Override
+        public boolean equals(Object other) {
+            return super.equals(other) && Objects.equal(body, ((ExplicitEffectorFromClosure<?,?>)other).body);
+        }
+        
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/8dbb0e4b/core/src/main/java/org/apache/brooklyn/core/effector/MethodEffector.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/MethodEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/MethodEffector.java
new file mode 100644
index 0000000..ad53adb
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/core/effector/MethodEffector.java
@@ -0,0 +1,180 @@
+/*
+ * 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;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.brooklyn.api.effector.Effector;
+import org.apache.brooklyn.api.effector.ParameterType;
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.core.annotation.EffectorParam;
+import org.apache.brooklyn.core.entity.AbstractEntity;
+import org.apache.brooklyn.core.mgmt.internal.EffectorUtils;
+import org.apache.brooklyn.util.core.flags.TypeCoercions;
+import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.apache.brooklyn.util.groovy.GroovyJavaMethods;
+import org.codehaus.groovy.runtime.MethodClosure;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.collect.Lists;
+
+/** concrete class for providing an Effector implementation that gets its information from annotations on a method;
+ * see Effector*Test for usage example.
+ * <p>
+ * note that the method must be on an interface in order for it to be remoted, with the current implementation.
+ * see comments in {@link #call(Entity, Map)} for more details.
+ */
+public class MethodEffector<T> extends AbstractEffector<T> {
+
+    private static final long serialVersionUID = 6989688364011965968L;
+    private static final Logger log = LoggerFactory.getLogger(MethodEffector.class);
+    
+    @SuppressWarnings("rawtypes")
+    public static Effector<?> create(Method m) {
+        return new MethodEffector(m);
+    }
+    
+    protected static class AnnotationsOnMethod {
+        final Class<?> clazz;
+        final String name;
+        final String description;
+        final Class<?> returnType;
+        final List<ParameterType<?>> parameters;
+
+        public AnnotationsOnMethod(Class<?> clazz, String methodName) {
+            this(clazz, inferBestMethod(clazz, methodName));
+        }
+
+        public AnnotationsOnMethod(Class<?> clazz, Method method) {
+            this.clazz = clazz;
+            this.name = method.getName();
+            this.returnType = method.getReturnType();
+
+            // Get the description
+            org.apache.brooklyn.core.annotation.Effector effectorAnnotation = method.getAnnotation(org.apache.brooklyn.core.annotation.Effector.class);
+            description = (effectorAnnotation != null) ? effectorAnnotation.description() : null;
+
+            // Get the parameters
+            parameters = Lists.newArrayList();
+            int numParameters = method.getParameterTypes().length;
+            for (int i = 0; i < numParameters; i++) {
+                parameters.add(toParameterType(method, i));
+            }
+        }
+
+        @SuppressWarnings({ "rawtypes", "unchecked" })
+        protected static ParameterType<?> toParameterType(Method method, int paramIndex) {
+            Annotation[] anns = method.getParameterAnnotations()[paramIndex];
+            Class<?> type = method.getParameterTypes()[paramIndex];
+            EffectorParam paramAnnotation = findAnnotation(anns, EffectorParam.class);
+
+            // TODO if blank, could do "param"+(i+1); would that be better?
+            // TODO this will now give "" if name is blank, rather than previously null. Is that ok?!
+            String name = (paramAnnotation != null) ? paramAnnotation.name() : null;
+
+            String paramDescription = (paramAnnotation == null || EffectorParam.MAGIC_STRING_MEANING_NULL.equals(paramAnnotation.description())) ? null : paramAnnotation.description();
+            String description = (paramDescription != null) ? paramDescription : null;
+
+            String paramDefaultValue = (paramAnnotation == null || EffectorParam.MAGIC_STRING_MEANING_NULL.equals(paramAnnotation.defaultValue())) ? null : paramAnnotation.defaultValue();
+            Object defaultValue = (paramDefaultValue != null) ? TypeCoercions.coerce(paramDefaultValue, type) : null;
+
+            return new BasicParameterType(name, type, description, defaultValue);
+        }
+        
+        @SuppressWarnings("unchecked")
+        protected static <T extends Annotation> T findAnnotation(Annotation[] anns, Class<T> type) {
+            for (Annotation ann : anns) {
+                if (type.isInstance(ann)) return (T) ann;
+            }
+            return null;
+        }
+        
+        protected static Method inferBestMethod(Class<?> clazz, String methodName) {
+            Method best = null;
+            for (Method it : clazz.getMethods()) { 
+                if (it.getName().equals(methodName)) {
+                    if (best==null || best.getParameterTypes().length < it.getParameterTypes().length) best=it;
+                }
+            }
+            if (best==null) {
+                throw new IllegalStateException("Cannot find method "+methodName+" on "+clazz.getCanonicalName());
+            }
+            return best;
+        }
+    }
+
+    /** Defines a new effector whose details are supplied as annotations on the given type and method name */
+    public MethodEffector(Class<?> whereEffectorDefined, String methodName) {
+        this(new AnnotationsOnMethod(whereEffectorDefined, methodName), null);
+    }
+
+    public MethodEffector(Method method) {
+        this(new AnnotationsOnMethod(method.getDeclaringClass(), method), null);
+    }
+
+    public MethodEffector(MethodClosure mc) {
+        this(new AnnotationsOnMethod((Class<?>)mc.getDelegate(), mc.getMethod()), null);
+    }
+
+    @SuppressWarnings("unchecked")
+    protected MethodEffector(AnnotationsOnMethod anns, String description) {
+        super(anns.name, (Class<T>)anns.returnType, anns.parameters, GroovyJavaMethods.<String>elvis(description, anns.description));
+    }
+
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    public T call(Entity entity, Map parameters) {
+        Object[] parametersArray = EffectorUtils.prepareArgsForEffector(this, parameters);
+        if (entity instanceof AbstractEntity) {
+            return EffectorUtils.invokeMethodEffector(entity, this, parametersArray);
+        } else {
+            // we are dealing with a proxy here
+            // this implementation invokes the method on the proxy
+            // (requiring it to be on the interface)
+            // and letting the proxy deal with the remoting / runAtEntity;
+            // alternatively we could create the task here and pass it to runAtEntity;
+            // the latter may allow us to simplify/remove a lot of the stuff from 
+            // EffectorUtils and possibly Effectors and Entities
+            
+            // TODO Should really find method with right signature, rather than just the right args.
+            // TODO prepareArgs can miss things out that have "default values"! Code below will probably fail if that happens.
+            Method[] methods = entity.getClass().getMethods();
+            for (Method method : methods) {
+                if (method.getName().equals(getName())) {
+                    if (parametersArray.length == method.getParameterTypes().length) {
+                        try {
+                            return (T) method.invoke(entity, parametersArray);
+                        } catch (Exception e) {
+                            // exception handled by the proxy invocation (which leads to EffectorUtils.invokeEffectorMethod...)
+                            throw Exceptions.propagate(e);
+                        }
+                    }
+                }
+            }
+            String msg = "Could not find method for effector "+getName()+" with "+parametersArray.length+" parameters on "+entity;
+            log.warn(msg+" (throwing); available methods are: "+Arrays.toString(methods));
+            throw new IllegalStateException(msg);
+        }
+    }
+    
+}