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);