This is an automated email from the ASF dual-hosted git repository. heneveld pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/brooklyn-server.git
commit 33a68bd303689f99dd4fd7e29c030aeb8ecf5d97 Author: Alex Heneveld <[email protected]> AuthorDate: Wed Jul 17 14:59:52 2024 +0100 add support for dsl to take maps as function args, and to chain when necessary --- .../spi/dsl/BrooklynDslDeferredSupplier.java | 10 ++ .../brooklyn/spi/dsl/DslDeferredFunctionCall.java | 13 +- .../spi/dsl/DslDeferredPropertyAccess.java | 13 +- .../spi/dsl/methods/BrooklynDslCommon.java | 133 +++++++++++++-------- .../brooklyn/spi/dsl/methods/DslComponent.java | 81 +++++++------ .../spi/dsl/methods/DslToStringHelpers.java | 123 +++++++++++++++---- .../brooklyn/camp/brooklyn/ConfigYamlTest.java | 55 +++++++++ .../core/entity/lifecycle/ServiceStateLogic.java | 2 +- 8 files changed, 315 insertions(+), 115 deletions(-) diff --git a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/BrooklynDslDeferredSupplier.java b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/BrooklynDslDeferredSupplier.java index 04eee1cb34..971e571c09 100644 --- a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/BrooklynDslDeferredSupplier.java +++ b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/BrooklynDslDeferredSupplier.java @@ -161,4 +161,14 @@ public abstract class BrooklynDslDeferredSupplier<T> implements DeferredSupplier Tasks.addTagDynamically(tag); } + + public String toString() { + try { + return toDslString(false); + } catch (DslToStringHelpers.YamlSyntaxRequired e) { + return toDslString(true); + } + } + + public abstract String toDslString(boolean yamlAllowed); } diff --git a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/DslDeferredFunctionCall.java b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/DslDeferredFunctionCall.java index 5d90cb63b7..9c24474805 100644 --- a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/DslDeferredFunctionCall.java +++ b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/DslDeferredFunctionCall.java @@ -28,6 +28,7 @@ import org.apache.brooklyn.api.mgmt.Task; import org.apache.brooklyn.camp.brooklyn.spi.dsl.methods.BrooklynDslCommon; import org.apache.brooklyn.camp.brooklyn.spi.dsl.methods.DslToStringHelpers; import org.apache.brooklyn.core.mgmt.BrooklynTaskTags; +import org.apache.brooklyn.util.collections.MutableList; import org.apache.brooklyn.util.core.task.ImmediateSupplier; import org.apache.brooklyn.util.core.task.Tasks; import org.apache.brooklyn.util.core.task.ValueResolver; @@ -170,7 +171,13 @@ public class DslDeferredFunctionCall extends BrooklynDslDeferredSupplier<Object> instanceArgs = ImmutableList.builder().add(obj).addAll(args).build(); method = Reflections.getMethodFromArgs(instance, fnName, instanceArgs); if (method.isPresent()) return ; - + + method = Reflections.findMethodMaybe((Class)instance, fnName, List.class); + if (method.isPresent()) { + instanceArgs = ImmutableList.of(args); + return; + } + Maybe<?> facade; try { facade = Reflections.invokeMethodFromArgs(BrooklynDslCommon.DslFacades.class, "wrap", ImmutableList.of(obj)); @@ -239,11 +246,11 @@ public class DslDeferredFunctionCall extends BrooklynDslDeferredSupplier<Object> } @Override - public String toString() { + public String toDslString(boolean yamlAllowed) { // prefer the dsl set on us, if set if (dsl instanceof String && Strings.isNonBlank((String)dsl)) return (String)dsl; - return DslToStringHelpers.fn(DslToStringHelpers.internal(object) + "." + fnName, args); + return DslToStringHelpers.chainFunction(yamlAllowed, object, fnName, args); } } diff --git a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/DslDeferredPropertyAccess.java b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/DslDeferredPropertyAccess.java index f5f9a17719..5cbcdd8f66 100644 --- a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/DslDeferredPropertyAccess.java +++ b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/DslDeferredPropertyAccess.java @@ -21,6 +21,7 @@ package org.apache.brooklyn.camp.brooklyn.spi.dsl; import com.fasterxml.jackson.annotation.JsonIgnore; import org.apache.brooklyn.api.mgmt.Task; import org.apache.brooklyn.api.objs.Configurable; +import org.apache.brooklyn.camp.brooklyn.spi.dsl.methods.DslToStringHelpers; import org.apache.brooklyn.camp.brooklyn.spi.dsl.parse.PropertyAccess; import org.apache.brooklyn.config.ConfigKey; import org.apache.brooklyn.core.config.ConfigKeys; @@ -142,11 +143,15 @@ public class DslDeferredPropertyAccess extends BrooklynDslDeferredSupplier { } @Override - public String toString() { + public String toDslString(boolean yamlAllowed) { // prefer the dsl set on us, if set if (dsl instanceof String && Strings.isNonBlank((String)dsl)) return (String)dsl; - - return target + "[" + - (index instanceof Integer ? index : StringEscapes.JavaStringEscapes.wrapJavaString("" + index)) + "]"; + String indexS = "[" + (index instanceof Integer ? index : StringEscapes.JavaStringEscapes.wrapJavaString("" + index)) + "]"; + try { + return (target==null ? "null" : target.toDslString(false)) + indexS; + } catch (DslToStringHelpers.YamlSyntaxRequired e) { + if (!yamlAllowed) throw e; + return "{ $brooklyn:chain: [ "+target.toDslString(true)+", "+indexS+" ] }"; + } } } diff --git a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/methods/BrooklynDslCommon.java b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/methods/BrooklynDslCommon.java index 6dde3ff5b3..b22207cb67 100644 --- a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/methods/BrooklynDslCommon.java +++ b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/methods/BrooklynDslCommon.java @@ -19,34 +19,28 @@ package org.apache.brooklyn.camp.brooklyn.spi.dsl.methods; import com.fasterxml.jackson.annotation.JsonIgnore; -import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.base.Preconditions.checkState; -import java.util.*; - +import com.google.common.base.Function; +import com.google.common.base.Objects; +import com.google.common.base.Optional; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; import com.google.common.reflect.TypeToken; -import org.apache.brooklyn.api.entity.EntitySpec; -import org.apache.brooklyn.api.internal.AbstractBrooklynObjectSpec; -import org.apache.brooklyn.api.mgmt.ManagementContext; -import org.apache.brooklyn.api.mgmt.classloading.BrooklynClassLoadingContext; -import org.apache.brooklyn.api.typereg.RegisteredType; -import org.apache.brooklyn.camp.brooklyn.spi.dsl.DslUtils; -import static org.apache.brooklyn.camp.brooklyn.spi.dsl.DslUtils.resolved; - -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutionException; -import java.util.function.BiFunction; -import java.util.function.Supplier; - +import com.thoughtworks.xstream.annotations.XStreamConverter; import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.api.entity.EntitySpec; import org.apache.brooklyn.api.mgmt.ExecutionContext; +import org.apache.brooklyn.api.mgmt.ManagementContext; import org.apache.brooklyn.api.mgmt.Task; +import org.apache.brooklyn.api.mgmt.classloading.BrooklynClassLoadingContext; import org.apache.brooklyn.api.objs.Configurable; import org.apache.brooklyn.api.sensor.Sensor; +import org.apache.brooklyn.api.typereg.RegisteredType; import org.apache.brooklyn.camp.brooklyn.BrooklynCampReservedKeys; import org.apache.brooklyn.camp.brooklyn.spi.creation.BrooklynYamlTypeInstantiator; import org.apache.brooklyn.camp.brooklyn.spi.creation.EntitySpecConfiguration; -import org.apache.brooklyn.camp.brooklyn.spi.dsl.BrooklynDslDeferredSupplier; -import org.apache.brooklyn.camp.brooklyn.spi.dsl.DslAccessible; +import org.apache.brooklyn.camp.brooklyn.spi.dsl.*; import org.apache.brooklyn.camp.brooklyn.spi.dsl.methods.DslComponent.Scope; import org.apache.brooklyn.camp.brooklyn.spi.dsl.parse.WorkflowTransformGet; import org.apache.brooklyn.config.ConfigKey; @@ -55,7 +49,6 @@ import org.apache.brooklyn.core.config.external.ExternalConfigSupplier; import org.apache.brooklyn.core.entity.EntityDynamicType; import org.apache.brooklyn.core.entity.EntityInternal; import org.apache.brooklyn.core.mgmt.BrooklynTaskTags; -import org.apache.brooklyn.core.mgmt.classloading.JavaBrooklynClassLoadingContext; import org.apache.brooklyn.core.mgmt.internal.ExternalConfigSupplierRegistry; import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal; import org.apache.brooklyn.core.mgmt.persist.DeserializingClassRenamesProvider; @@ -81,12 +74,10 @@ import org.apache.brooklyn.util.core.task.Tasks; import org.apache.brooklyn.util.core.xstream.ObjectWithDefaultStringImplConverter; import org.apache.brooklyn.util.exceptions.Exceptions; import org.apache.brooklyn.util.guava.Maybe; -import org.apache.brooklyn.util.guava.TypeTokens; import org.apache.brooklyn.util.javalang.Reflections; import org.apache.brooklyn.util.javalang.coerce.TryCoercer; import org.apache.brooklyn.util.javalang.coerce.TypeCoercer; import org.apache.brooklyn.util.net.Urls; -import org.apache.brooklyn.util.os.Os; import org.apache.brooklyn.util.text.StringEscapes.JavaStringEscapes; import org.apache.brooklyn.util.text.Strings; import org.apache.brooklyn.util.yaml.Yamls; @@ -94,13 +85,15 @@ import org.apache.commons.beanutils.BeanUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.google.common.base.Function; -import com.google.common.base.Objects; -import com.google.common.base.Optional; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.Lists; -import com.google.common.collect.Maps; -import com.thoughtworks.xstream.annotations.XStreamConverter; +import java.util.*; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.function.BiFunction; +import java.util.function.Supplier; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; +import static org.apache.brooklyn.camp.brooklyn.spi.dsl.DslUtils.resolved; /** * static import functions which can be used in `$brooklyn:xxx` contexts @@ -327,14 +320,18 @@ public class BrooklynDslCommon { } @Override - public String toString() { - return DslToStringHelpers.concat(DslToStringHelpers.internal(obj), ".", DslToStringHelpers.fn("config", keyName)); + public String toDslString(boolean yamlAllowed) { + return DslToStringHelpers.chainFunction(yamlAllowed, obj, "config", Collections.singletonList(keyName)); } } @DslAccessible public static BrooklynDslDeferredSupplier<?> attributeWhenReady(Object sensorName) { - return new DslComponent(Scope.THIS, "").attributeWhenReady(sensorName); + return attributeWhenReady(sensorName, null); + } + @DslAccessible + public static BrooklynDslDeferredSupplier<?> attributeWhenReady(final Object sensorName, Map options) { + return new DslComponent(Scope.THIS, "").attributeWhenReady(sensorName, options); } @DslAccessible @@ -503,10 +500,8 @@ public class BrooklynDslCommon { } @Override - public String toString() { - return "$brooklyn:literal(" + - (literalString!=null ? JavaStringEscapes.wrapJavaString(literalString) : literalObjectJson) - +")"; + public String toDslString(boolean yamlAllowed) { + return DslToStringHelpers.fn1(yamlAllowed, "literal", literalString!=null ? literalString : literalObjectJson); } } @@ -593,8 +588,8 @@ public class BrooklynDslCommon { } @Override - public String toString() { - return "$brooklyn:urlEncode("+arg+")"; + public String toDslString(boolean yamlAllowed) { + return DslToStringHelpers.fn1(yamlAllowed, "urlEncode", arg); } } @@ -648,8 +643,8 @@ public class BrooklynDslCommon { } @Override - public String toString() { - return DslToStringHelpers.fn("formatString", MutableList.<Object>builder().add(pattern).addAll(args).build()); + public String toDslString(boolean yamlAllowed) { + return DslToStringHelpers.fn(yamlAllowed, "formatString", MutableList.<Object>builder().add(pattern).addAll(args).build()); } } @@ -705,8 +700,8 @@ public class BrooklynDslCommon { } @Override - public String toString() { - return DslToStringHelpers.fn("regexReplace", source, pattern, replacement); + public String toDslString(boolean yamlAllowed) { + return DslToStringHelpers.fn(yamlAllowed, "regexReplacement", source, pattern, replacement); } } @@ -950,15 +945,24 @@ public class BrooklynDslCommon { } @Override - public String toString() { + public String toDslString(boolean yamlAllowed) { // prefer the dsl set on us, if set if (dsl instanceof String && Strings.isNonBlank((String)dsl)) return (String)dsl; - Object arg = type != null ? type.getName() : typeName; + String arg = type != null ? type.getName() : typeName; + // old approach +// if (!constructorArgs.isEmpty()) { +// arg = MutableList.<Object>of(arg).appendAll(constructorArgs).toString(); +// } +// return DslToStringHelpers.fn1(yamlAllowed, "object", arg); + + // new approach; preserve list if (!constructorArgs.isEmpty()) { - arg = MutableList.of(arg).appendAll(constructorArgs).toString(); + return DslToStringHelpers.fn(yamlAllowed, "object", MutableList.<Object>of(arg).appendAll(constructorArgs)); + } else { + return DslToStringHelpers.fn1(yamlAllowed, "object", arg); } - return DslToStringHelpers.fn("object", arg); + } } @@ -1026,19 +1030,48 @@ public class BrooklynDslCommon { } @Override - public String toString() { - return DslToStringHelpers.fn("external", providerName, key); + public String toDslString(boolean yamlAllowed) { + return DslToStringHelpers.fn(yamlAllowed, "external", providerName, key); } } + @DslAccessible public static Object template(Object template) { return new DslComponent(Scope.THIS, "").template(template); } + @DslAccessible public static Object template(Object template, Map<? ,?> substitutions) { return new DslComponent(Scope.THIS, "").template(template, substitutions); } + @DslAccessible + public static Object chain(List<Object> parts) { + Iterator<Object> pi = parts.iterator(); + if (!pi.hasNext()) throw new IllegalArgumentException("chain must take at least one argument"); + Object resultO = pi.next(); + if (!(resultO instanceof BrooklynDslDeferredSupplier)) throw new IllegalStateException("Invalid argument to chain. First argument should be a DSL supplier."); + + BrooklynDslDeferredSupplier result = (BrooklynDslDeferredSupplier) resultO; + while (pi.hasNext()) { + Object next = pi.next(); + if (next instanceof String && ((String) next).startsWith("[")) { + String ns = ((String) next).substring(1).trim(); + if (!ns.endsWith("]")) throw new IllegalArgumentException("Property access must be wrapped in open and close square bracket"); + Object index; + if (ns.startsWith("\"")) index = JavaStringEscapes.unwrapJavaString(ns); + else index = Integer.parseInt(ns); + // dynamic indexes are not supported, neither here nor in the class below + result = new DslDeferredPropertyAccess(result, next); + + } else if (next instanceof Map && ((Map)next).size()==1) { + Map nm = (Map) next; + result = new DslDeferredFunctionCall(result, (String) Iterables.getOnlyElement(nm.keySet()), (List) Iterables.getOnlyElement(nm.values())); + } + } + return result; + } + public static class Functions { @DslAccessible public static Object regexReplacement(final Object pattern, final Object replacement) { @@ -1094,8 +1127,8 @@ public class BrooklynDslCommon { } @Override - public String toString() { - return DslToStringHelpers.fn("function.regexReplace", pattern, replacement); + public String toDslString(boolean yamlAllowed) { + return DslToStringHelpers.fn(yamlAllowed, "function.regexReplace", pattern, replacement); } } } diff --git a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/methods/DslComponent.java b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/methods/DslComponent.java index 6838013374..cb0e14c382 100644 --- a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/methods/DslComponent.java +++ b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/methods/DslComponent.java @@ -19,24 +19,13 @@ package org.apache.brooklyn.camp.brooklyn.spi.dsl.methods; import com.fasterxml.jackson.annotation.JsonIgnore; -import com.google.common.base.CaseFormat; -import com.google.common.base.Converter; import com.google.common.base.Objects; -import com.google.common.base.Preconditions; -import com.google.common.base.Predicate; -import com.google.common.base.Predicates; +import com.google.common.base.*; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; import com.google.common.util.concurrent.Callables; import com.thoughtworks.xstream.annotations.XStreamConverter; - -import java.util.*; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutionException; -import java.util.function.Function; -import java.util.stream.Collectors; - import org.apache.brooklyn.api.entity.Entity; import org.apache.brooklyn.api.entity.Group; import org.apache.brooklyn.api.location.Location; @@ -46,13 +35,9 @@ import org.apache.brooklyn.api.objs.BrooklynObject; import org.apache.brooklyn.api.sensor.AttributeSensor; import org.apache.brooklyn.api.sensor.Sensor; import org.apache.brooklyn.camp.brooklyn.BrooklynCampConstants; -import org.apache.brooklyn.core.mgmt.internal.AppGroupTraverser; import org.apache.brooklyn.camp.brooklyn.spi.dsl.BrooklynDslDeferredSupplier; import org.apache.brooklyn.camp.brooklyn.spi.dsl.DslAccessible; import org.apache.brooklyn.camp.brooklyn.spi.dsl.DslFunctionSource; - -import static com.jayway.jsonpath.Filter.filter; -import static org.apache.brooklyn.camp.brooklyn.spi.dsl.DslUtils.resolved; import org.apache.brooklyn.config.ConfigKey; import org.apache.brooklyn.core.config.ConfigKeys; import org.apache.brooklyn.core.entity.Entities; @@ -60,7 +45,7 @@ import org.apache.brooklyn.core.entity.EntityInternal; import org.apache.brooklyn.core.entity.EntityPredicates; import org.apache.brooklyn.core.location.Locations; import org.apache.brooklyn.core.mgmt.BrooklynTaskTags; -import org.apache.brooklyn.core.mgmt.internal.LocalEntityManager; +import org.apache.brooklyn.core.mgmt.internal.AppGroupTraverser; import org.apache.brooklyn.core.sensor.DependentConfiguration; import org.apache.brooklyn.core.sensor.Sensors; import org.apache.brooklyn.util.JavaGroovyEquivalents; @@ -79,6 +64,14 @@ import org.apache.brooklyn.util.text.Strings; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.*; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.function.Function; +import java.util.stream.Collectors; + +import static org.apache.brooklyn.camp.brooklyn.spi.dsl.DslUtils.resolved; + public class DslComponent extends BrooklynDslDeferredSupplier<Entity> implements DslFunctionSource { private static final Logger log = LoggerFactory.getLogger(DslComponent.class); @@ -654,8 +647,8 @@ public class DslComponent extends BrooklynDslDeferredSupplier<Entity> implements return Objects.equal(this.component, that.component); } @Override - public String toString() { - return DslToStringHelpers.component(component, DslToStringHelpers.fn("entityId")); + public String toDslString(boolean yamlAllowed) { + return DslToStringHelpers.chainFunctionOnComponent(yamlAllowed, component, "entityId"); } } @@ -663,20 +656,30 @@ public class DslComponent extends BrooklynDslDeferredSupplier<Entity> implements public BrooklynDslDeferredSupplier<?> attributeWhenReady(final Object sensorNameOrSupplier) { return new AttributeWhenReady(this, sensorNameOrSupplier); } + @DslAccessible + public BrooklynDslDeferredSupplier<?> attributeWhenReady(final Object sensorNameOrSupplier, Map options) { + return new AttributeWhenReady(this, sensorNameOrSupplier, options); + } public static class AttributeWhenReady extends BrooklynDslDeferredSupplier<Object> { private static final long serialVersionUID = 1740899524088902383L; private final DslComponent component; @XStreamConverter(ObjectWithDefaultStringImplConverter.class) private final Object sensorName; + private final Map options; // JSON deserialization only private AttributeWhenReady() { this.component = null; this.sensorName = null; + this.options = null; } public AttributeWhenReady(DslComponent component, Object sensorName) { + this(component, sensorName, null); + } + public AttributeWhenReady(DslComponent component, Object sensorName, Map opts) { this.component = Preconditions.checkNotNull(component); this.sensorName = sensorName; + this.options = opts; } public Object getSensorName() { @@ -726,7 +729,8 @@ public class DslComponent extends BrooklynDslDeferredSupplier<Entity> implements if (!(targetSensor instanceof AttributeSensor<?>)) { targetSensor = Sensors.newSensor(Object.class, sensorNameS); } - return (Task<Object>) DependentConfiguration.attributeWhenReady(targetEntity, (AttributeSensor<?>)targetSensor); + return (Task<Object>) DependentConfiguration.attributeWhenReady(targetEntity, (AttributeSensor<?>)targetSensor, options!=null ? + TypeCoercions.coerce(options, DependentConfiguration.AttributeWhenReadyOptions.class) : DependentConfiguration.AttributeWhenReadyOptions.defaultOptions()); } @Override @@ -742,8 +746,9 @@ public class DslComponent extends BrooklynDslDeferredSupplier<Entity> implements Objects.equal(this.sensorName, that.sensorName); } @Override - public String toString() { - return DslToStringHelpers.component(component, DslToStringHelpers.fn("attributeWhenReady", sensorName)); + public String toDslString(boolean yamlAllowed) { + return DslToStringHelpers.chainFunctionOnComponent(yamlAllowed, component, + "attributeWhenReady", MutableList.of(sensorName).appendIfNotNull(options)); } } @@ -850,8 +855,8 @@ public class DslComponent extends BrooklynDslDeferredSupplier<Entity> implements } @Override - public String toString() { - return DslToStringHelpers.component(component, DslToStringHelpers.fn("config", keyName)); + public String toDslString(boolean yamlAllowed) { + return DslToStringHelpers.chainFunctionOnComponent1(yamlAllowed, component, "config", keyName); } } @@ -958,9 +963,9 @@ public class DslComponent extends BrooklynDslDeferredSupplier<Entity> implements } @Override - public String toString() { - return DslToStringHelpers.component(component, DslToStringHelpers.fn("sensorName", - sensorName instanceof Sensor ? ((Sensor<?>)sensorName).getName() : sensorName)); + public String toDslString(boolean yamlAllowed) { + return DslToStringHelpers.chainFunctionOnComponent1(yamlAllowed, component, "sensorName", + sensorName instanceof Sensor ? ((Sensor<?>)sensorName).getName() : sensorName); } } @@ -1092,8 +1097,8 @@ public class DslComponent extends BrooklynDslDeferredSupplier<Entity> implements Objects.equal(this.index, that.index); } @Override - public String toString() { - return DslToStringHelpers.component(component, DslToStringHelpers.fn("location", index)); + public String toDslString(boolean yamlAllowed) { + return DslToStringHelpers.chainFunctionOnComponent1(yamlAllowed, component, "location", index); } } @@ -1183,6 +1188,11 @@ public class DslComponent extends BrooklynDslDeferredSupplier<Entity> implements } }).build(); } + + @Override + public String toDslString(boolean yamlAllowed) { + return DslToStringHelpers.chainFunctionOnComponent(yamlAllowed, component, "template", MutableList.of(template).appendIfNotNull(substitutions)); + } } public static enum Scope { @@ -1244,28 +1254,27 @@ public class DslComponent extends BrooklynDslDeferredSupplier<Entity> implements } @Override - public String toString() { + public String toDslString(boolean yamlAllowed) { Object component = componentId != null ? componentId : componentIdSupplier; if (scope==Scope.GLOBAL) { - return DslToStringHelpers.fn("entity", component); + return DslToStringHelpers.fn1(yamlAllowed, "entity", component); } if (scope==Scope.THIS) { if (scopeComponent!=null) { return scopeComponent.toString(); } - return DslToStringHelpers.fn("entity", "this", ""); + return DslToStringHelpers.fn(yamlAllowed, "entity", "this", ""); } String remainder; if (component==null || "".equals(component)) { - remainder = DslToStringHelpers.fn(scope.toString()); + return DslToStringHelpers.chainFunctionOnComponent(yamlAllowed, scopeComponent, scope.toString()); } else { - remainder = DslToStringHelpers.fn(scope.toString(), component); + return DslToStringHelpers.chainFunctionOnComponent1(yamlAllowed, scopeComponent, scope.toString(), component); } - - return DslToStringHelpers.component(scopeComponent, remainder); + } } diff --git a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/methods/DslToStringHelpers.java b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/methods/DslToStringHelpers.java index 8bf4460f98..4dd9cfb179 100644 --- a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/methods/DslToStringHelpers.java +++ b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/methods/DslToStringHelpers.java @@ -19,8 +19,13 @@ package org.apache.brooklyn.camp.brooklyn.spi.dsl.methods; import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; import org.apache.brooklyn.api.objs.BrooklynObject; +import org.apache.brooklyn.camp.brooklyn.spi.dsl.BrooklynDslDeferredSupplier; import org.apache.brooklyn.camp.brooklyn.spi.dsl.methods.DslComponent.Scope; import org.apache.brooklyn.util.collections.MutableList; import org.apache.brooklyn.util.text.Strings; @@ -32,6 +37,7 @@ public class DslToStringHelpers { /** concatenates the arguments, each appropriately dsl-tostringed with prefixes removed, * and then prefixes the result */ + // no longer needed now that we use chain... functions public static String concat(String ...x) { StringBuilder r = new StringBuilder(); for (String xi: x) { @@ -44,36 +50,111 @@ public class DslToStringHelpers { } /** convenience for functions, inserting parentheses and commas */ - public static String fn(String functionName, Iterable<?> args) { - StringBuilder out = new StringBuilder(); - out.append(BrooklynDslCommon.PREFIX); - out.append(functionName); - out.append("("); - if (args!=null) { - boolean nonFirst = false; - for (Object s: args) { - if (nonFirst) out.append(", "); - out.append(internal(s)); - nonFirst = true; + public static String fn(boolean yamlAllowed, String functionName, Iterable<?> args) { + return fn(yamlAllowed, true, functionName, args); + } + public static String fn(boolean yamlAllowed, boolean prefixWanted, String functionName, Iterable<?> args) { + try { + StringBuilder out = new StringBuilder(); + if (prefixWanted) out.append(BrooklynDslCommon.PREFIX); + out.append(functionName); + out.append("("); + if (args != null) { + boolean nonFirst = false; + for (Object s : args) { + if (nonFirst) out.append(", "); + out.append(internal(false, s)); + nonFirst = true; + } + } + out.append(")"); + return out.toString(); + } catch (YamlSyntaxRequired e) { + if (!yamlAllowed) throw e; + + StringBuilder out = new StringBuilder(); + out.append("{ "); + if (prefixWanted) out.append(BrooklynDslCommon.PREFIX); + out.append(functionName); + out.append(": [ "); + if (args != null) { + boolean nonFirst = false; + for (Object s : args) { + if (nonFirst) out.append(", "); + out.append(internal(true, s)); + nonFirst = true; + } } + out.append("] }"); + return out.toString(); } - out.append(")"); - return out.toString(); } - public static String fn(String functionName, Object ...args) { - return fn(functionName, Arrays.asList(args)); + public static String fn(boolean yamlAllowed, String functionName) { + return fn(yamlAllowed, functionName, Collections.emptyList()); } - + public static String fn1(boolean yamlAllowed, String functionName, Object arg1) { + return fn(yamlAllowed, functionName, Collections.singletonList(arg1)); + } + public static String fn(boolean yamlAllowed, String functionName, Object arg1, Object arg2, Object ...args) { + return fn(yamlAllowed, functionName, MutableList.of(arg1, arg2).appendAll(Arrays.asList(args))); + } + + public static class YamlSyntaxRequired extends RuntimeException {} + /** convenience for internal arguments, removing any prefix, and applying wrap/escapes and other conversions */ - public static String internal(Object x) { + public static String internal(boolean yamlAllowed, Object x) { + return internal(yamlAllowed, false, x); + } + public static String internal(boolean yamlAllowed, boolean prefixWanted, Object x) { if (x==null) return "null"; if (x instanceof String) return JavaStringEscapes.wrapJavaString((String)x); - if (x instanceof BrooklynObject) return fn("entity", MutableList.of( ((BrooklynObject)x).getId() )); - return Strings.removeFromStart(x.toString(), BrooklynDslCommon.PREFIX); + if (x instanceof BrooklynObject) return fn(yamlAllowed, "entity", MutableList.of( ((BrooklynObject)x).getId() )); + + // these may not produce valid yaml, if used from within a function + if (!yamlAllowed) { + if (x instanceof Iterable || x instanceof Map) throw new YamlSyntaxRequired(); + } + if (x instanceof Iterable) return "[ " + MutableList.copyOf((Iterable)x).stream().map(xi -> internal(yamlAllowed, true, xi)).collect(Collectors.joining(", ")) + " ]"; + if (x instanceof Map) return "{ " + ((Map<?,?>)x).entrySet().stream().map(entry -> + DslToStringHelpers.internal(yamlAllowed, entry.getKey())+": "+DslToStringHelpers.internal(yamlAllowed, true, entry.getValue())).collect(Collectors.joining(", ")) + " }"; + + String v; + if (x instanceof BrooklynDslDeferredSupplier) { + v = ((BrooklynDslDeferredSupplier<?>) x).toDslString(yamlAllowed); + } else { + v = x.toString(); + } + if (!prefixWanted) v = Strings.removeFromStart(v, BrooklynDslCommon.PREFIX); + return v; + } + + public static String chainFunctionOnComponent(boolean yamlAllowed, DslComponent component, String fnNameNoArgs) { + return chainFunctionOnComponent(yamlAllowed, component, fnNameNoArgs, Collections.emptyList()); + } + public static String chainFunctionOnComponent(boolean yamlAllowed, DslComponent component, String fnName, Object arg1, Object arg2, Object ...args) { + return chainFunctionOnComponent(yamlAllowed, component, fnName, MutableList.of(arg1, arg2).appendAll(Arrays.asList(args))); + } + public static String chainFunctionOnComponent1(boolean yamlAllowed, DslComponent component, String fnName, Object arg1) { + return chainFunctionOnComponent(yamlAllowed, component, fnName, Collections.singletonList(arg1)); + } + public static String chainFunctionOnComponent(boolean yamlAllowed, DslComponent component, String fnName, List<Object> args) { + if (component==null || component.getScope()==Scope.THIS) { + // ignore component + return DslToStringHelpers.fn(yamlAllowed, fnName, args); + } + return chainFunction(yamlAllowed, component, fnName, args); } - public static String component(DslComponent component, String remainder) { - return component==null || component.getScope()==Scope.THIS ? remainder : concat(internal(component), ".", remainder); + public static String chainFunction(boolean yamlAllowed, Object object, String fnName, List<?> args) { + try { + return DslToStringHelpers.fn(false, DslToStringHelpers.internal(false, object) + "." + fnName, args); + } catch (YamlSyntaxRequired e) { + if (!yamlAllowed) throw e; + return "{ $brooklyn:chain: [ "+ + DslToStringHelpers.internal(true, true, object) + ", " + + "{ "+fnName+": "+internal(true, args) + " } ] }"; + } } + } diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/ConfigYamlTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/ConfigYamlTest.java index fcbccfdacd..8b819ba880 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/ConfigYamlTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/ConfigYamlTest.java @@ -20,9 +20,13 @@ package org.apache.brooklyn.camp.brooklyn; import com.google.common.annotations.Beta; import java.util.Map; + +import com.google.common.base.Stopwatch; import org.apache.brooklyn.core.config.Sanitizer; +import org.apache.brooklyn.core.entity.EntityAsserts; import org.apache.brooklyn.core.internal.BrooklynProperties; import org.apache.brooklyn.core.server.BrooklynServerConfig; +import org.apache.brooklyn.util.guava.Maybe; import org.apache.brooklyn.util.internal.BrooklynSystemProperties; import org.apache.brooklyn.util.text.StringEscapes.JavaStringEscapes; import org.apache.brooklyn.util.yaml.Yamls; @@ -431,6 +435,57 @@ public class ConfigYamlTest extends AbstractYamlTest { assertEquals(entity.config().getNonBlocking(TestEntity.CONF_SET_PLAIN).get(), ImmutableSet.of("myOther")); } + @Test + public void testAttributeWhenReadyOptions() throws Exception { + String yaml = Joiner.on("\n").join( + "services:", + "- type: org.apache.brooklyn.core.test.entity.TestEntity", + " brooklyn.config:", + " test.confName: { $brooklyn:attributeWhenReady: [ \"test.name\", { timeout: 10ms } ] }"); + + final Entity app = createStartWaitAndLogApplication(yaml); + final TestEntity entity = (TestEntity) Iterables.getOnlyElement(app.getChildren()); + + // Attribute not yet set; non-blocking will return promptly without the value + Stopwatch sw = Stopwatch.createStarted(); + Asserts.assertFailsWith(() -> entity.config().get(TestEntity.CONF_NAME), + Asserts.expectedFailureContainsIgnoreCase("Cannot resolve", "$brooklyn:attributeWhenReady", "test.name", "10ms", "Resolving config test.confName", "Unsatisfied after ")); + Asserts.assertThat(Duration.of(sw.elapsed()), d -> d.isLongerThan(Duration.millis(9))); + + entity.sensors().set(TestEntity.NAME, "x"); + EntityAsserts.assertConfigEquals(entity, TestEntity.CONF_NAME, "x"); + } + + @Test + public void testOtherEntityAttributeWhenReadyOptions() throws Exception { + String v0 = "{ $brooklyn:chain: [ $brooklyn:entity(\"entity2\"), { attributeWhenReady: [ \"test.name\", { timeout: 10ms } ] } ] }"; + String v1 = "{ $brooklyn:chain: [ $brooklyn:entity(\"entity2\"), { attributeWhenReady: [ \"test.name\", { \"timeout\": \"10ms\" } ] } ] }"; + + String yaml = Joiner.on("\n").join( + "services:", + "- type: org.apache.brooklyn.core.test.entity.TestEntity", + " brooklyn.config:", + " test.confName: "+v0, + "- type: org.apache.brooklyn.core.test.entity.TestEntity", + " id: entity2"); + + final Entity app = createStartWaitAndLogApplication(yaml); + final TestEntity entity1 = (TestEntity) Iterables.get(app.getChildren(), 0); + final TestEntity entity2 = (TestEntity) Iterables.get(app.getChildren(), 1); + + // Attribute not yet set; non-blocking will return promptly without the value + Stopwatch sw = Stopwatch.createStarted(); + Asserts.assertFailsWith(() -> entity1.config().get(TestEntity.CONF_NAME), + Asserts.expectedFailureContainsIgnoreCase("Cannot resolve", "$brooklyn:chain", " attributeWhenReady", "test.name", "10ms", "Resolving config test.confName", "Unsatisfied after ")); + Asserts.assertThat(Duration.of(sw.elapsed()), d -> d.isLongerThan(Duration.millis(9))); + + entity2.sensors().set(TestEntity.NAME, "x"); + EntityAsserts.assertConfigEquals(entity1, TestEntity.CONF_NAME, "x"); + + Maybe<Object> rawV = entity1.config().getRaw(TestEntity.CONF_NAME); + Asserts.assertEquals(rawV.get().toString(), v1); + } + @Test public void testConfigGoodNumericCoercions() throws Exception { String yaml = Joiner.on("\n").join( 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 5ded2a7033..b294c0d6b7 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 @@ -181,7 +181,7 @@ public class ServiceStateLogic { return expected.getState(); } - private static void waitBrieflyForServiceUpIfStateIsRunning(String when, Entity entity, Lifecycle state) { + public static void waitBrieflyForServiceUpIfStateIsRunning(String when, Entity entity, Lifecycle state) { if (state==Lifecycle.RUNNING) { Boolean up = entity.getAttribute(Attributes.SERVICE_UP); if (!Boolean.TRUE.equals(up) && Entities.isManagedActive(entity)) {
