| /* |
| * 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.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.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 org.apache.brooklyn.api.entity.Entity; |
| import org.apache.brooklyn.api.mgmt.ExecutionContext; |
| import org.apache.brooklyn.api.mgmt.Task; |
| import org.apache.brooklyn.api.objs.Configurable; |
| import org.apache.brooklyn.api.sensor.Sensor; |
| 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.methods.DslComponent.Scope; |
| import org.apache.brooklyn.camp.brooklyn.spi.dsl.parse.WorkflowTransformGet; |
| import org.apache.brooklyn.config.ConfigKey; |
| import org.apache.brooklyn.core.config.ConfigKeys; |
| 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; |
| import org.apache.brooklyn.core.objs.AbstractConfigurationSupportInternal; |
| import org.apache.brooklyn.core.objs.BrooklynObjectInternal; |
| import org.apache.brooklyn.core.resolve.jackson.BeanWithTypePlanTransformer; |
| import org.apache.brooklyn.core.resolve.jackson.BrooklynJacksonSerializationUtils; |
| import org.apache.brooklyn.core.sensor.DependentConfiguration; |
| import org.apache.brooklyn.core.typereg.RegisteredTypeLoadingContexts; |
| import org.apache.brooklyn.core.typereg.RegisteredTypes; |
| import org.apache.brooklyn.core.workflow.steps.variables.TransformVariableWorkflowStep; |
| import org.apache.brooklyn.util.collections.Jsonya; |
| import org.apache.brooklyn.util.collections.MutableList; |
| import org.apache.brooklyn.util.collections.MutableMap; |
| import org.apache.brooklyn.util.core.ClassLoaderUtils; |
| import org.apache.brooklyn.util.core.config.ConfigBag; |
| import org.apache.brooklyn.util.core.flags.FlagUtils; |
| import org.apache.brooklyn.util.core.flags.TypeCoercions; |
| import org.apache.brooklyn.util.core.json.BrooklynObjectsJsonMapper; |
| import org.apache.brooklyn.util.core.task.DeferredSupplier; |
| import org.apache.brooklyn.util.core.task.ImmediateSupplier; |
| 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; |
| 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; |
| |
| /** |
| * static import functions which can be used in `$brooklyn:xxx` contexts |
| * WARNING: Don't overload methods - the DSL evaluator will pick any one that matches, not the best match. |
| */ |
| public class BrooklynDslCommon { |
| |
| private static final Logger LOG = LoggerFactory.getLogger(BrooklynDslCommon.class); |
| |
| public static final String PREFIX = "$brooklyn:"; |
| |
| public static synchronized void registerSerializationHooks() { registerSerializationHooks(false); } |
| public static synchronized void registerSerializationHooks(boolean forceReinitialization) { |
| if (INITIALIZED && !forceReinitialization) return; |
| BrooklynJacksonSerializationUtils.JsonDeserializerForCommonBrooklynThings.BROOKLYN_PARSE_DSL_FUNCTION = DslUtils::parseBrooklynDsl; |
| BrooklynObjectsJsonMapper.DslToStringSerialization.BROOKLYN_DSL_INTERFACE = BrooklynDslDeferredSupplier.class; |
| registerSpecCoercionAdapter(); |
| registerWorkflowTransforms(); |
| INITIALIZED = true; |
| } |
| private static boolean INITIALIZED = false; |
| static { |
| registerSerializationHooks(); |
| } |
| private static void registerSpecCoercionAdapter() { |
| TypeCoercions.registerAdapter("10-specs", new TryCoercer() { |
| @Override |
| public <T> Maybe<T> tryCoerce(Object input, TypeToken<T> type) { |
| BiFunction<ManagementContext, Object, Object> dslParse = BrooklynJacksonSerializationUtils.JsonDeserializerForCommonBrooklynThings.BROOKLYN_PARSE_DSL_FUNCTION; |
| if (! |
| // TODO there is a JavaEntitySpecResolver but no analogue for adjuncts |
| EntitySpec.class |
| // AbstractBrooklynObjectSpec.class |
| .isAssignableFrom(type.getRawType()) || dslParse==null) { |
| // only applies if a spec is wanted and dsl parse registered |
| return null; |
| } |
| if (!(input instanceof Map)) { |
| // only if given a map |
| return null; |
| } |
| |
| Entity entity = BrooklynTaskTags.getContextEntity(Tasks.current()); |
| ManagementContext mgmt = entity != null ? ((EntityInternal) entity).getManagementContext() : null; |
| if (mgmt==null) return null; |
| |
| Map m = (Map) input; |
| if (!(m.containsKey(BeanWithTypePlanTransformer.TYPE_SIMPLE_KEY) || m.containsKey(BeanWithTypePlanTransformer.TYPE_UNAMBIGUOUS_KEY))) { |
| // and map says a type |
| return null; |
| } |
| |
| try { |
| BrooklynClassLoadingContext loader = RegisteredTypes.getClassLoadingContext(mgmt, entity); |
| Object spec = DslUtils.transformSpecialFlags(mgmt, loader, dslParse.apply(mgmt, MutableMap.of("$brooklyn:entitySpec", input))); |
| if (spec instanceof Supplier) spec = ((Supplier) spec).get(); |
| |
| return Maybe.of((T) spec); |
| |
| } catch (Exception e) { |
| return Maybe.absent(e); |
| } |
| } |
| }); |
| } |
| public static void registerWorkflowTransforms() { |
| TransformVariableWorkflowStep.registerTransformation("get", () -> new WorkflowTransformGet()); |
| } |
| |
| // Access specific entities |
| |
| @DslAccessible |
| public static DslComponent self() { |
| return new DslComponent(Scope.THIS); |
| } |
| @DslAccessible |
| public static DslComponent entity(Object id) { |
| return DslComponent.newInstance(Scope.GLOBAL, id); |
| } |
| @DslAccessible |
| public static DslComponent parent() { |
| return new DslComponent(Scope.PARENT); |
| } |
| @DslAccessible |
| public static DslComponent child(Object id) { |
| return DslComponent.newInstance(Scope.CHILD, id); |
| } |
| @DslAccessible |
| public static DslComponent sibling(Object id) { |
| return DslComponent.newInstance(Scope.SIBLING, id); |
| } |
| @DslAccessible |
| public static DslComponent descendant(Object id) { |
| return DslComponent.newInstance(Scope.DESCENDANT, id); |
| } |
| @DslAccessible |
| public static DslComponent ancestor(Object id) { |
| return DslComponent.newInstance(Scope.ANCESTOR, id); |
| } |
| @DslAccessible |
| public static DslComponent root() { |
| return new DslComponent(Scope.ROOT); |
| } |
| |
| @DslAccessible |
| public static DslComponent scopeRoot() { |
| return new DslComponent(Scope.SCOPE_ROOT); |
| } |
| // above is within the current definition |
| // whereas the below _prefers_ in the current definition |
| |
| @DslAccessible |
| public static DslComponent component(Object id) { |
| return component("global", id); |
| } |
| @DslAccessible |
| public static DslComponent component(String scope, Object id) { |
| if (!DslComponent.Scope.isValid(scope)) { |
| throw new IllegalArgumentException(scope + " is not a valid scope"); |
| } |
| return DslComponent.newInstance(DslComponent.Scope.fromString(scope), id); |
| } |
| |
| @DslAccessible |
| public static DslComponent application(Object id) { |
| return DslComponent.newInstance(Scope.APPLICATIONS, id); |
| } |
| |
| @DslAccessible |
| public static DslComponent member(Object id) { |
| return DslComponent.newInstance(Scope.MEMBERS, id); |
| } |
| |
| // Access things on entities |
| |
| @DslAccessible |
| public static BrooklynDslDeferredSupplier<?> config(Object keyName) { |
| return new DslComponent(Scope.THIS, "").config(keyName); |
| } |
| |
| @DslAccessible |
| public static BrooklynDslDeferredSupplier<?> config(BrooklynObjectInternal obj, Object keyName) { |
| return new DslBrooklynObjectConfigSupplier(obj, keyName); |
| } |
| |
| public static class DslBrooklynObjectConfigSupplier extends BrooklynDslDeferredSupplier<Object> { |
| private static final long serialVersionUID = -2378555915585603381L; |
| |
| // Keep in mind this object gets serialized so is the following reference |
| private BrooklynObjectInternal obj; |
| @XStreamConverter(ObjectWithDefaultStringImplConverter.class) |
| private Object keyName; |
| |
| public DslBrooklynObjectConfigSupplier(BrooklynObjectInternal obj, Object keyName) { |
| checkNotNull(obj, "obj"); |
| checkNotNull(keyName, "keyName"); |
| |
| this.obj = obj; |
| this.keyName = keyName; |
| } |
| |
| public BrooklynObjectInternal getObj() { |
| return obj; |
| } |
| |
| public Object getKeyName() { |
| return keyName; |
| } |
| |
| protected String resolveKeyName(boolean immediately) { |
| if (keyName instanceof String) { |
| return (String)keyName; |
| } |
| |
| return Tasks.resolving(keyName) |
| .as(String.class) |
| .context(DslComponent.findExecutionContext(this)) |
| .immediately(immediately) |
| .description("Resolving key name from " + keyName) |
| .get(); |
| } |
| |
| @Override @JsonIgnore |
| public Maybe<Object> getImmediately() { |
| if (obj instanceof Entity) { |
| // Shouldn't worry too much about it since DSL can fetch objects from same app only. |
| // Just in case check whether it's same app for entities. |
| checkState(entity().getApplicationId().equals(((Entity)obj).getApplicationId())); |
| } |
| String keyNameS = resolveKeyName(true); |
| ConfigKey<Object> key = ConfigKeys.newConfigKey(Object.class, keyNameS); |
| Maybe<? extends Object> result = ((AbstractConfigurationSupportInternal)obj.config()).getNonBlocking(key, true); |
| return Maybe.<Object>cast(result); |
| } |
| |
| @Override |
| public Task<Object> newTask() { |
| return Tasks.builder() |
| .displayName("retrieving config for "+keyName+" on "+obj) |
| .tag(BrooklynTaskTags.TRANSIENT_TASK_TAG) |
| .dynamic(false) |
| .body(new Callable<Object>() { |
| @Override |
| public Object call() throws Exception { |
| String keyNameS = resolveKeyName(true); |
| ConfigKey<Object> key = ConfigKeys.newConfigKey(Object.class, keyNameS); |
| return obj.getConfig(key); |
| }}) |
| .build(); |
| } |
| |
| @Override |
| public int hashCode() { |
| return Objects.hashCode(obj, keyName); |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| if (this == obj) return true; |
| if (obj == null || getClass() != obj.getClass()) return false; |
| DslBrooklynObjectConfigSupplier that = DslBrooklynObjectConfigSupplier.class.cast(obj); |
| return Objects.equal(this.obj, that.obj) && |
| Objects.equal(this.keyName, that.keyName); |
| } |
| |
| @Override |
| public String toString() { |
| return DslToStringHelpers.concat(DslToStringHelpers.internal(obj), ".", DslToStringHelpers.fn("config", keyName)); |
| } |
| } |
| |
| @DslAccessible |
| public static BrooklynDslDeferredSupplier<?> attributeWhenReady(Object sensorName) { |
| return new DslComponent(Scope.THIS, "").attributeWhenReady(sensorName); |
| } |
| |
| @DslAccessible |
| public static BrooklynDslDeferredSupplier<?> entityId() { |
| return new DslComponent(Scope.THIS, "").entityId(); |
| } |
| |
| /** Returns a {@link Sensor}, looking up the sensor on the context if available and using that, |
| * or else defining an untyped (Object) sensor */ |
| @DslAccessible |
| public static BrooklynDslDeferredSupplier<Sensor<?>> sensor(Object sensorName) { |
| return new DslComponent(Scope.THIS, "").sensor(sensorName); |
| } |
| |
| /** Returns a {@link Sensor} declared on the type (e.g. entity class) declared in the first argument. */ |
| @SuppressWarnings({ "unchecked", "rawtypes" }) |
| @DslAccessible |
| public static Sensor<?> sensor(String clazzName, String sensorName) { |
| try { |
| // TODO Should use catalog's classloader, rather than ClassLoaderUtils; how to get that? Should we return a future?! |
| // Should have the catalog's loader at this point in a thread local |
| String mappedClazzName = DeserializingClassRenamesProvider.INSTANCE.findMappedName(clazzName); |
| Class<?> clazz = new ClassLoaderUtils(BrooklynDslCommon.class).loadClass(mappedClazzName); |
| |
| Sensor<?> sensor; |
| if (Entity.class.isAssignableFrom(clazz)) { |
| sensor = new EntityDynamicType((Class<? extends Entity>) clazz).getSensor(sensorName); |
| } else { |
| // Some non-entity classes (e.g. ServiceRestarter policy) declare sensors that other |
| // entities/policies/enrichers may wish to reference. |
| Map<String,Sensor<?>> sensors = EntityDynamicType.findSensors((Class)clazz, null); |
| sensor = sensors.get(sensorName); |
| } |
| if (sensor == null) { |
| // TODO could extend API to return a sensor of the given type; useful but makes API ambiguous in theory (unlikely in practise, but still...) |
| throw new IllegalArgumentException("Sensor " + sensorName + " not found on class " + clazzName); |
| } |
| return sensor; |
| } catch (ClassNotFoundException e) { |
| throw Exceptions.propagate(e); |
| } |
| } |
| |
| @DslAccessible |
| public static BrooklynDslDeferredSupplier<?> location() { |
| return new DslComponent(Scope.THIS, "").location(); |
| } |
| |
| @DslAccessible |
| public static BrooklynDslDeferredSupplier<?> location(Object index) { |
| return new DslComponent(Scope.THIS, "").location(index); |
| } |
| |
| // Build complex things |
| |
| // TODO allow entitySpec to take string, parsed as YAML, and if just a string that's taken as the type |
| |
| @DslAccessible |
| public static EntitySpecConfiguration entitySpec(Map<String, Object> arguments) { |
| return new EntitySpecConfiguration(arguments); |
| } |
| |
| /** |
| * Return an instance of the specified class with its fields set according |
| * to the {@link Map}. Or a {@link BrooklynDslDeferredSupplier} if either the arguments are |
| * not yet fully resolved, or the class cannot be loaded yet (e.g. needs the catalog's OSGi |
| * bundles). |
| */ |
| @SuppressWarnings("unchecked") |
| @DslAccessible |
| public static Object object(Map<String, Object> arguments) { |
| ConfigBag config = ConfigBag.newInstance(arguments); |
| String typeName = BrooklynYamlTypeInstantiator.InstantiatorFromKey.extractTypeName("object", config).orNull(); |
| List<Object> constructorArgs = (List<Object>) config.getStringKeyMaybe("constructor.args").or(ImmutableList.of()); |
| String factoryMethodName = (String) config.getStringKeyMaybe("factoryMethod.name").orNull(); |
| List<Object> factoryMethodArgs = (List<Object>) config.getStringKeyMaybe("factoryMethod.args").or(ImmutableList.of()); |
| Map<String,Object> objectFields = (Map<String, Object>) config.getStringKeyMaybe("object.fields").or(MutableMap.of()); |
| Map<String,Object> brooklynConfig = (Map<String, Object>) config.getStringKeyMaybe(BrooklynCampReservedKeys.BROOKLYN_CONFIG).or(MutableMap.of()); |
| boolean deferred = TypeCoercions.coerce(config.getStringKeyMaybe("deferred").or(Boolean.FALSE), Boolean.class); |
| |
| String mappedTypeName = DeserializingClassRenamesProvider.INSTANCE.findMappedName(typeName); |
| Class<?> type; |
| try { |
| // local classes get loaded here |
| type = new ClassLoaderUtils(BrooklynDslCommon.class).loadClass(mappedTypeName); |
| } catch (ClassNotFoundException e) { |
| // this is normal, we just do a deferred load, likely a registered type |
| if (LOG.isTraceEnabled()) { |
| LOG.trace("No local class " + typeName + " for DSL 'object'; assuming it is a registered type; will defer its loading"); |
| } |
| return new DslObject(mappedTypeName, constructorArgs, objectFields, brooklynConfig); |
| } |
| |
| if (!deferred && resolved(constructorArgs) && resolved(factoryMethodArgs) && resolved(objectFields.values()) && resolved(brooklynConfig.values())) { |
| if (factoryMethodName == null) { |
| return DslObject.create(type, constructorArgs, objectFields, brooklynConfig); |
| } else { |
| return DslObject.create(type, factoryMethodName, factoryMethodArgs, objectFields, brooklynConfig); |
| } |
| } else { |
| if (factoryMethodName == null) { |
| return new DslObject(type, constructorArgs, objectFields, brooklynConfig); |
| } else { |
| return new DslObject(type, factoryMethodName, factoryMethodArgs, objectFields, brooklynConfig); |
| } |
| } |
| } |
| |
| @DslAccessible |
| public static Object object(String argumentsMapAsYamlStringOrShorthand) { |
| Object arg = Yamls.parseAll(argumentsMapAsYamlStringOrShorthand).iterator().next(); |
| if (arg instanceof Map) return object( (Map<String,Object>)arg ); |
| if (arg instanceof Collection) { |
| List argL = MutableList.copyOf((Collection)arg); |
| if (argL.size()>=1) { |
| return object(MutableMap.of("type", argL.remove(0), "constructor.args", argL)); |
| } |
| } |
| if (arg instanceof String) { |
| return object(MutableMap.of("type", arg, "constructor.args", Collections.emptyList())); |
| } |
| |
| throw new IllegalArgumentException("Argument to object should be a map, or shorthand type name as string, or type name and constructor arguments as list"); |
| } |
| |
| // String manipulation |
| |
| /** Return a supplier for the given expression as a literal string without any further parsing. */ |
| @DslAccessible |
| public static Object literal(Object expression) { |
| // since 2020-10 always defer, in case something else might try to parse it |
| return new DslLiteral(expression); |
| } |
| |
| public final static class DslLiteral extends BrooklynDslDeferredSupplier<Object> { |
| final String literalString; |
| final String literalObjectJson; |
| |
| private DslLiteral() { this(null); } |
| |
| public DslLiteral(Object input) { |
| this.literalString = input instanceof String ? (String)input : null; |
| this.literalObjectJson = (input==null || input instanceof String) ? null : Jsonya.render(input); |
| } |
| |
| public String getLiteralObjectJson() { |
| return literalObjectJson; |
| } |
| |
| public String getLiteralString() { |
| return literalString; |
| } |
| |
| @Override |
| public Task<Object> newTask() { |
| return Tasks.builder().displayName("DSL literal value") |
| .tag(BrooklynTaskTags.TRANSIENT_TASK_TAG) |
| .dynamic(false) |
| .body(() -> getImmediately().get()) |
| .build(); |
| } |
| |
| @Override @JsonIgnore |
| public Maybe<Object> getImmediately() { |
| return Maybe.ofAllowingNull( literalObjectJson!=null ? Yamls.parseAll(literalObjectJson).iterator().next() : literalString ); |
| } |
| |
| @Override |
| public String toString() { |
| return "$brooklyn:literal(" + |
| (literalString!=null ? JavaStringEscapes.wrapJavaString(literalString) : literalObjectJson) |
| +")"; |
| } |
| } |
| |
| /** |
| * Returns the arg with characters escaped so it is a valid part of a URL, or a |
| * {@link BrooklynDslDeferredSupplier} that returns this if the arguments are not yet fully |
| * resolved. |
| * |
| * See {@link Urls#encode(String)} for further details (it currently uses the encoding rules for |
| * "x-www-form-urlencoded") |
| * |
| * Do not call with a whole URL unless you want everything escaped (e.g. "http://myhost" will be |
| * encoded as "http%3A%2F%2Fmyhost"). |
| */ |
| @DslAccessible |
| public static Object urlEncode(Object arg) { |
| if (resolved(arg)) { |
| // if all args are resolved, apply the transform now |
| return (arg == null) ? null : Urls.encode(arg.toString()); |
| } else { |
| return new DslUrlEncode(arg); |
| } |
| } |
| |
| /** |
| * Returns a formatted string or a {@link BrooklynDslDeferredSupplier} if the arguments |
| * are not yet fully resolved. |
| */ |
| @DslAccessible |
| public static Object formatString(final Object pattern, final Object...args) { |
| if (resolved(Lists.asList(pattern, args))) { |
| // if all args are resolved, apply the format string now |
| return String.format(String.valueOf(pattern), args); |
| } else { |
| return new DslFormatString(pattern, args); |
| } |
| } |
| |
| @DslAccessible |
| public static Object regexReplacement(final Object source, final Object pattern, final Object replacement) { |
| if (resolved(Arrays.asList(source, pattern, replacement))) { |
| return (new Functions.RegexReplacer(String.valueOf(pattern), String.valueOf(replacement))).apply(String.valueOf(source)); |
| } else { |
| return new DslRegexReplacement(source, pattern, replacement); |
| } |
| } |
| |
| /** |
| * Deferred execution of escaping a URL. |
| * |
| * @see DependentConfiguration#urlEncode(Object) |
| */ |
| protected static class DslUrlEncode extends BrooklynDslDeferredSupplier<String> { |
| |
| private static final long serialVersionUID = -4849297712650560863L; |
| |
| private final Object arg; |
| |
| public DslUrlEncode(Object arg) { |
| this.arg = arg; |
| } |
| |
| @Override @JsonIgnore |
| public final Maybe<String> getImmediately() { |
| return DependentConfiguration.urlEncodeImmediately(arg); |
| } |
| |
| @Override |
| public Task<String> newTask() { |
| return DependentConfiguration.urlEncode(arg); |
| } |
| |
| @Override |
| public int hashCode() { |
| return Objects.hashCode(arg); |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| if (this == obj) return true; |
| if (obj == null || getClass() != obj.getClass()) return false; |
| DslUrlEncode that = DslUrlEncode.class.cast(obj); |
| return Objects.equal(this.arg, that.arg); |
| } |
| |
| @Override |
| public String toString() { |
| return "$brooklyn:urlEncode("+arg+")"; |
| } |
| } |
| |
| /** |
| * Deferred execution of String formatting. |
| * |
| * @see DependentConfiguration#formatString(Object, Object...) |
| */ |
| public static class DslFormatString extends BrooklynDslDeferredSupplier<String> { |
| |
| private static final long serialVersionUID = -4849297712650560863L; |
| |
| private final Object pattern; |
| private final Object[] args; |
| |
| public DslFormatString(Object pattern, Object ...args) { |
| this.pattern = pattern; |
| this.args = args; |
| } |
| |
| public Object getPattern() { |
| return pattern; |
| } |
| |
| public Object[] getArgs() { |
| return args; |
| } |
| |
| @Override @JsonIgnore |
| public final Maybe<String> getImmediately() { |
| return DependentConfiguration.formatStringImmediately(pattern, args); |
| } |
| |
| @Override |
| public Task<String> newTask() { |
| return DependentConfiguration.formatString(pattern, args); |
| } |
| |
| @Override |
| public int hashCode() { |
| return Objects.hashCode(pattern, args); |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| if (this == obj) return true; |
| if (obj == null || getClass() != obj.getClass()) return false; |
| DslFormatString that = DslFormatString.class.cast(obj); |
| return Objects.equal(this.pattern, that.pattern) && |
| Arrays.deepEquals(this.args, that.args); |
| } |
| |
| @Override |
| public String toString() { |
| return DslToStringHelpers.fn("formatString", MutableList.<Object>builder().add(pattern).addAll(args).build()); |
| } |
| } |
| |
| public static class DslRegexReplacement extends BrooklynDslDeferredSupplier<String> { |
| |
| private static final long serialVersionUID = 737189899361183341L; |
| |
| private Object source; |
| private Object pattern; |
| private Object replacement; |
| |
| public DslRegexReplacement(Object source, Object pattern, Object replacement) { |
| this.pattern = pattern; |
| this.replacement = replacement; |
| this.source = source; |
| } |
| |
| public Object getSource() { |
| return source; |
| } |
| |
| public Object getPattern() { |
| return pattern; |
| } |
| |
| public Object getReplacement() { |
| return replacement; |
| } |
| |
| @Override @JsonIgnore |
| public Maybe<String> getImmediately() { |
| return DependentConfiguration.regexReplacementImmediately(source, pattern, replacement); |
| } |
| |
| @Override |
| public Task<String> newTask() { |
| return DependentConfiguration.regexReplacement(source, pattern, replacement); |
| } |
| |
| @Override |
| public int hashCode() { |
| return Objects.hashCode(source, pattern, replacement); |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| if (this == obj) return true; |
| if (obj == null || getClass() != obj.getClass()) return false; |
| DslRegexReplacement that = DslRegexReplacement.class.cast(obj); |
| return Objects.equal(this.pattern, that.pattern) && |
| Objects.equal(this.replacement, that.replacement) && |
| Objects.equal(this.source, that.source); |
| } |
| |
| @Override |
| public String toString() { |
| return DslToStringHelpers.fn("regexReplace", source, pattern, replacement); |
| } |
| } |
| |
| /** @deprecated since 0.7.0; kept for persisted state backwards compatibility */ |
| @SuppressWarnings({ "serial", "unused" }) |
| @Deprecated |
| private static class FormatString extends DslFormatString { |
| public FormatString(Object pattern, Object[] args) { |
| super(pattern, args); |
| } |
| } |
| |
| /** Deferred execution of Object creation. */ |
| public static class DslObject extends BrooklynDslDeferredSupplier<Object> { |
| |
| private static final long serialVersionUID = 8878388748085419L; |
| |
| private final String typeName; |
| private final String factoryMethodName; |
| private final Class<?> type; |
| private final List<Object> constructorArgs, factoryMethodArgs; |
| private final Map<String, Object> fields, config; |
| |
| public DslObject( |
| String typeName, |
| List<Object> constructorArgs, |
| Map<String, Object> fields, |
| Map<String, Object> config) { |
| this.typeName = checkNotNull(typeName, "typeName"); |
| this.type = null; |
| this.constructorArgs = checkNotNull(constructorArgs, "constructorArgs"); |
| this.factoryMethodName = null; |
| this.factoryMethodArgs = ImmutableList.of(); |
| this.fields = MutableMap.copyOf(fields); |
| this.config = MutableMap.copyOf(config); |
| } |
| |
| public DslObject( |
| Class<?> type, |
| List<Object> constructorArgs, |
| Map<String, Object> fields, |
| Map<String, Object> config) { |
| this.typeName = null; |
| this.type = checkNotNull(type, "type"); |
| this.constructorArgs = checkNotNull(constructorArgs, "constructorArgs"); |
| this.factoryMethodName = null; |
| this.factoryMethodArgs = ImmutableList.of(); |
| this.fields = MutableMap.copyOf(fields); |
| this.config = MutableMap.copyOf(config); |
| } |
| |
| public DslObject( |
| String typeName, |
| String factoryMethodName, |
| List<Object> factoryMethodArgs, |
| Map<String, Object> fields, |
| Map<String, Object> config) { |
| this.typeName = checkNotNull(typeName, "typeName"); |
| this.type = null; |
| this.constructorArgs = ImmutableList.of(); |
| this.factoryMethodName = factoryMethodName; |
| this.factoryMethodArgs = checkNotNull(factoryMethodArgs, "factoryMethodArgs"); |
| this.fields = MutableMap.copyOf(fields); |
| this.config = MutableMap.copyOf(config); |
| } |
| |
| public DslObject( |
| Class<?> type, |
| String factoryMethodName, |
| List<Object> factoryMethodArgs, |
| Map<String, Object> fields, |
| Map<String, Object> config) { |
| this.typeName = null; |
| this.type = checkNotNull(type, "type"); |
| this.constructorArgs = ImmutableList.of(); |
| this.factoryMethodName = factoryMethodName; |
| this.factoryMethodArgs = checkNotNull(factoryMethodArgs, "factoryMethodArgs"); |
| this.fields = MutableMap.copyOf(fields); |
| this.config = MutableMap.copyOf(config); |
| } |
| |
| |
| @Override @JsonIgnore |
| public Maybe<Object> getImmediately() { |
| // TODO reconcile with "bean-with-type" usage and constructors; |
| // for now, if it's a registered type we use that to get the java instance but we ignore fields on it |
| |
| final Class<?> clazz = getOrLoadType(); |
| final ExecutionContext executionContext = entity().getExecutionContext(); |
| |
| final Function<Object, Object> resolver = new Function<Object, Object>() { |
| @Override public Object apply(Object value) { |
| Maybe<Object> result = Tasks.resolving(value, Object.class).context(executionContext).deep().immediately(true).getMaybe(); |
| if (result.isAbsent()) { |
| throw new ImmediateValueNotAvailableException(); |
| } else { |
| return result.get(); |
| } |
| } |
| }; |
| |
| try { |
| Map<String, Object> resolvedFields = MutableMap.copyOf(Maps.transformValues(fields, resolver)); |
| Map<String, Object> resolvedConfig = MutableMap.copyOf(Maps.transformValues(config, resolver)); |
| List<Object> resolvedConstructorArgs = MutableList.copyOf(Lists.transform(constructorArgs, resolver)); |
| List<Object> resolvedFactoryMethodArgs = MutableList.copyOf(Lists.transform(factoryMethodArgs, resolver)); |
| |
| Object result; |
| if (factoryMethodName == null) { |
| result = create(clazz, resolvedConstructorArgs, resolvedFields, resolvedConfig); |
| } else { |
| result = create(clazz, factoryMethodName, resolvedFactoryMethodArgs, resolvedFields, resolvedConfig); |
| } |
| return Maybe.of(result); |
| } catch (ImmediateValueNotAvailableException e) { |
| return Maybe.absent(e); |
| } |
| } |
| |
| @Override |
| public Task<Object> newTask() { |
| final Class<?> clazz = getOrLoadType(); |
| final ExecutionContext executionContext = entity().getExecutionContext(); |
| |
| final Function<Object, Object> resolver = new Function<Object, Object>() { |
| @Override public Object apply(Object value) { |
| try { |
| return Tasks.resolveDeepValueWithoutCoercion(value, executionContext); |
| } catch (ExecutionException | InterruptedException e) { |
| throw Exceptions.propagate(e); |
| } |
| } |
| }; |
| |
| return Tasks.builder().displayName("building instance of '"+clazz+"'") |
| .tag(BrooklynTaskTags.TRANSIENT_TASK_TAG) |
| .dynamic(false) |
| .body(new Callable<Object>() { |
| @Override |
| public Object call() throws Exception { |
| // TODO de-dupe with getImmediately |
| |
| Map<String, Object> resolvedFields = MutableMap.copyOf(Maps.transformValues(fields, resolver)); |
| Map<String, Object> resolvedConfig = MutableMap.copyOf(Maps.transformValues(config, resolver)); |
| List<Object> resolvedConstructorArgs = MutableList.copyOf(Lists.transform(constructorArgs, resolver)); |
| List<Object> resolvedFactoryMethodArgs = MutableList.copyOf(Lists.transform(factoryMethodArgs, resolver)); |
| |
| if (factoryMethodName == null) { |
| return create(clazz, resolvedConstructorArgs, resolvedFields, resolvedConfig); |
| } else { |
| return create(clazz, factoryMethodName, resolvedFactoryMethodArgs, resolvedFields, resolvedConfig); |
| } |
| }}) |
| .build(); |
| } |
| |
| @JsonIgnore |
| protected Class<?> getOrLoadType() { |
| Class<?> type = this.type; |
| |
| if (type == null) { |
| EntityInternal entity = entity(); |
| try { |
| if (entity==null) { |
| throw new IllegalStateException("Cannot invoke without a Task running the context of an entity"); |
| } |
| RegisteredType rt = managementContext().getTypeRegistry().get(typeName, RegisteredTypeLoadingContexts.loader(RegisteredTypes.getClassLoadingContext(entity))); |
| if (rt!=null) { |
| Object inst = managementContext().getTypeRegistry().create(rt, null, null); |
| if (inst!=null) { |
| // we ignore the actually instance for now; see comments at start of this class |
| return inst.getClass(); |
| } |
| } |
| |
| // fall back to trying to load as a class |
| type = new ClassLoaderUtils(BrooklynDslCommon.class, entity).loadClass(typeName); |
| } catch (ClassNotFoundException e) { |
| throw Exceptions.propagate(e); |
| } |
| } |
| return type; |
| } |
| |
| public static <T> T create(Class<T> type, List<?> constructorArgs, Map<String,?> fields, Map<String,?> config) { |
| try { |
| T bean = Reflections.invokeConstructorFromArgs(type, constructorArgs.toArray()).get(); |
| BeanUtils.populate(bean, fields); |
| |
| if (config.size() > 0) { |
| if (bean instanceof Configurable) { |
| ConfigBag configBag = ConfigBag.newInstance(config); |
| FlagUtils.setFieldsFromFlags(bean, configBag); |
| FlagUtils.setAllConfigKeys((Configurable) bean, configBag, true); |
| } else { |
| LOG.warn("While building object, type "+type+" is not 'Configurable'; cannot apply supplied config (continuing)"); |
| } |
| } |
| return bean; |
| } catch (Exception e) { |
| throw Exceptions.propagate(e); |
| } |
| } |
| |
| public static Object create(Class<?> type, String factoryMethodName, List<?> factoryMethodArgs, Map<String,?> fields, Map<String,?> config) { |
| try { |
| Optional<TypeCoercer> coercer = Optional.of(TypeCoercions.asTypeCoercer()); |
| Object bean = Reflections.invokeMethodFromArgs(type, factoryMethodName, factoryMethodArgs, false, coercer).get(); |
| BeanUtils.populate(bean, fields); |
| |
| if (config.size() > 0) { |
| if (bean instanceof Configurable) { |
| ConfigBag configBag = ConfigBag.newInstance(config); |
| FlagUtils.setFieldsFromFlags(bean, configBag); |
| FlagUtils.setAllConfigKeys((Configurable) bean, configBag, true); |
| } else { |
| LOG.warn("While building object via factory method '"+factoryMethodName+"', type " |
| + (bean == null ? "<null>" : bean.getClass())+" is not 'Configurable'; cannot apply " |
| + "supplied config (continuing)"); |
| } |
| } |
| return bean; |
| } catch (Exception e) { |
| throw Exceptions.propagate(e); |
| } |
| } |
| |
| @Override |
| public int hashCode() { |
| return Objects.hashCode(type, fields, config); |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| if (this == obj) return true; |
| if (obj == null || getClass() != obj.getClass()) return false; |
| DslObject that = DslObject.class.cast(obj); |
| return Objects.equal(this.type, that.type) && |
| Objects.equal(this.fields, that.fields) && |
| Objects.equal(this.config, that.config); |
| } |
| |
| @Override |
| public String toString() { |
| // 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; |
| if (!constructorArgs.isEmpty()) { |
| arg = MutableList.of(arg).appendAll(constructorArgs).toString(); |
| } |
| return DslToStringHelpers.fn("object", arg); |
| } |
| } |
| |
| /** |
| * Defers to management context's {@link ExternalConfigSupplierRegistry} to resolve values at runtime. |
| * The name of the appropriate {@link ExternalConfigSupplier} is captured, along with the key of |
| * the desired config value. |
| */ |
| @DslAccessible |
| public static DslExternal external(final String providerName, final String key) { |
| return new DslExternal(providerName, key); |
| } |
| public final static class DslExternal extends BrooklynDslDeferredSupplier<Object> { |
| private static final long serialVersionUID = -3860334240490397057L; |
| private final String providerName; |
| private final String key; |
| |
| public DslExternal(String providerName, String key) { |
| this.providerName = providerName; |
| this.key = key; |
| } |
| |
| public String getProviderName() { |
| return providerName; |
| } |
| |
| public String getKey() { |
| return key; |
| } |
| |
| @Override @JsonIgnore |
| public final Maybe<Object> getImmediately() { |
| // Note this call to getConfig() is different from entity.getConfig. |
| // We expect it to not block waiting for other entities. |
| ManagementContextInternal managementContext = DslExternal.managementContext(); |
| return Maybe.<Object>of(managementContext.getExternalConfigProviderRegistry().getConfig(providerName, key)); |
| } |
| |
| @Override |
| public Task<Object> newTask() { |
| return Tasks.<Object>builder() |
| .displayName("resolving external configuration: '" + key + "' from provider '" + providerName + "'") |
| .dynamic(false) |
| .body(new Callable<Object>() { |
| @Override |
| public Object call() throws Exception { |
| return getImmediately().get(); |
| } |
| }) |
| .build(); |
| } |
| |
| @Override |
| public int hashCode() { |
| return Objects.hashCode(providerName, key); |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| if (this == obj) return true; |
| if (obj == null || getClass() != obj.getClass()) return false; |
| DslExternal that = DslExternal.class.cast(obj); |
| return Objects.equal(this.providerName, that.providerName) && |
| Objects.equal(this.key, that.key); |
| } |
| |
| @Override |
| public String toString() { |
| return DslToStringHelpers.fn("external", providerName, key); |
| } |
| } |
| |
| public static Object template(Object template) { |
| return new DslComponent(Scope.THIS, "").template(template); |
| } |
| |
| public static Object template(Object template, Map<? ,?> substitutions) { |
| return new DslComponent(Scope.THIS, "").template(template, substitutions); |
| } |
| |
| public static class Functions { |
| @DslAccessible |
| public static Object regexReplacement(final Object pattern, final Object replacement) { |
| if (resolved(pattern, replacement)) { |
| return new org.apache.brooklyn.util.text.StringFunctions.RegexReplacer(String.valueOf(pattern), String.valueOf(replacement)); |
| } else { |
| return new DslRegexReplacer(pattern, replacement); |
| } |
| } |
| |
| /** @deprecated since 0.11.0; use {@link org.apache.brooklyn.util.text.StringFunctions.RegexReplacer} instead */ |
| @Deprecated |
| public static class RegexReplacer extends org.apache.brooklyn.util.text.StringFunctions.RegexReplacer { |
| public RegexReplacer(String pattern, String replacement) { |
| super(pattern, replacement); |
| } |
| } |
| |
| protected static class DslRegexReplacer extends BrooklynDslDeferredSupplier<Function<String, String>> { |
| |
| private static final long serialVersionUID = -2900037495440842269L; |
| |
| private Object pattern; |
| private Object replacement; |
| |
| public DslRegexReplacer(Object pattern, Object replacement) { |
| this.pattern = pattern; |
| this.replacement = replacement; |
| } |
| |
| @Override @JsonIgnore |
| public Maybe<Function<String, String>> getImmediately() { |
| return DependentConfiguration.regexReplacementImmediately(pattern, replacement); |
| } |
| |
| @Override |
| public Task<Function<String, String>> newTask() { |
| return DependentConfiguration.regexReplacement(pattern, replacement); |
| } |
| |
| @Override |
| public int hashCode() { |
| return Objects.hashCode(pattern, replacement); |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| if (this == obj) return true; |
| if (obj == null || getClass() != obj.getClass()) return false; |
| DslRegexReplacer that = DslRegexReplacer.class.cast(obj); |
| return Objects.equal(this.pattern, that.pattern) && |
| Objects.equal(this.replacement, that.replacement); |
| } |
| |
| @Override |
| public String toString() { |
| return DslToStringHelpers.fn("function.regexReplace", pattern, replacement); |
| } |
| } |
| } |
| |
| // The results of the following methods are not supposed to get serialized. They are |
| // only intermediate values for the DSL evaluator to apply function calls on. There |
| // will always be a next method that gets executed on the return value. |
| public static class DslFacades { |
| private static class EntitySupplier implements DeferredSupplier<Entity>, ImmediateSupplier<Entity> { |
| private String entityId; |
| |
| public EntitySupplier(String entityId) { |
| this.entityId = entityId; |
| } |
| |
| @Override @JsonIgnore |
| public Maybe<Entity> getImmediately() { |
| EntityInternal entity = entity(); |
| if (entity == null) { |
| return Maybe.absent("No entity available"); |
| } |
| Entity targetEntity = entity.getManagementContext().getEntityManager().getEntity(entityId); |
| return Maybe.of(targetEntity); |
| } |
| |
| @Override |
| public Entity get() { |
| return getImmediately().orNull(); |
| } |
| |
| private EntityInternal entity() { |
| return (EntityInternal) BrooklynTaskTags.getTargetOrContextEntity(Tasks.current()); |
| } |
| } |
| |
| @DslAccessible |
| public static Object wrap(Entity entity) { |
| return DslComponent.newInstance(Scope.GLOBAL, new EntitySupplier(entity.getId())); |
| } |
| } |
| |
| } |