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
index 3f5d77a84c..fe79774312 100644
--- a/core/src/main/java/org/apache/brooklyn/core/effector/AddChildrenEffector.java
+++ b/core/src/main/java/org/apache/brooklyn/core/effector/AddChildrenEffector.java
@@ -35,17 +35,17 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import com.google.common.annotations.Beta;
import com.google.common.collect.Iterables;
-/** Entity initializer which defines an effector which adds a child blueprint to an entity.
+/**
+ * Entity initializer which defines an effector which adds a child blueprint to an entity.
*
* 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
+ * @since 0.7.0
+ */
public class AddChildrenEffector extends AddEffector {
private static final Logger log = LoggerFactory.getLogger(AddChildrenEffector.class);
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
index 9590bcff57..795fa954f0 100644
--- a/core/src/main/java/org/apache/brooklyn/core/effector/AddEffector.java
+++ b/core/src/main/java/org/apache/brooklyn/core/effector/AddEffector.java
@@ -54,31 +54,31 @@
* 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
+ * @since 0.7.0
+ */
public class AddEffector implements EntityInitializer {
-
+
public static final ConfigKey EFFECTOR_NAME = ConfigKeys.newStringConfigKey("name");
public static final ConfigKey EFFECTOR_DESCRIPTION = ConfigKeys.newStringConfigKey("description");
-
+
public static final ConfigKey> EFFECTOR_PARAMETER_DEFS = new MapConfigKey(Object.class, "parameters");
- final Effector> effector;
-
+ protected 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 EffectorBuilder newEffectorBuilder(Class type, ConfigBag params) {
String name = Preconditions.checkNotNull(params.get(EFFECTOR_NAME), "name must be supplied when defining an effector: %s", params);
EffectorBuilder eff = Effectors.effector(type, name);
eff.description(params.get(EFFECTOR_DESCRIPTION));
-
+
Map paramDefs = params.get(EFFECTOR_PARAMETER_DEFS);
if (paramDefs!=null) {
for (Map.Entry paramDef: paramDefs.entrySet()){
@@ -97,7 +97,7 @@ public static EffectorBuilder newEffectorBuilder(Class type, ConfigBag
}
}
}
-
+
return eff;
}
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
index 92cc4ec140..12b2ebf114 100644
--- a/core/src/main/java/org/apache/brooklyn/core/effector/AddSensor.java
+++ b/core/src/main/java/org/apache/brooklyn/core/effector/AddSensor.java
@@ -20,112 +20,20 @@
import java.util.Map;
-import org.apache.brooklyn.api.entity.Entity;
-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.core.sensor.Sensors;
-import org.apache.brooklyn.util.core.ClassLoaderUtils;
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.
- *
- * 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
+ * @deprecated use {@link org.apache.brooklyn.core.sensor.AddSensor} instead
*/
-@Beta
-public class AddSensor implements EntityInitializer {
-
- public static final ConfigKey SENSOR_NAME = ConfigKeys.newStringConfigKey("name", "The name of the sensor to create");
- public static final ConfigKey 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 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 AttributeSensor sensor;
- protected final ConfigBag params;
+@Deprecated
+public class AddSensor extends org.apache.brooklyn.core.sensor.AddSensor {
public AddSensor(Map params) {
- this(ConfigBag.newInstance(params));
+ super(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.params = params;
- }
-
- @Override
- public void apply(EntityLocal entity) {
- sensor = newSensor(entity);
- ((EntityInternal) entity).getMutableEntityType().addSensor(sensor);
- }
-
- private AttributeSensor newSensor(Entity entity) {
- String className = getFullClassName(type);
- Class clazz = getType(entity, className);
- return Sensors.newSensor(clazz, name);
- }
-
- @SuppressWarnings("unchecked")
- protected Class getType(Entity entity, String className) {
- try {
- // TODO use OSGi loader (low priority however); also ensure that allows primitives
- Maybe> primitive = Boxing.getPrimitiveType(className);
- if (primitive.isPresent()) return (Class) primitive.get();
-
- return (Class) new ClassLoaderUtils(this, entity).loadClass(className);
- } catch (ClassNotFoundException e) {
- if (!className.contains(".")) {
- // could be assuming "java.lang" package; try again with that
- try {
- return (Class) 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;
- }
+ super(params);
}
-}
+}
\ No newline at end of file
diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/CompositeEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/CompositeEffector.java
index 62a598454f..5031203313 100644
--- a/core/src/main/java/org/apache/brooklyn/core/effector/CompositeEffector.java
+++ b/core/src/main/java/org/apache/brooklyn/core/effector/CompositeEffector.java
@@ -29,6 +29,8 @@
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.effector.composite.ReplaceEffector;
+import org.apache.brooklyn.core.effector.composite.SequenceEffector;
import org.apache.brooklyn.core.entity.EntityInitializers;
import org.apache.brooklyn.core.entity.EntityInternal;
import org.apache.brooklyn.util.core.config.ConfigBag;
@@ -42,7 +44,11 @@
import com.google.common.collect.Lists;
import com.google.common.reflect.TypeToken;
+/**
+ * @deprecated use a combintaion of {@link ReplaceEffector} and {@link SequenceEffector} instead
+ */
@Beta
+@Deprecated
public class CompositeEffector extends AddEffector {
private static final Logger LOG = LoggerFactory.getLogger(CompositeEffector.class);
diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/RemoveEntityEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/RemoveEntityEffector.java
new file mode 100644
index 0000000000..641adbf4e5
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/core/effector/RemoveEntityEffector.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.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.entity.EntityInitializers;
+import org.apache.brooklyn.core.entity.EntityPredicates;
+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.common.base.Optional;
+import com.google.common.base.Predicate;
+import com.google.common.collect.Iterables;
+import com.google.common.reflect.TypeToken;
+
+/**
+ * @since 0.11.0
+ */
+@Beta
+public class RemoveEntityEffector extends AddEffector {
+
+ private static final Logger LOG = LoggerFactory.getLogger(RemoveEntityEffector.class);
+
+ public static final ConfigKey ENTITY_ID = ConfigKeys.builder(String.class)
+ .name("entityId")
+ .description("The id of the entity to be removed")
+ .build();
+
+ public static final ConfigKey> ENTITY_PREDICATE = ConfigKeys.builder(new TypeToken>() { })
+ .name("predicate")
+ .description("A predicate that will match the entity to be removed")
+ .build();
+
+ public RemoveEntityEffector(ConfigBag params) {
+ super(newEffectorBuilder(params).build());
+ }
+
+ public RemoveEntityEffector(Map params) {
+ this(ConfigBag.newInstance(params));
+ }
+
+ public static EffectorBuilder newEffectorBuilder(ConfigBag params) {
+ EffectorBuilder eff = (EffectorBuilder) AddEffector.newEffectorBuilder(Boolean.class, params);
+ eff.impl(new Body(eff.buildAbstract(), params));
+ return eff;
+ }
+
+ protected static class Body extends EffectorBody {
+ protected final Effector> effector;
+ protected final ConfigBag config;
+
+ protected Object mutex = new Object[0];
+
+ public Body(Effector> eff, ConfigBag config) {
+ this.effector = eff;
+ this.config = config;
+ }
+
+ @Override
+ public Boolean call(final ConfigBag params) {
+ synchronized (mutex) {
+ ConfigBag all = ConfigBag.newInstanceCopying(config).putAll(params);
+ Predicate predicate = EntityInitializers.resolve(all, ENTITY_PREDICATE);
+ if (predicate == null) {
+ String entityId = EntityInitializers.resolve(all, ENTITY_ID);
+ predicate = EntityPredicates.idEqualTo(entityId);
+ }
+ Optional child = Iterables.tryFind(entity().getChildren(), predicate);
+ if (child.isPresent()) {
+ boolean success = entity().removeChild(child.get());
+ if (success) {
+ LOG.debug("{}: Removed child {} from {}", new Object[] { this, child.get(), entity() });
+ return true;
+ }
+ }
+ LOG.warn("{}: Could not find child of {} using {}", new Object[] { this, entity(), predicate });
+ return false;
+ }
+ }
+ }
+
+}
diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/SetSensorEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/SetSensorEffector.java
new file mode 100644
index 0000000000..d386f1a9f6
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/core/effector/SetSensorEffector.java
@@ -0,0 +1,107 @@
+/*
+ * 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.effector.Effector;
+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.effector.Effectors.EffectorBuilder;
+import org.apache.brooklyn.core.entity.EntityInitializers;
+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.common.base.Preconditions;
+import com.google.common.reflect.TypeToken;
+
+/**
+ * {@code
+ * brooklyn.initializers:
+ * - type: org.apache.brooklyn.core.effector.SetSensorEffector
+ * brooklyn.config:
+ * name: setStatus
+ * sensor: $brooklyn:sensor("myentity.status")
+ * }
+ *
+ * @since 0.11.0
+ */
+@Beta
+public class SetSensorEffector extends AddEffector {
+
+ private static final Logger LOG = LoggerFactory.getLogger(SetSensorEffector.class);
+
+ public static final ConfigKey> SENSOR = ConfigKeys.newConfigKey(
+ new TypeToken>() { },
+ "sensor",
+ "The sensor whose value is to be set");
+
+ public static final ConfigKey VALUE = ConfigKeys.newConfigKey(Object.class,
+ "value",
+ "The value to be set on the sensor");
+
+ private final Object mutex = new Object[0];
+
+ public SetSensorEffector(Map, ?> params) {
+ this(ConfigBag.newInstance(params));
+ }
+
+ public SetSensorEffector(ConfigBag params) {
+ this(newEffectorBuilder(params).build());
+ }
+
+ public SetSensorEffector(Effector> effector) {
+ super(effector);
+ }
+
+ public static EffectorBuilder newEffectorBuilder(ConfigBag params) {
+ EffectorBuilder eb = AddEffector.newEffectorBuilder(Object.class, params);
+ EffectorBody body = new Body(eb.buildAbstract(), params);
+ eb.impl(body);
+ return eb;
+ }
+
+ protected static class Body extends EffectorBody {
+ protected final Effector> effector;
+ protected final ConfigBag config;
+ protected final Object mutex = new Object[0];
+
+ public Body(Effector> eff, ConfigBag config) {
+ this.effector = eff;
+ this.config = config;
+
+ Preconditions.checkNotNull(config.getAllConfigRaw().get(SENSOR.getName()), "The sensor must be supplied when defining this effector");
+ }
+
+ @Override
+ public Object call(final ConfigBag params) {
+ synchronized (mutex) {
+ LOG.debug("{}: Effector called with config {}, params {}", new Object[] { this, config, params });
+ AttributeSensor sensor = EntityInitializers.resolve(config, SENSOR);
+ Object value = EntityInitializers.resolve(params, VALUE);
+ Object old = entity().sensors().set(sensor, value);
+ LOG.debug("{}: Effector set {} to {} (was {})", new Object[] { this, sensor.getName(), value, old });
+ return old;
+ }
+ }
+ }
+}
diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/composite/AbstractCompositeEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/composite/AbstractCompositeEffector.java
new file mode 100644
index 0000000000..db11058f97
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/core/effector/composite/AbstractCompositeEffector.java
@@ -0,0 +1,179 @@
+/*
+ * 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.composite;
+
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.brooklyn.api.effector.Effector;
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.mgmt.Task;
+import org.apache.brooklyn.core.effector.AddEffector;
+import org.apache.brooklyn.core.effector.EffectorBody;
+import org.apache.brooklyn.util.core.config.ConfigBag;
+import org.apache.brooklyn.util.guava.Maybe;
+import org.apache.brooklyn.util.text.Strings;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.annotations.Beta;
+import com.google.common.collect.Iterables;
+
+/**
+ * @since 0.11.0
+ */
+@Beta
+public abstract class AbstractCompositeEffector extends AddEffector {
+
+ private static final Logger LOG = LoggerFactory.getLogger(AbstractCompositeEffector.class);
+
+ public AbstractCompositeEffector(Effector> effector) {
+ super(effector);
+ }
+
+ protected static abstract class Body extends EffectorBody {
+ protected final Effector> effector;
+ protected final ConfigBag config;
+
+ protected Object mutex = new Object[0];
+
+ public Body(Effector> eff, ConfigBag config) {
+ this.effector = eff;
+ this.config = config;
+ }
+
+ @Override
+ public abstract T call(final ConfigBag params);
+
+ protected String getEffectorName(Object effectorDetails) {
+ String effectorName = null;
+ if (effectorDetails instanceof String) {
+ effectorName = (String) effectorDetails;
+ } else if (effectorDetails instanceof Map) {
+ Map effectorMap = (Map) effectorDetails;
+ Set keys = effectorMap.keySet();
+ if (keys.size() != 1) {
+ throw new IllegalArgumentException("Effector parameter cannot be parsed: " + effectorDetails);
+ }
+ effectorName = Iterables.getOnlyElement(keys);
+ } else {
+ effectorName = Strings.toString(effectorDetails);
+ }
+ return effectorName;
+ }
+
+ protected Entity getTargetEntity(Object effectorDetails) {
+ Entity targetEntity = entity();
+ if (effectorDetails instanceof Map) {
+ Map effectorMap = (Map) effectorDetails;
+ Set keys = effectorMap.keySet();
+ if (keys.size() != 1) {
+ throw new IllegalArgumentException("Effector parameter cannot be parsed: " + effectorDetails);
+ }
+ String effectorName = Iterables.getOnlyElement(keys);
+ Object effectorParams = effectorMap.get(effectorName);
+ if (effectorParams instanceof Map) {
+ Map effectorParamsMap = (Map) effectorParams;
+ if (effectorParamsMap.containsKey("target")) {
+ Object targetObject = effectorParamsMap.get("target");
+ if (targetObject instanceof Entity) {
+ targetEntity = (Entity) targetObject;
+ } else {
+ throw new IllegalArgumentException("Effector target is not an Entity: " + targetObject);
+ }
+ }
+ }
+ }
+ return targetEntity;
+ }
+
+ protected String getInputArgument(Object effectorDetails) {
+ String inputArgument = null;
+ if (effectorDetails instanceof Map) {
+ Map effectorMap = (Map) effectorDetails;
+ Set keys = effectorMap.keySet();
+ if (keys.size() != 1) {
+ throw new IllegalArgumentException("Effector parameter cannot be parsed: " + effectorDetails);
+ }
+ String effectorName = Iterables.getOnlyElement(keys);
+ Object effectorParams = effectorMap.get(effectorName);
+ if (effectorParams instanceof Map) {
+ Map effectorParamsMap = (Map) effectorParams;
+ if (effectorParamsMap.containsKey("input")) {
+ Object inputDetails = effectorParamsMap.get("input");
+ if (inputDetails instanceof String) {
+ inputArgument = (String) inputDetails;
+ } else if (inputDetails instanceof Map) {
+ Map inputMap = (Map) inputDetails;
+ Set inputKeys = inputMap.keySet();
+ if (inputKeys.size() != 1) {
+ throw new IllegalArgumentException("Effector input cannot be parsed: " + inputDetails);
+ }
+ inputArgument = Iterables.getOnlyElement(inputKeys);
+ } else {
+ inputArgument = Strings.toString(inputDetails);
+ }
+ }
+ }
+ }
+ return inputArgument;
+ }
+
+ protected String getInputParameter(Object effectorDetails) {
+ String inputArgument = getInputArgument(effectorDetails);
+ String inputParameter = null;
+ if (inputArgument != null && effectorDetails instanceof Map) {
+ Map effectorMap = (Map) effectorDetails;
+ Set keys = effectorMap.keySet();
+ if (keys.size() != 1) {
+ throw new IllegalArgumentException("Effector parameter cannot be parsed: " + effectorDetails);
+ }
+ String effectorName = Iterables.getOnlyElement(keys);
+ Object effectorParams = effectorMap.get(effectorName);
+ if (effectorParams instanceof Map) {
+ Map effectorParamsMap = (Map) effectorParams;
+ if (effectorParamsMap.containsKey("input")) {
+ Object inputDetails = effectorParamsMap.get("input");
+ if (inputDetails instanceof Map) {
+ Map inputMap = (Map) inputDetails;
+ if (inputMap.size() != 1) {
+ throw new IllegalArgumentException("Effector input cannot be parsed: " + inputDetails);
+ }
+ inputParameter = Strings.toString(inputMap.get(inputArgument));
+ }
+ }
+ }
+ }
+ return inputParameter;
+ }
+
+ protected Task> submitEffectorNamed(Entity target, String effectorName, ConfigBag params) {
+ LOG.debug("{} invoking {} with params {}", new Object[] { this, effectorName, params });
+ Maybe> effector = target.getEntityType().getEffectorByName(effectorName);
+ if (effector.isAbsent()) {
+ throw new IllegalStateException("Cannot find effector " + effectorName);
+ }
+ return target.invoke(effector.get(), params.getAllConfig());
+ }
+
+ protected Object invokeEffectorNamed(Entity target, String effectorName, ConfigBag params) {
+ return submitEffectorNamed(target, effectorName, params).getUnchecked();
+ }
+ }
+}
diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/composite/ChoiceEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/composite/ChoiceEffector.java
new file mode 100644
index 0000000000..12dd9e54e2
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/core/effector/composite/ChoiceEffector.java
@@ -0,0 +1,140 @@
+/*
+ * 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.composite;
+
+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.AddEffector;
+import org.apache.brooklyn.core.effector.EffectorBody;
+import org.apache.brooklyn.core.effector.Effectors.EffectorBuilder;
+import org.apache.brooklyn.core.entity.EntityInitializers;
+import org.apache.brooklyn.util.core.config.ConfigBag;
+import org.apache.brooklyn.util.text.Strings;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Predicates;
+
+/**
+ * Execute an effector, and depending on the result, execute either
+ * the success or failure effector.
+ *
+ * @since 0.11.0
+ */
+@Beta
+public class ChoiceEffector extends AbstractCompositeEffector {
+
+ private static final Logger LOG = LoggerFactory.getLogger(ChoiceEffector.class);
+
+ public static final ConfigKey INPUT = ConfigKeys.builder(String.class)
+ .name("input")
+ .description("Choice input parameter")
+ .build();
+
+ public static final ConfigKey CHOICE = ConfigKeys.builder(Object.class)
+ .name("choice")
+ .description("Effector details for the choice effector")
+ .constraint(Predicates.notNull())
+ .build();
+
+ public static final ConfigKey SUCCESS = ConfigKeys.builder(Object.class)
+ .name("success")
+ .description("Effector details for the success effector")
+ .constraint(Predicates.notNull())
+ .build();
+
+ public static final ConfigKey FAILURE = ConfigKeys.builder(Object.class)
+ .name("failure")
+ .description("Effector details for the failure effector")
+ .build();
+
+ public ChoiceEffector(ConfigBag params) {
+ super(newEffectorBuilder(params).build());
+ }
+
+ public ChoiceEffector(Map, ?> params) {
+ this(ConfigBag.newInstance(params));
+ }
+
+ public static EffectorBuilder newEffectorBuilder(ConfigBag params) {
+ EffectorBuilder eff = AddEffector.newEffectorBuilder(Object.class, params);
+ EffectorBody body = new Body(eff.buildAbstract(), params);
+ eff.impl(body);
+ return eff;
+ }
+
+ protected static class Body extends AbstractCompositeEffector.Body {
+
+ public Body(Effector> eff, ConfigBag config) {
+ super(eff, config);
+ Preconditions.checkNotNull(config.getAllConfigRaw().get(CHOICE.getName()), "Choice effector details must be supplied when defining this effector");
+ Preconditions.checkNotNull(config.getAllConfigRaw().get(SUCCESS.getName()), "Success effector details must be supplied when defining this effector");
+ }
+
+ @Override
+ public Object call(final ConfigBag params) {
+ synchronized (mutex) {
+ LOG.debug("{} called with config {}, params {}", new Object[] { this, config, params });
+ String input = config.get(INPUT);
+ Object inputObject = params.getStringKey(input);
+
+ Object choiceDetails = EntityInitializers.resolve(config, CHOICE);
+ String choiceEffectorName = getEffectorName(choiceDetails);
+ String choiceInputArgument = getInputArgument(choiceDetails);
+ Entity choiceTargetEntity = getTargetEntity(choiceDetails);
+ LOG.debug("{} executing {}({}) on {}", new Object[] { this, choiceEffectorName, choiceInputArgument, choiceTargetEntity });
+
+ if (choiceInputArgument == null) {
+ throw new IllegalArgumentException("Input is not set for choice effector: " + choiceDetails);
+ }
+ params.putStringKey(choiceInputArgument, inputObject);
+
+ Object output = invokeEffectorNamed(choiceTargetEntity, choiceEffectorName, params);
+ Boolean success = Boolean.parseBoolean(Strings.toString(output));
+ LOG.debug("{} result of {} was {}/{}", new Object[] { this, choiceEffectorName, Strings.toString(output), success });
+
+ Object effectorDetails = EntityInitializers.resolve(config, success ? SUCCESS : FAILURE);
+
+ if (!success && effectorDetails == null) {
+ return null;
+ }
+
+ String effectorName = getEffectorName(effectorDetails);
+ String inputArgument = getInputArgument(effectorDetails);
+ Entity targetEntity = getTargetEntity(effectorDetails);
+ LOG.debug("{} executing {}({}) on {}", new Object[] { this, effectorName, inputArgument, targetEntity });
+
+ if (inputArgument == null) {
+ throw new IllegalArgumentException("Input is not set for effector: " + effectorDetails);
+ }
+ params.putStringKey(inputArgument, inputObject);
+ Object result = invokeEffectorNamed(targetEntity, effectorName, params);
+
+ LOG.debug("{} effector {} returned {}", new Object[] { this, effector.getName(), result });
+ return result;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/composite/ComposeEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/composite/ComposeEffector.java
new file mode 100644
index 0000000000..09e39166c8
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/core/effector/composite/ComposeEffector.java
@@ -0,0 +1,120 @@
+/*
+ * 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.composite;
+
+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.AddEffector;
+import org.apache.brooklyn.core.effector.EffectorBody;
+import org.apache.brooklyn.core.effector.Effectors.EffectorBuilder;
+import org.apache.brooklyn.core.entity.EntityInitializers;
+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.common.base.Preconditions;
+import com.google.common.base.Predicates;
+import com.google.common.collect.ImmutableList;
+import com.google.common.reflect.TypeToken;
+
+/**
+ * Execute a series of effectors using the output from one as
+ * the input for the next.
+ *
+ * @since 0.11.0
+ */
+@Beta
+public class ComposeEffector extends AbstractCompositeEffector {
+
+ private static final Logger LOG = LoggerFactory.getLogger(ComposeEffector.class);
+
+ public static final ConfigKey> COMPOSE = ConfigKeys.builder(new TypeToken>() { })
+ .name("compose")
+ .description("Effector details list for the compose effector")
+ .constraint(Predicates.notNull())
+ .defaultValue(ImmutableList.of())
+ .build();
+
+ public ComposeEffector(ConfigBag params) {
+ super(newEffectorBuilder(params).build());
+ }
+
+ public ComposeEffector(Map, ?> params) {
+ this(ConfigBag.newInstance(params));
+ }
+
+ public static EffectorBuilder newEffectorBuilder(ConfigBag params) {
+ EffectorBuilder eff = AddEffector.newEffectorBuilder(Object.class, params);
+ EffectorBody body = new Body(eff.buildAbstract(), params);
+ eff.impl(body);
+ return eff;
+ }
+
+ protected static class Body extends AbstractCompositeEffector.Body {
+
+ public Body(Effector> eff, ConfigBag config) {
+ super(eff, config);
+ Preconditions.checkNotNull(config.getAllConfigRaw().get(COMPOSE.getName()), "Compose effector names must be supplied when defining this effector");
+ }
+
+ @Override
+ public Object call(final ConfigBag params) {
+ synchronized (mutex) {
+ LOG.debug("{} called with config {}, params {}", new Object[] { this, config, params });
+ List effectors = EntityInitializers.resolve(config, COMPOSE);
+
+ Object result = null;
+
+ for (Object effectorDetails : effectors) {
+ String effectorName = getEffectorName(effectorDetails);
+ String inputArgument = getInputArgument(effectorDetails);
+ String inputParameter = getInputParameter(effectorDetails);
+ Entity targetEntity = getTargetEntity(effectorDetails);
+ LOG.debug("{} executing {}({}:{}) on {}", new Object[] { this, effectorName, inputArgument, inputParameter, targetEntity });
+
+ if (inputArgument == null) {
+ throw new IllegalArgumentException("Input is not set for this effector: " + effectorDetails);
+ }
+ if (inputParameter == null) {
+ if (result == null) {
+ Object input = params.getStringKey(inputArgument);
+ params.putStringKey(inputArgument, input);
+ } else {
+ params.putStringKey(inputArgument, result);
+ }
+ } else {
+ Object input = params.getStringKey(inputParameter);
+ params.putStringKey(inputArgument, input);
+ }
+
+ result = invokeEffectorNamed(targetEntity, effectorName, params);
+ }
+
+ LOG.debug("{} effector {} returned {}", new Object[] { this, effector.getName(), result });
+ return result;
+ }
+ }
+ }
+}
diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/composite/LoopEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/composite/LoopEffector.java
new file mode 100644
index 0000000000..58bdc310ee
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/core/effector/composite/LoopEffector.java
@@ -0,0 +1,125 @@
+/*
+ * 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.composite;
+
+import java.util.Collection;
+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.api.mgmt.Task;
+import org.apache.brooklyn.config.ConfigKey;
+import org.apache.brooklyn.core.config.ConfigKeys;
+import org.apache.brooklyn.core.effector.AddEffector;
+import org.apache.brooklyn.core.effector.EffectorBody;
+import org.apache.brooklyn.core.effector.Effectors.EffectorBuilder;
+import org.apache.brooklyn.core.entity.EntityInitializers;
+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.common.base.Preconditions;
+import com.google.common.base.Predicates;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+
+/**
+ * Execute an effector repeatedly against each element of an
+ * input collection.
+ *
+ * @since 0.11.0
+ */
+@Beta
+public class LoopEffector extends AbstractCompositeEffector {
+
+ private static final Logger LOG = LoggerFactory.getLogger(LoopEffector.class);
+
+ public static final ConfigKey INPUT = ConfigKeys.builder(String.class)
+ .name("input")
+ .description("Loop input parameter")
+ .build();
+
+ public static final ConfigKey LOOP = ConfigKeys.builder(Object.class)
+ .name("loop")
+ .description("Effector details for the loop effector")
+ .constraint(Predicates.notNull())
+ .build();
+
+ public LoopEffector(ConfigBag params) {
+ super(newEffectorBuilder(params).build());
+ }
+
+ public LoopEffector(Map, ?> params) {
+ this(ConfigBag.newInstance(params));
+ }
+
+ public static EffectorBuilder newEffectorBuilder(ConfigBag params) {
+ EffectorBuilder eff = AddEffector.newEffectorBuilder(List.class, params);
+ EffectorBody body = new Body(eff.buildAbstract(), params);
+ eff.impl(body);
+ return eff;
+ }
+
+ protected static class Body extends AbstractCompositeEffector.Body {
+
+ public Body(Effector> eff, ConfigBag config) {
+ super(eff, config);
+ Preconditions.checkNotNull(config.getAllConfigRaw().get(LOOP.getName()), "Loop effector names must be supplied when defining this effector");
+ }
+
+ @Override
+ public List call(final ConfigBag params) {
+ synchronized (mutex) {
+ LOG.debug("{} called with config {}, params {}", new Object[] { this, config, params });
+ Object effectorDetails = EntityInitializers.resolve(config, LOOP);
+ String input = config.get(INPUT);
+ Object inputObject = params.getStringKey(input);
+ if (!(inputObject instanceof Collection)) {
+ throw new IllegalArgumentException("Input to loop is not a collection: " + inputObject);
+ }
+ Collection> inputCollection = (Collection) inputObject;
+
+ String effectorName = getEffectorName(effectorDetails);
+ String inputArgument = getInputArgument(effectorDetails);
+ Entity targetEntity = getTargetEntity(effectorDetails);
+ LOG.debug("{} executing {}({}) on {}", new Object[] { this, effectorName, inputArgument, targetEntity });
+
+ if (inputArgument == null) {
+ throw new IllegalArgumentException("Input is not set for this effector: " + effectorDetails);
+ }
+
+ List> tasks = Lists.newArrayList();
+ for (Object each : inputCollection) {
+ params.putStringKey(inputArgument, each);
+ tasks.add(submitEffectorNamed(targetEntity, effectorName, params));
+ }
+
+ List result = Lists.newArrayList();
+ for (Task> each : tasks) {
+ result.add(each.getUnchecked());
+ }
+
+ LOG.debug("{} effector {} returned {}", new Object[] { this, effector.getName(), Iterables.toString(result) });
+ return result;
+ }
+ }
+ }
+}
diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/composite/ParallelEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/composite/ParallelEffector.java
new file mode 100644
index 0000000000..cfe5ca2c5d
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/core/effector/composite/ParallelEffector.java
@@ -0,0 +1,115 @@
+/*
+ * 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.composite;
+
+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.api.mgmt.Task;
+import org.apache.brooklyn.config.ConfigKey;
+import org.apache.brooklyn.core.config.ConfigKeys;
+import org.apache.brooklyn.core.effector.AddEffector;
+import org.apache.brooklyn.core.effector.EffectorBody;
+import org.apache.brooklyn.core.effector.Effectors.EffectorBuilder;
+import org.apache.brooklyn.core.entity.EntityInitializers;
+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.common.base.Preconditions;
+import com.google.common.base.Predicates;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import com.google.common.reflect.TypeToken;
+
+/**
+ * Execute a sequence of effectors with the same input.
+ *
+ * @since 0.11.0
+ */
+@Beta
+public class ParallelEffector extends AbstractCompositeEffector {
+
+ private static final Logger LOG = LoggerFactory.getLogger(ParallelEffector.class);
+
+ public static final ConfigKey> PARALLEL = ConfigKeys.builder(new TypeToken>() { })
+ .name("parallel")
+ .description("Effector details list for the parallel effector")
+ .constraint(Predicates.notNull())
+ .defaultValue(ImmutableList.of())
+ .build();
+
+ public ParallelEffector(ConfigBag params) {
+ super(newEffectorBuilder(params).build());
+ }
+
+ public ParallelEffector(Map, ?> params) {
+ this(ConfigBag.newInstance(params));
+ }
+
+ public static EffectorBuilder newEffectorBuilder(ConfigBag params) {
+ EffectorBuilder eff = AddEffector.newEffectorBuilder(Object.class, params);
+ EffectorBody body = new Body(eff.buildAbstract(), params);
+ eff.impl(body);
+ return eff;
+ }
+
+ protected static class Body extends AbstractCompositeEffector.Body {
+
+ public Body(Effector> eff, ConfigBag config) {
+ super(eff, config);
+ Preconditions.checkNotNull(config.getAllConfigRaw().get(PARALLEL.getName()), "Parallel effector names must be supplied when defining this effector");
+ }
+
+ @Override
+ public Object call(final ConfigBag params) {
+ synchronized (mutex) {
+ LOG.debug("{} called with config {}, params {}", new Object[] { this, config, params });
+ List effectors = EntityInitializers.resolve(config, PARALLEL);
+
+ List> tasks = Lists.newArrayList();
+ for (Object effectorDetails : effectors) {
+ String effectorName = getEffectorName(effectorDetails);
+ String inputArgument = getInputArgument(effectorDetails);
+ Entity targetEntity = getTargetEntity(effectorDetails);
+ LOG.debug("{} executing {}({}) on {}", new Object[] { this, effectorName, inputArgument, targetEntity });
+
+ if (inputArgument == null) {
+ throw new IllegalArgumentException("Input is not set for this effector: " + effectorDetails);
+ }
+ Object input = params.getStringKey(inputArgument);
+ params.putStringKey(inputArgument, input);
+
+ tasks.add(submitEffectorNamed(targetEntity, effectorName, params));
+ }
+
+ Object result = null;
+ for (Task> each : tasks) {
+ result = each.getUnchecked();
+ }
+
+ LOG.debug("{} effector {} returned {}", new Object[] { this, effector.getName(), result });
+ return result;
+ }
+ }
+ }
+}
diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/composite/RepeatEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/composite/RepeatEffector.java
new file mode 100644
index 0000000000..faa55ff041
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/core/effector/composite/RepeatEffector.java
@@ -0,0 +1,123 @@
+/*
+ * 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.composite;
+
+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.AddEffector;
+import org.apache.brooklyn.core.effector.EffectorBody;
+import org.apache.brooklyn.core.effector.Effectors.EffectorBuilder;
+import org.apache.brooklyn.core.entity.EntityInitializers;
+import org.apache.brooklyn.util.core.config.ConfigBag;
+import org.apache.brooklyn.util.math.MathPredicates;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Predicates;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+
+/**
+ * Execute an effector a selected number of times.
+ *
+ * @since 0.11.0
+ */
+@Beta
+public class RepeatEffector extends AbstractCompositeEffector {
+
+ private static final Logger LOG = LoggerFactory.getLogger(RepeatEffector.class);
+
+ public static final ConfigKey REPEAT = ConfigKeys.builder(Object.class)
+ .name("repeat")
+ .description("Effector details list for the repeat effector")
+ .constraint(Predicates.notNull())
+ .build();
+
+ public static final ConfigKey COUNT = ConfigKeys.builder(Integer.class)
+ .name("count")
+ .description("Number of times to repeat the effector")
+ .constraint(MathPredicates.greaterThan(0d))
+ .defaultValue(1)
+ .build();
+
+ public RepeatEffector(ConfigBag params) {
+ super(newEffectorBuilder(params).build());
+ }
+
+ public RepeatEffector(Map, ?> params) {
+ this(ConfigBag.newInstance(params));
+ }
+
+ public static EffectorBuilder newEffectorBuilder(ConfigBag params) {
+ EffectorBuilder eff = AddEffector.newEffectorBuilder(List.class, params);
+ EffectorBody body = new Body(eff.buildAbstract(), params);
+ eff.impl(body);
+ return eff;
+ }
+
+ protected static class Body extends AbstractCompositeEffector.Body {
+
+ public Body(Effector> eff, ConfigBag config) {
+ super(eff, config);
+ Preconditions.checkNotNull(config.getAllConfigRaw().get(REPEAT.getName()), "Repeat effector details must be supplied when defining this effector");
+ }
+
+ @Override
+ public List call(final ConfigBag params) {
+ synchronized (mutex) {
+ LOG.debug("{} called with config {}, params {}", new Object[] { this, config, params });
+ Object effectorDetails = EntityInitializers.resolve(config, REPEAT);
+ Integer count = EntityInitializers.resolve(config, COUNT);
+
+ String effectorName = getEffectorName(effectorDetails);
+ String inputArgument = getInputArgument(effectorDetails);
+ String inputParameter = getInputParameter(effectorDetails);
+ Entity targetEntity = getTargetEntity(effectorDetails);
+ LOG.debug("{} executing {}({}:{}) on {}", new Object[] { this, effectorName, inputArgument, inputParameter, targetEntity });
+
+ if (inputArgument == null) {
+ throw new IllegalArgumentException("Input is not set for this effector: " + effectorDetails);
+ }
+ if (inputParameter == null) {
+ Object input = params.getStringKey(inputArgument);
+ params.putStringKey(inputArgument, input);
+ } else {
+ Object input = params.getStringKey(inputParameter);
+ params.putStringKey(inputArgument, input);
+ }
+
+ List results = Lists.newArrayList();
+ for (int i = 0; i < count ; i++) {
+ Object result = invokeEffectorNamed(targetEntity, effectorName, params);
+ results.add(result);
+ }
+
+ LOG.debug("{} effector {} returned {}", new Object[] { this, effector.getName(), Iterables.toString(results) });
+ return results;
+ }
+ }
+ }
+}
diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/composite/ReplaceEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/composite/ReplaceEffector.java
new file mode 100644
index 0000000000..54e3aec530
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/core/effector/composite/ReplaceEffector.java
@@ -0,0 +1,137 @@
+/*
+ * 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.composite;
+
+import java.util.Map;
+
+import org.apache.brooklyn.api.effector.Effector;
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.entity.EntityLocal;
+import org.apache.brooklyn.config.ConfigKey;
+import org.apache.brooklyn.core.config.ConfigKeys;
+import org.apache.brooklyn.core.effector.AddEffector;
+import org.apache.brooklyn.core.effector.EffectorBody;
+import org.apache.brooklyn.core.effector.Effectors;
+import org.apache.brooklyn.core.effector.Effectors.EffectorBuilder;
+import org.apache.brooklyn.core.entity.EntityInitializers;
+import org.apache.brooklyn.core.entity.EntityInternal;
+import org.apache.brooklyn.util.core.config.ConfigBag;
+import org.apache.brooklyn.util.guava.Maybe;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Predicates;
+
+/**
+ * Replace an effector with another, optionally executing the originsl
+ * {@link ReplaceAction#POST before} or {@link ReplaceAction#PRE after}
+ * the replacement.
+ *
+ * @since 0.11.0
+ */
+@Beta
+public final class ReplaceEffector extends AbstractCompositeEffector {
+
+ private static final Logger LOG = LoggerFactory.getLogger(ReplaceEffector.class);
+
+ public enum ReplaceAction {
+ PRE,
+ POST,
+ OVERRIDE
+ }
+
+ public static final String ORIGINAL = "original-";
+
+ public static final ConfigKey ACTION = ConfigKeys.builder(ReplaceAction.class)
+ .name("action")
+ .description("Action to take with the replaced effector")
+ .defaultValue(ReplaceAction.OVERRIDE)
+ .build();
+
+ public static final ConfigKey REPLACE = ConfigKeys.builder(Object.class)
+ .name("replace")
+ .description("Effector details for the replace effector")
+ .constraint(Predicates.notNull())
+ .build();
+
+ public ReplaceEffector(ConfigBag params) {
+ super(newEffectorBuilder(params).build());
+ }
+
+ public ReplaceEffector(Map, ?> params) {
+ this(ConfigBag.newInstance(params));
+ }
+
+ public static EffectorBuilder newEffectorBuilder(ConfigBag params) {
+ EffectorBuilder eff = AddEffector.newEffectorBuilder(Object.class, params);
+ EffectorBody body = new Body(eff.buildAbstract(), params);
+ eff.impl(body);
+ return eff;
+ }
+
+ @Override
+ public void apply(EntityLocal entity) {
+ Maybe> effectorMaybe = entity.getEntityType().getEffectorByName(effector.getName());
+ if (effectorMaybe.isPresentAndNonNull()) {
+ Effector> original = Effectors.effector(effectorMaybe.get()).name(ORIGINAL + effector.getName()).build();
+ ((EntityInternal) entity).getMutableEntityType().addEffector(original);
+ }
+ super.apply(entity);
+ }
+
+ protected static class Body extends AbstractCompositeEffector.Body {
+
+ public Body(Effector> eff, ConfigBag config) {
+ super(eff, config);
+ Preconditions.checkNotNull(config.getAllConfigRaw().get(REPLACE.getName()), "Replace effector details must be supplied when defining this effector");
+ }
+
+ @Override
+ public Object call(final ConfigBag params) {
+ synchronized (mutex) {
+ LOG.debug("{} called with config {}, params {}", new Object[] { this, config, params });
+ ReplaceAction action = config.get(ACTION);
+ Object effectorDetails = EntityInitializers.resolve(config, REPLACE);
+
+ String effectorName = getEffectorName(effectorDetails);
+ String inputArgument = getInputArgument(effectorDetails);
+ Entity targetEntity = getTargetEntity(effectorDetails);
+ LOG.debug("{} executing {}({}) on {}", new Object[] { this, effectorName, inputArgument, targetEntity });
+
+ if (inputArgument != null) {
+ Object input = params.getStringKey(inputArgument);
+ params.putStringKey(inputArgument, input);
+ }
+
+ if (action == ReplaceAction.POST) {
+ invokeEffectorNamed(targetEntity, ORIGINAL + effector.getName(), params);
+ }
+ Object result = invokeEffectorNamed(targetEntity, effectorName, params);
+ if (action == ReplaceAction.PRE) {
+ invokeEffectorNamed(targetEntity, ORIGINAL + effector.getName(), params);
+ }
+
+ LOG.debug("{} effector {} returned {}", new Object[] { this, effector.getName(), result });
+ return result;
+ }
+ }
+ }
+}
diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/composite/SequenceEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/composite/SequenceEffector.java
new file mode 100644
index 0000000000..0dc1ee02ce
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/core/effector/composite/SequenceEffector.java
@@ -0,0 +1,109 @@
+/*
+ * 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.composite;
+
+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.AddEffector;
+import org.apache.brooklyn.core.effector.EffectorBody;
+import org.apache.brooklyn.core.effector.Effectors.EffectorBuilder;
+import org.apache.brooklyn.core.entity.EntityInitializers;
+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.common.base.Preconditions;
+import com.google.common.base.Predicates;
+import com.google.common.collect.ImmutableList;
+import com.google.common.reflect.TypeToken;
+
+/**
+ * Execute a sequence of effectors with the same input.
+ *
+ * @since 0.11.0
+ */
+@Beta
+public class SequenceEffector extends AbstractCompositeEffector {
+
+ private static final Logger LOG = LoggerFactory.getLogger(SequenceEffector.class);
+
+ public static final ConfigKey> SEQUENCE = ConfigKeys.builder(new TypeToken>() { })
+ .name("sequence")
+ .description("Effector details list for the sequence effector")
+ .constraint(Predicates.notNull())
+ .defaultValue(ImmutableList.of())
+ .build();
+
+ public SequenceEffector(ConfigBag params) {
+ super(newEffectorBuilder(params).build());
+ }
+
+ public SequenceEffector(Map, ?> params) {
+ this(ConfigBag.newInstance(params));
+ }
+
+ public static EffectorBuilder newEffectorBuilder(ConfigBag params) {
+ EffectorBuilder eff = AddEffector.newEffectorBuilder(Object.class, params);
+ EffectorBody body = new Body(eff.buildAbstract(), params);
+ eff.impl(body);
+ return eff;
+ }
+
+ protected static class Body extends AbstractCompositeEffector.Body {
+
+ public Body(Effector> eff, ConfigBag config) {
+ super(eff, config);
+ Preconditions.checkNotNull(config.getAllConfigRaw().get(SEQUENCE.getName()), "Sequence effector details must be supplied when defining this effector");
+ }
+
+ @Override
+ public Object call(final ConfigBag params) {
+ synchronized (mutex) {
+ LOG.debug("{} called with config {}, params {}", new Object[] { this, config, params });
+ List effectors = EntityInitializers.resolve(config, SEQUENCE);
+
+ Object result = null;
+
+ for (Object effectorDetails : effectors) {
+ String effectorName = getEffectorName(effectorDetails);
+ String inputArgument = getInputArgument(effectorDetails);
+ Entity targetEntity = getTargetEntity(effectorDetails);
+ LOG.debug("{} executing {}({}) on {}", new Object[] { this, effectorName, inputArgument, targetEntity });
+
+ if (inputArgument == null) {
+ throw new IllegalArgumentException("Input is not set for this effector: " + effectorDetails);
+ }
+ Object input = params.getStringKey(inputArgument);
+ params.putStringKey(inputArgument, input);
+
+ result = invokeEffectorNamed(targetEntity, effectorName, params);
+ }
+
+ LOG.debug("{} effector {} returned {}", new Object[] { this, effector.getName(), result });
+ return result;
+ }
+ }
+ }
+}
diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/composite/TransformEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/composite/TransformEffector.java
new file mode 100644
index 0000000000..b3bd55b3e6
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/core/effector/composite/TransformEffector.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.composite;
+
+import java.util.Map;
+
+import org.apache.brooklyn.api.effector.Effector;
+import org.apache.brooklyn.config.ConfigKey;
+import org.apache.brooklyn.core.config.ConfigKeys;
+import org.apache.brooklyn.core.effector.AddEffector;
+import org.apache.brooklyn.core.effector.EffectorBody;
+import org.apache.brooklyn.core.effector.Effectors.EffectorBuilder;
+import org.apache.brooklyn.core.entity.EntityInitializers;
+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.common.base.Function;
+import com.google.common.base.Functions;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Predicates;
+import com.google.common.reflect.TypeToken;
+
+/**
+ * Execute a {@link Function} as an effector body.
+ *
+ * @since 0.11.0
+ */
+@Beta
+public class TransformEffector extends AbstractCompositeEffector {
+
+ private static final Logger LOG = LoggerFactory.getLogger(TransformEffector.class);
+
+ public static final ConfigKey INPUT = ConfigKeys.builder(String.class)
+ .name("input")
+ .description("Transformer input parameter")
+ .build();
+
+ public static final ConfigKey> FUNCTION = ConfigKeys.builder(new TypeToken>() { })
+ .name("function")
+ .description("Transformer function to apply")
+ .constraint(Predicates.notNull())
+ .defaultValue(Functions.identity())
+ .build();
+
+ public TransformEffector(ConfigBag params) {
+ super(newEffectorBuilder(params).build());
+ }
+
+ public TransformEffector(Map, ?> params) {
+ this(ConfigBag.newInstance(params));
+ }
+
+ public static EffectorBuilder newEffectorBuilder(ConfigBag params) {
+ EffectorBuilder eff = AddEffector.newEffectorBuilder(Object.class, params);
+ EffectorBody body = new Body(eff.buildAbstract(), params);
+ eff.impl(body);
+ return eff;
+ }
+
+ protected static class Body extends AbstractCompositeEffector.Body {
+
+ public Body(Effector> eff, ConfigBag config) {
+ super(eff, config);
+ Preconditions.checkNotNull(config.getAllConfigRaw().get(FUNCTION.getName()), "Transform function must be supplied when defining this effector");
+ }
+
+ @Override
+ public Object call(final ConfigBag params) {
+ synchronized (mutex) {
+ LOG.debug("{} called with config {}, params {}", new Object[] { this, config, params });
+ Function function = EntityInitializers.resolve(config, FUNCTION);
+
+ String input = config.get(INPUT);
+ Object value = params.getStringKey(input);
+ Object result = function.apply(value);
+
+ LOG.debug("{} effector {} returned {}", new Object[] { this, effector.getName(), result });
+ return result;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/ssh/SshEffectorTasks.java b/core/src/main/java/org/apache/brooklyn/core/effector/ssh/SshEffectorTasks.java
index e28581773d..3a502facb5 100644
--- a/core/src/main/java/org/apache/brooklyn/core/effector/ssh/SshEffectorTasks.java
+++ b/core/src/main/java/org/apache/brooklyn/core/effector/ssh/SshEffectorTasks.java
@@ -58,7 +58,6 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import com.google.common.annotations.Beta;
import com.google.common.base.Function;
import com.google.common.collect.Maps;
@@ -72,7 +71,6 @@
* @see SshTasks
* @since 0.6.0
*/
-@Beta
public class SshEffectorTasks {
private static final Logger log = LoggerFactory.getLogger(SshEffectorTasks.class);
@@ -291,13 +289,14 @@ public static SshEffectorTaskFactory isPidFromFileRunning(String pidFil
});
}
- /** extracts the values for the main brooklyn.ssh.config.* config keys (i.e. those declared in ConfigKeys)
+ /**
+ * Extracts the values for the main brooklyn.ssh.config.* config keys (i.e. those declared in ConfigKeys)
* as declared on the entity, and inserts them in a map using the unprefixed state, for ssh.
*
* currently this is computed for each call, which may be wasteful, but it is reliable in the face of config changes.
* we could cache the Map. note that we do _not_ cache (or even own) the SshTool;
- * the SshTool is created or re-used by the SshMachineLocation making use of these properties */
- @Beta
+ * the SshTool is created or re-used by the SshMachineLocation making use of these properties
+ */
public static Map getSshFlags(Entity entity, Location optionalLocation) {
Set> sshConfig = MutableSet.of();
diff --git a/core/src/main/java/org/apache/brooklyn/core/entity/lifecycle/ServiceStateLogic.java b/core/src/main/java/org/apache/brooklyn/core/entity/lifecycle/ServiceStateLogic.java
index ba08a443ed..ca266f314a 100644
--- a/core/src/main/java/org/apache/brooklyn/core/entity/lifecycle/ServiceStateLogic.java
+++ b/core/src/main/java/org/apache/brooklyn/core/entity/lifecycle/ServiceStateLogic.java
@@ -469,6 +469,11 @@ public static class ComputeServiceIndicatorsFromChildrenAndMembers extends Abstr
"enricher.service_state.children_and_members.ignore_entities.service_state_values",
"Service states (including null) which indicate an entity should be ignored when looking at children service states; anything apart from RUNNING not in this list will be treated as not healthy (by default just ON_FIRE will mean not healthy)",
MutableSet.builder().addAll(Lifecycle.values()).add(null).remove(Lifecycle.RUNNING).remove(Lifecycle.ON_FIRE).build().asUnmodifiable());
+ @SuppressWarnings("serial")
+ public static final ConfigKey> IGNORE_ENTITIES_WITH_THESE_SERVICE_STATES_WHEN_STARTING = ConfigKeys.newConfigKey(new TypeToken>() {},
+ "enricher.service_state.children_and_members.ignore_entities.starting_service_state_values",
+ "Service states (including null) which indicate an entity should be ignored when looking at children service states; anything apart from RUNNING not in this list will be treated as not healthy (by default just ON_FIRE will mean not healthy)",
+ MutableSet.builder().addAll(Lifecycle.values()).add(null).remove(Lifecycle.RUNNING).remove(Lifecycle.ON_FIRE).remove(Lifecycle.STARTING).remove(Lifecycle.CREATED).build().asUnmodifiable());
protected String getKeyForMapSensor() {
return Preconditions.checkNotNull(super.getUniqueTag());
@@ -552,7 +557,10 @@ protected Object computeServiceNotUp() {
Map values = getValues(SERVICE_UP);
List violators = MutableList.of();
boolean ignoreNull = getConfig(IGNORE_ENTITIES_WITH_SERVICE_UP_NULL);
- Set ignoreStates = getConfig(IGNORE_ENTITIES_WITH_THESE_SERVICE_STATES);
+ Lifecycle current = entity.sensors().get(Attributes.SERVICE_STATE_ACTUAL);
+ Set ignoreStates = (current == Lifecycle.STARTING)
+ ? config().get(IGNORE_ENTITIES_WITH_THESE_SERVICE_STATES_WHEN_STARTING)
+ : config().get(IGNORE_ENTITIES_WITH_THESE_SERVICE_STATES);
int entries=0;
int numUp=0;
for (Map.Entry state: values.entrySet()) {
@@ -590,7 +598,10 @@ protected Object computeServiceProblems() {
Map values = getValues(SERVICE_STATE_ACTUAL);
int numRunning=0;
List onesNotHealthy=MutableList.of();
- Set ignoreStates = getConfig(IGNORE_ENTITIES_WITH_THESE_SERVICE_STATES);
+ Lifecycle current = entity.sensors().get(Attributes.SERVICE_STATE_ACTUAL);
+ Set ignoreStates = (current == Lifecycle.STARTING)
+ ? config().get(IGNORE_ENTITIES_WITH_THESE_SERVICE_STATES_WHEN_STARTING)
+ : config().get(IGNORE_ENTITIES_WITH_THESE_SERVICE_STATES);
for (Map.Entry state: values.entrySet()) {
if (state.getValue()==Lifecycle.RUNNING) numRunning++;
else if (!ignoreStates.contains(state.getValue()))
diff --git a/core/src/main/java/org/apache/brooklyn/core/sensor/AddSensor.java b/core/src/main/java/org/apache/brooklyn/core/sensor/AddSensor.java
new file mode 100644
index 0000000000..12ef257e81
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/core/sensor/AddSensor.java
@@ -0,0 +1,128 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.core.sensor;
+
+import java.util.Map;
+
+import org.apache.brooklyn.api.entity.Entity;
+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.util.core.ClassLoaderUtils;
+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.base.Preconditions;
+
+/**
+ * Creates a new {@link AttributeSensor} on an entity.
+ *
+ * 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
+ */
+public class AddSensor implements EntityInitializer {
+
+ public static final ConfigKey SENSOR_NAME = ConfigKeys.newStringConfigKey("name", "The name of the sensor to create");
+ public static final ConfigKey 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 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 AttributeSensor sensor;
+ protected final ConfigBag params;
+
+ public AddSensor(Map 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.params = params;
+ }
+
+ @Override
+ public void apply(EntityLocal entity) {
+ sensor = newSensor(entity);
+ ((EntityInternal) entity).getMutableEntityType().addSensor(sensor);
+ }
+
+ private AttributeSensor newSensor(Entity entity) {
+ String className = getFullClassName(type);
+ Class clazz = getType(entity, className);
+ return Sensors.newSensor(clazz, name);
+ }
+
+ @SuppressWarnings("unchecked")
+ protected Class getType(Entity entity, String className) {
+ try {
+ // TODO use OSGi loader (low priority however); also ensure that allows primitives
+ Maybe> primitive = Boxing.getPrimitiveType(className);
+ if (primitive.isPresent()) return (Class) primitive.get();
+
+ return (Class) new ClassLoaderUtils(this, entity).loadClass(className);
+ } catch (ClassNotFoundException e) {
+ if (!className.contains(".")) {
+ // could be assuming "java.lang" package; try again with that
+ try {
+ return (Class) 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;
+ }
+ }
+
+}
diff --git a/core/src/main/java/org/apache/brooklyn/core/sensor/http/HttpRequestSensor.java b/core/src/main/java/org/apache/brooklyn/core/sensor/http/HttpRequestSensor.java
index 22e24f1950..9358f3d1f1 100644
--- a/core/src/main/java/org/apache/brooklyn/core/sensor/http/HttpRequestSensor.java
+++ b/core/src/main/java/org/apache/brooklyn/core/sensor/http/HttpRequestSensor.java
@@ -25,8 +25,8 @@
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.AddSensor;
import org.apache.brooklyn.core.entity.EntityInitializers;
+import org.apache.brooklyn.core.sensor.AddSensor;
import org.apache.brooklyn.core.sensor.ssh.SshCommandSensor;
import org.apache.brooklyn.feed.http.HttpFeed;
import org.apache.brooklyn.feed.http.HttpPollConfig;
@@ -35,7 +35,6 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import com.google.common.annotations.Beta;
import com.google.common.base.Functions;
import com.google.common.base.Supplier;
@@ -47,7 +46,6 @@
*
* @see SshCommandSensor
*/
-@Beta
public final class HttpRequestSensor extends AddSensor {
private static final Logger LOG = LoggerFactory.getLogger(HttpRequestSensor.class);
diff --git a/core/src/main/java/org/apache/brooklyn/core/sensor/password/CreatePasswordSensor.java b/core/src/main/java/org/apache/brooklyn/core/sensor/password/CreatePasswordSensor.java
index 7b7a908bdf..7d7dda1200 100644
--- a/core/src/main/java/org/apache/brooklyn/core/sensor/password/CreatePasswordSensor.java
+++ b/core/src/main/java/org/apache/brooklyn/core/sensor/password/CreatePasswordSensor.java
@@ -23,7 +23,7 @@
import org.apache.brooklyn.api.entity.EntityLocal;
import org.apache.brooklyn.config.ConfigKey;
import org.apache.brooklyn.core.config.ConfigKeys;
-import org.apache.brooklyn.core.effector.AddSensor;
+import org.apache.brooklyn.core.sensor.AddSensor;
import org.apache.brooklyn.util.core.config.ConfigBag;
import org.apache.brooklyn.util.text.Identifiers;
diff --git a/core/src/main/java/org/apache/brooklyn/core/sensor/ssh/SshCommandSensor.java b/core/src/main/java/org/apache/brooklyn/core/sensor/ssh/SshCommandSensor.java
index fc93d7425d..24d19c55a2 100644
--- a/core/src/main/java/org/apache/brooklyn/core/sensor/ssh/SshCommandSensor.java
+++ b/core/src/main/java/org/apache/brooklyn/core/sensor/ssh/SshCommandSensor.java
@@ -22,28 +22,17 @@
import java.util.concurrent.ExecutionException;
import org.apache.brooklyn.api.entity.Entity;
-import org.apache.brooklyn.feed.CommandPollConfig;
-import org.apache.brooklyn.feed.ssh.SshFeed;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.google.common.annotations.Beta;
-import com.google.common.base.Function;
-import com.google.common.base.Functions;
-import com.google.common.base.Preconditions;
-import com.google.common.base.Supplier;
-
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.AddSensor;
import org.apache.brooklyn.core.entity.BrooklynConfigKeys;
import org.apache.brooklyn.core.entity.EntityInternal;
+import org.apache.brooklyn.core.sensor.AddSensor;
import org.apache.brooklyn.core.sensor.http.HttpRequestSensor;
-import org.apache.brooklyn.feed.AbstractCommandFeed;
-import org.apache.brooklyn.feed.ssh.SshPollConfig;
+import org.apache.brooklyn.feed.CommandPollConfig;
+import org.apache.brooklyn.feed.ssh.SshFeed;
import org.apache.brooklyn.feed.ssh.SshValueFunctions;
import org.apache.brooklyn.util.collections.MutableMap;
import org.apache.brooklyn.util.core.config.ConfigBag;
@@ -53,6 +42,13 @@
import org.apache.brooklyn.util.exceptions.Exceptions;
import org.apache.brooklyn.util.os.Os;
import org.apache.brooklyn.util.text.Strings;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Function;
+import com.google.common.base.Functions;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Supplier;
/**
* Configurable {@link EntityInitializer} which adds an SSH sensor feed running the command
supplied
@@ -61,7 +57,6 @@
*
* @see HttpRequestSensor
*/
-@Beta
public final class SshCommandSensor extends AddSensor {
private static final Logger LOG = LoggerFactory.getLogger(SshCommandSensor.class);
@@ -142,7 +137,6 @@ public T apply(String input) {
entity.addFeed(feed);
}
- @Beta
public static String makeCommandExecutingInDirectory(String command, String executionDir, Entity entity) {
String finalCommand = command;
String execDir = executionDir;
diff --git a/core/src/main/java/org/apache/brooklyn/entity/group/EntityPredicateRemovalStrategy.java b/core/src/main/java/org/apache/brooklyn/entity/group/EntityPredicateRemovalStrategy.java
new file mode 100644
index 0000000000..9b8a6eb3a8
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/entity/group/EntityPredicateRemovalStrategy.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.entity.group;
+
+import java.util.Collection;
+
+import javax.annotation.Nullable;
+
+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.entity.EntityPredicates;
+
+import com.google.common.base.Optional;
+import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
+import com.google.common.collect.Iterables;
+import com.google.common.reflect.TypeToken;
+
+public class EntityPredicateRemovalStrategy extends RemovalStrategy {
+
+ public static final ConfigKey> ENTITY_PREDICATE = ConfigKeys.builder(new TypeToken>() { })
+ .name("predicate")
+ .description("A predicate that will match the entities to be removed")
+ .constraint(Predicates.notNull())
+ .defaultValue(Predicates.not(EntityPredicates.isServiceUp()))
+ .build();
+
+ @Nullable
+ @Override
+ public Entity apply(@Nullable Collection input) {
+ Predicate predicate = config().get(ENTITY_PREDICATE);
+ Optional entity = Iterables.tryFind(input, predicate);
+ return entity.orNull();
+ }
+}
diff --git a/core/src/main/java/org/apache/brooklyn/policy/InvokeEffectorOnCollectionSensorChange.java b/core/src/main/java/org/apache/brooklyn/policy/InvokeEffectorOnCollectionSensorChange.java
index 352d40d13d..8352245cb9 100644
--- a/core/src/main/java/org/apache/brooklyn/policy/InvokeEffectorOnCollectionSensorChange.java
+++ b/core/src/main/java/org/apache/brooklyn/policy/InvokeEffectorOnCollectionSensorChange.java
@@ -159,14 +159,14 @@ public void onEvent(SensorEvent> event) {
}
private void onAdded(Object newElement) {
- onEvent(getOnAddedEffector(), newElement);
+ invokeEffector(getOnAddedEffector(), newElement);
}
private void onRemoved(Object newElement) {
- onEvent(getOnRemovedEffector(), newElement);
+ invokeEffector(getOnRemovedEffector(), newElement);
}
- private void onEvent(String effectorName, Object parameter) {
+ private void invokeEffector(String effectorName, Object parameter) {
Maybe> effector = getEffector(effectorName);
if (effector.isPresentAndNonNull()) {
final Map parameters;
diff --git a/core/src/main/java/org/apache/brooklyn/util/core/json/BrooklynObjectsJsonMapper.java b/core/src/main/java/org/apache/brooklyn/util/core/json/BrooklynObjectsJsonMapper.java
index 7e96457769..8b289425d2 100644
--- a/core/src/main/java/org/apache/brooklyn/util/core/json/BrooklynObjectsJsonMapper.java
+++ b/core/src/main/java/org/apache/brooklyn/util/core/json/BrooklynObjectsJsonMapper.java
@@ -28,7 +28,7 @@ public static ObjectMapper newMapper(ManagementContext mgmt) {
ObjectMapper mapper = new ObjectMapper();
mapper.setSerializerProvider(sp);
- mapper.setVisibilityChecker(new PossiblyStrictPreferringFieldsVisibilityChecker());
+ mapper.setVisibility(new PossiblyStrictPreferringFieldsVisibilityChecker());
SimpleModule mapperModule = new SimpleModule("Brooklyn", new Version(0, 0, 0, "ignored", null, null));
diff --git a/policy/src/main/java/org/apache/brooklyn/policy/action/AbstractScheduledEffectorPolicy.java b/policy/src/main/java/org/apache/brooklyn/policy/action/AbstractScheduledEffectorPolicy.java
new file mode 100644
index 0000000000..2afff65e3c
--- /dev/null
+++ b/policy/src/main/java/org/apache/brooklyn/policy/action/AbstractScheduledEffectorPolicy.java
@@ -0,0 +1,163 @@
+/*
+ * 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.policy.action;
+
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.Map;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+
+import org.apache.brooklyn.api.effector.Effector;
+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.entity.EntityInitializers;
+import org.apache.brooklyn.core.policy.AbstractPolicy;
+import org.apache.brooklyn.util.collections.MutableMap;
+import org.apache.brooklyn.util.core.config.ConfigBag;
+import org.apache.brooklyn.util.core.config.ResolvingConfigBag;
+import org.apache.brooklyn.util.core.task.Tasks;
+import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.apache.brooklyn.util.guava.Maybe;
+import org.apache.brooklyn.util.time.Duration;
+import org.apache.brooklyn.util.time.DurationPredicates;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.Predicates;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+import com.google.common.reflect.TypeToken;
+
+@Beta
+public abstract class AbstractScheduledEffectorPolicy extends AbstractPolicy implements Runnable {
+
+ private static final Logger LOG = LoggerFactory.getLogger(AbstractScheduledEffectorPolicy.class);
+
+ public static final String TIME_FORMAT = "HH:mm:ss";
+ public static final String NOW = "now";
+ public static final String IMMEDIATELY = "immediately";
+
+ private static final DateFormat FORMATTER = SimpleDateFormat.getTimeInstance();
+
+ public static final ConfigKey EFFECTOR = ConfigKeys.builder(String.class)
+ .name("effector")
+ .description("The effector to be executed by this policy")
+ .constraint(Predicates.notNull())
+ .build();
+
+ public static final ConfigKey> EFFECTOR_ARGUMENTS = ConfigKeys.builder(new TypeToken>() { })
+ .name("args")
+ .description("The effector arguments and their values")
+ .constraint(Predicates.notNull())
+ .defaultValue(ImmutableMap.of())
+ .build();
+
+ public static final ConfigKey TIME = ConfigKeys.builder(String.class)
+ .name("time")
+ .description("An optional time when this policy should be first executed, formatted as HH:mm:ss")
+ .build();
+
+ public static final ConfigKey WAIT = ConfigKeys.builder(Duration.class)
+ .name("wait")
+ .description("An optional duration after which this policy should be first executed. The time config takes precedence if present")
+ .constraint(Predicates.or(Predicates.isNull(), DurationPredicates.positive()))
+ .build();
+
+ protected final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
+ protected final Object mutex = new Object[0];
+
+ protected Effector> effector;
+
+ public AbstractScheduledEffectorPolicy() {
+ this(MutableMap.of());
+ }
+
+ public AbstractScheduledEffectorPolicy(Map props) {
+ super(props);
+ }
+
+ public void setEntity(EntityLocal entity) {
+ super.setEntity(entity);
+ effector = getEffector();
+ }
+
+ @Override
+ public void destroy(){
+ super.destroy();
+ executor.shutdownNow();
+ }
+
+ protected Effector> getEffector() {
+ String effectorName = config().get(EFFECTOR);
+ Maybe> effector = entity.getEntityType().getEffectorByName(effectorName);
+ if (effector.isAbsentOrNull()) {
+ throw new IllegalStateException("Cannot find effector " + effectorName);
+ }
+ return effector.get();
+ }
+
+ protected Duration getWaitUntil(String time) {
+ if (time.equalsIgnoreCase(NOW) || time.equalsIgnoreCase(IMMEDIATELY)) {
+ return Duration.ZERO;
+ }
+ try {
+ Calendar now = Calendar.getInstance();
+ Calendar when = Calendar.getInstance();
+ boolean formatted = time.contains(":"); // FIXME deprecated TimeDuration coercion
+ Date parsed = formatted ? FORMATTER.parse(time) : new Date(Long.parseLong(time) * 1000);
+ when.setTime(parsed);
+ when.set(now.get(Calendar.YEAR), now.get(Calendar.MONTH), now.get(Calendar.DATE));
+ if (when.before(now)) {
+ when.add(Calendar.DATE, 1);
+ }
+ return Duration.millis(Math.max(0, when.getTimeInMillis() - now.getTimeInMillis()));
+ } catch (ParseException | NumberFormatException e) {
+ LOG.warn("{}: Time should be formatted as {}: {}", new Object[] { this, TIME_FORMAT, e.getMessage() });
+ throw Exceptions.propagate(e);
+ }
+ }
+
+ @Override
+ public void run() {
+ synchronized (mutex) {
+ try {
+ ConfigBag bag = ResolvingConfigBag.newInstanceExtending(getManagementContext(), config().getBag());
+ Map args = EntityInitializers.resolve(bag, EFFECTOR_ARGUMENTS);
+ LOG.debug("{}: Resolving arguments for {}: {}", new Object[] { this, effector.getName(), Iterables.toString(args.keySet()) });
+ Map resolved = (Map) Tasks.resolving(args, Object.class)
+ .deep(true)
+ .context(entity)
+ .get();
+
+ LOG.debug("{}: Invoking effector on {}, {}({})", new Object[] { this, entity, effector.getName(), resolved });
+ Object result = entity.invoke(effector, resolved).getUnchecked();
+ LOG.debug("{}: Effector {} returned {}", new Object[] { this, effector.getName(), result });
+ } catch (Throwable t) {
+ LOG.warn("{}: Exception running {}: {}", new Object[] { this, effector.getName(), t.getMessage() });
+ Exceptions.propagate(t);
+ }
+ }
+ }
+}
diff --git a/policy/src/main/java/org/apache/brooklyn/policy/action/PeriodicEffectorPolicy.java b/policy/src/main/java/org/apache/brooklyn/policy/action/PeriodicEffectorPolicy.java
new file mode 100644
index 0000000000..119fe64e57
--- /dev/null
+++ b/policy/src/main/java/org/apache/brooklyn/policy/action/PeriodicEffectorPolicy.java
@@ -0,0 +1,111 @@
+/*
+ * 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.policy.action;
+
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.apache.brooklyn.api.entity.EntityLocal;
+import org.apache.brooklyn.api.sensor.AttributeSensor;
+import org.apache.brooklyn.api.sensor.SensorEvent;
+import org.apache.brooklyn.api.sensor.SensorEventListener;
+import org.apache.brooklyn.config.ConfigKey;
+import org.apache.brooklyn.core.config.ConfigKeys;
+import org.apache.brooklyn.core.sensor.Sensors;
+import org.apache.brooklyn.util.collections.MutableMap;
+import org.apache.brooklyn.util.time.Duration;
+import org.apache.brooklyn.util.time.DurationPredicates;
+import org.apache.brooklyn.util.time.Time;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.Preconditions;
+
+/**
+ * {@code
+ * brooklyn.policies:
+ * - type: org.apache.brooklyn.policy.action.ScheduledEffectorPolicy
+ * brooklyn.config:
+ * effector: repaveCluster
+ * args:
+ * k: $brooklyn:config("repave.size")
+ * period: 1 day
+ * time: 17:00:00
+ * }
+ */
+@Beta
+public class PeriodicEffectorPolicy extends AbstractScheduledEffectorPolicy {
+
+ private static final Logger LOG = LoggerFactory.getLogger(PeriodicEffectorPolicy.class);
+
+ public static final ConfigKey PERIOD = ConfigKeys.builder(Duration.class)
+ .name("period")
+ .description("The duration between executions of this policy")
+ .constraint(DurationPredicates.positive())
+ .defaultValue(Duration.hours(1))
+ .build();
+
+ public static final AttributeSensor START_SCHEDULER = Sensors.newBooleanSensor("scheduler.start", "Start the periodic effector execution after this becomes true");
+
+ protected AtomicBoolean running = new AtomicBoolean(false);
+
+ public PeriodicEffectorPolicy() {
+ this(MutableMap.of());
+ }
+
+ public PeriodicEffectorPolicy(Map props) {
+ super(props);
+ }
+
+ @Override
+ public void setEntity(final EntityLocal entity) {
+ super.setEntity(entity);
+
+ subscriptions().subscribe(entity, START_SCHEDULER, handler);
+ }
+
+ private final SensorEventListener handler = new SensorEventListener() {
+ @Override
+ public void onEvent(SensorEvent event) {
+ synchronized (mutex) {
+ LOG.debug("{}: Got event {}", PeriodicEffectorPolicy.this, event);
+ if (event.getSensor().getName().equals(START_SCHEDULER.getName())) {
+ Boolean start = (Boolean) event.getValue();
+ if (start && running.compareAndSet(false, true)) {
+ Duration period = Preconditions.checkNotNull(config().get(PERIOD), "The period must be configured for this policy");
+ String time = config().get(TIME);
+ Duration wait = config().get(WAIT);
+ if (time != null) {
+ wait = getWaitUntil(time);
+ } else if (wait == null) {
+ wait = period;
+ }
+
+ LOG.debug("{}: Scheduling {} every {} in {}", new Object[] { PeriodicEffectorPolicy.this, effector.getName(),
+ Time.fromDurationToTimeStringRounded().apply(period), Time.fromDurationToTimeStringRounded().apply(wait) });
+ executor.scheduleAtFixedRate(PeriodicEffectorPolicy.this, wait.toMilliseconds(), period.toMilliseconds(), TimeUnit.MILLISECONDS);
+ }
+ }
+ }
+ }
+ };
+
+}
diff --git a/policy/src/main/java/org/apache/brooklyn/policy/action/ScheduledEffectorPolicy.java b/policy/src/main/java/org/apache/brooklyn/policy/action/ScheduledEffectorPolicy.java
new file mode 100644
index 0000000000..74a310a688
--- /dev/null
+++ b/policy/src/main/java/org/apache/brooklyn/policy/action/ScheduledEffectorPolicy.java
@@ -0,0 +1,111 @@
+/*
+ * 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.policy.action;
+
+import java.util.Date;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.brooklyn.api.entity.EntityLocal;
+import org.apache.brooklyn.api.sensor.AttributeSensor;
+import org.apache.brooklyn.api.sensor.SensorEvent;
+import org.apache.brooklyn.api.sensor.SensorEventListener;
+import org.apache.brooklyn.core.sensor.Sensors;
+import org.apache.brooklyn.util.collections.MutableMap;
+import org.apache.brooklyn.util.time.Duration;
+import org.apache.brooklyn.util.time.Time;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.annotations.Beta;
+
+/**
+ * {@code
+ * brooklyn.policies:
+ * - type: org.apache.brooklyn.policy.action.ScheduledEffectorPolicy
+ * brooklyn.config:
+ * effector: repaveCluster
+ * args:
+ * k: $brooklyn:config("repave.size")
+ * time: 12:00:00
+ * }
+ */
+@Beta
+public class ScheduledEffectorPolicy extends AbstractScheduledEffectorPolicy {
+
+ private static final Logger LOG = LoggerFactory.getLogger(ScheduledEffectorPolicy.class);
+
+ public static final AttributeSensor INVOKE_IMMEDIATELY = Sensors.newBooleanSensor("scheduler.invoke.now", "Invoke the configured effector immediately when this becomes true");
+ public static final AttributeSensor INVOKE_AT = Sensors.newSensor(Date.class, "scheduler.invoke.at", "Invoke the configured effector at this time");
+
+ public ScheduledEffectorPolicy() {
+ this(MutableMap.of());
+ }
+
+ public ScheduledEffectorPolicy(Map props) {
+ super(props);
+ }
+
+ @Override
+ public void setEntity(final EntityLocal entity) {
+ super.setEntity(entity);
+
+ subscriptions().subscribe(entity, INVOKE_IMMEDIATELY, handler);
+ subscriptions().subscribe(entity, INVOKE_AT, handler);
+
+ String time = config().get(TIME);
+ Duration wait = config().get(WAIT);
+ if (time != null) {
+ scheduleAt(time);
+ } else if (wait != null) {
+ LOG.debug("{}: Scheduling {} in {} ({} ms)",
+ new Object[] { this, effector.getName(), Time.fromDurationToTimeStringRounded().apply(wait), wait.toMilliseconds() });
+ executor.schedule(this, wait.toMilliseconds(), TimeUnit.MILLISECONDS);
+ }
+ }
+
+ protected void scheduleAt(String time) {
+ Duration wait = getWaitUntil(time);
+ LOG.debug("{}: Scheduling {} at {} (in {})",
+ new Object[] { this, effector.getName(), time, Time.fromDurationToTimeStringRounded().apply(wait) });
+ executor.schedule(this, wait.toMilliseconds(), TimeUnit.MILLISECONDS);
+ }
+
+ private final SensorEventListener handler = new SensorEventListener() {
+ @Override
+ public void onEvent(SensorEvent event) {
+ synchronized (mutex) {
+ LOG.debug("{}: Got event {}", ScheduledEffectorPolicy.this, event);
+ if (event.getSensor().getName().equals(INVOKE_AT.getName())) {
+ String time = (String) event.getValue();
+ if (time != null) {
+ scheduleAt(time);
+ }
+ }
+ if (event.getSensor().getName().equals(INVOKE_IMMEDIATELY.getName())) {
+ Boolean invoke = (Boolean) event.getValue();
+ if (invoke) {
+ executor.submit(ScheduledEffectorPolicy.this);
+ }
+ }
+ }
+ }
+ };
+
+}
diff --git a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/EffectorSummary.java b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/EffectorSummary.java
index 3e09c73f3f..9227144604 100644
--- a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/EffectorSummary.java
+++ b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/EffectorSummary.java
@@ -23,6 +23,8 @@
import java.util.Map;
import java.util.Set;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.google.common.base.MoreObjects;
@@ -38,7 +40,7 @@ public static class ParameterSummary implements HasName, Serializable {
private final String name;
private final String type;
- @JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL)
+ @JsonInclude(Include.NON_NULL)
private final String description;
private final T defaultValue;
private final boolean shouldSanitize;
diff --git a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/EntityDetail.java b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/EntityDetail.java
index 567c40bda4..b2a22f7f52 100644
--- a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/EntityDetail.java
+++ b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/EntityDetail.java
@@ -18,23 +18,25 @@
*/
package org.apache.brooklyn.rest.domain;
-import com.fasterxml.jackson.annotation.JsonProperty;
-import com.fasterxml.jackson.databind.annotation.JsonSerialize;
-import com.google.common.collect.ImmutableList;
-import org.apache.brooklyn.core.entity.lifecycle.Lifecycle;
-
import java.net.URI;
import java.util.Collections;
import java.util.List;
import java.util.Map;
+import org.apache.brooklyn.core.entity.lifecycle.Lifecycle;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.common.collect.ImmutableList;
+
public class EntityDetail extends EntitySummary {
private static final long serialVersionUID = 100490507982229165L;
private final String applicationId;
private final String parentId;
- @JsonSerialize(include = JsonSerialize.Inclusion.NON_EMPTY)
+ @JsonInclude(Include.NON_NULL)
private final List children;
private final List groupIds;
private final List> members;
diff --git a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/EntitySpec.java b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/EntitySpec.java
index b4d9c04110..494fbd8ca7 100644
--- a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/EntitySpec.java
+++ b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/EntitySpec.java
@@ -20,14 +20,14 @@
import static com.google.common.base.Preconditions.checkNotNull;
-import com.fasterxml.jackson.annotation.JsonProperty;
-import com.google.common.collect.ImmutableMap;
-
import java.io.Serializable;
import java.util.Collections;
import java.util.Map;
import java.util.Objects;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.common.collect.ImmutableMap;
+
public class EntitySpec implements HasName, Serializable {
private static final long serialVersionUID = -3882575609132757188L;
diff --git a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/EntitySummary.java b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/EntitySummary.java
index e0a585a116..ae123ef421 100644
--- a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/EntitySummary.java
+++ b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/EntitySummary.java
@@ -18,15 +18,16 @@
*/
package org.apache.brooklyn.rest.domain;
-import com.fasterxml.jackson.annotation.JsonProperty;
-import com.fasterxml.jackson.databind.annotation.JsonSerialize;
-import com.google.common.collect.ImmutableMap;
-
import java.io.Serializable;
import java.net.URI;
import java.util.Map;
import java.util.Objects;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.common.collect.ImmutableMap;
+
public class EntitySummary implements HasId, HasName, Serializable {
private static final long serialVersionUID = 100490507982229165L;
@@ -34,7 +35,7 @@ public class EntitySummary implements HasId, HasName, Serializable {
private final String id;
private final String name;
private final String type;
- @JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL)
+ @JsonInclude(Include.NON_NULL)
private final String catalogItemId;
private final Map links;
diff --git a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/PolicyConfigSummary.java b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/PolicyConfigSummary.java
index 8115ab97a5..8ffaf6ef93 100644
--- a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/PolicyConfigSummary.java
+++ b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/PolicyConfigSummary.java
@@ -22,10 +22,11 @@
import java.util.Map;
import java.util.Objects;
+import org.apache.brooklyn.config.ConfigKey;
+
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.google.common.collect.ImmutableMap;
-import org.apache.brooklyn.config.ConfigKey;
public class PolicyConfigSummary extends ConfigSummary {
diff --git a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/ScriptExecutionSummary.java b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/ScriptExecutionSummary.java
index c84573c3d9..27ccdad341 100644
--- a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/ScriptExecutionSummary.java
+++ b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/ScriptExecutionSummary.java
@@ -21,20 +21,21 @@
import java.io.Serializable;
import java.util.Objects;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.annotation.JsonProperty;
-import com.fasterxml.jackson.databind.annotation.JsonSerialize;
public class ScriptExecutionSummary implements Serializable {
private static final long serialVersionUID = -7707936602991185960L;
- @JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL)
+ @JsonInclude(Include.NON_NULL)
private final Object result;
- @JsonSerialize(include = JsonSerialize.Inclusion.NON_EMPTY)
+ @JsonInclude(Include.NON_NULL)
private final String problem;
- @JsonSerialize(include = JsonSerialize.Inclusion.NON_EMPTY)
+ @JsonInclude(Include.NON_EMPTY)
private final String stdout;
- @JsonSerialize(include = JsonSerialize.Inclusion.NON_EMPTY)
+ @JsonInclude(Include.NON_EMPTY)
private final String stderr;
public ScriptExecutionSummary(
diff --git a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/TaskSummary.java b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/TaskSummary.java
index eef57e6516..e3b0e9509a 100644
--- a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/TaskSummary.java
+++ b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/TaskSummary.java
@@ -30,8 +30,9 @@
import org.apache.brooklyn.util.collections.Jsonya;
import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.annotation.JsonProperty;
-import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
@@ -58,14 +59,14 @@ public class TaskSummary implements HasId, Serializable {
private final List children;
private final LinkWithMetadata submittedByTask;
- @JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL)
+ @JsonInclude(Include.NON_NULL)
private final LinkWithMetadata blockingTask;
- @JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL)
+ @JsonInclude(Include.NON_NULL)
private final String blockingDetails;
private final String detailedStatus;
- @JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL)
+ @JsonInclude(Include.NON_NULL)
private final Map streams;
private final Map links;
diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/collections/CollectionFunctionals.java b/utils/common/src/main/java/org/apache/brooklyn/util/collections/CollectionFunctionals.java
index a264b7e8e8..61426995ee 100644
--- a/utils/common/src/main/java/org/apache/brooklyn/util/collections/CollectionFunctionals.java
+++ b/utils/common/src/main/java/org/apache/brooklyn/util/collections/CollectionFunctionals.java
@@ -184,14 +184,28 @@ private LimitFunction(int max) {
@Override
public List apply(I input) {
if (input==null) return null;
- MutableList result = MutableList.of();
- for (T i: input) {
- result.add(i);
- if (result.size()>=max)
- return result;
- }
- return result;
+ return MutableList.copyOf(Iterables.limit(input, max));
+ }
+ }
+
+ public static final class IterableTransformerFunction> implements Function> {
+ private final Function function;
+
+ private IterableTransformerFunction(Function function) {
+ this.function = function;
+ }
+
+ @Override
+ public List apply(I input) {
+ if (input==null) return null;
+ return MutableList.copyOf(Iterables.transform(input, function));
}
+
+ @Override public String toString() { return "iterableTransformer"; }
+ }
+
+ public static > Function> iterableTransformer(Function function) {
+ return new IterableTransformerFunction(function);
}
// ---------
diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/text/StringFunctions.java b/utils/common/src/main/java/org/apache/brooklyn/util/text/StringFunctions.java
index ddf1914d05..5ce718bbf6 100644
--- a/utils/common/src/main/java/org/apache/brooklyn/util/text/StringFunctions.java
+++ b/utils/common/src/main/java/org/apache/brooklyn/util/text/StringFunctions.java
@@ -28,6 +28,8 @@
import com.google.common.base.Functions;
import com.google.common.base.Objects;
import com.google.common.base.Preconditions;
+import com.google.common.base.Splitter;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
public class StringFunctions {
@@ -279,6 +281,24 @@ public String apply(@Nullable Iterable> input) {
}
}
+ /** splits a string into a collection of elements */
+ public static Function> splitter(final String separator) {
+ return new SplitterFunction(separator);
+ }
+
+ private static class SplitterFunction implements Function> {
+ private final String separator;
+
+ public SplitterFunction(String separator) {
+ this.separator = separator;
+ }
+ @Override
+ public Iterable> apply(@Nullable String input) {
+ if (input == null) return ImmutableList.of();
+ return ImmutableList.copyOf(Splitter.on(separator).split(input));
+ }
+ }
+
/** joins the given objects in a collection as a toString with the given separator */
public static Function, String> joiner(final String separator) {
return new JoinerFunction(separator);