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 <[email protected]> Authored: Wed Aug 19 22:39:55 2015 +0100 Committer: Aled Sage <[email protected]> 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); + } + } + +}
