/*
* 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 java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import javax.annotation.Nullable;
import org.apache.brooklyn.api.entity.Entity;
import org.apache.brooklyn.api.mgmt.Task;
import org.apache.brooklyn.api.mgmt.TaskAdaptable;
import org.apache.brooklyn.api.mgmt.TaskFactory;
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.DslUtils;
import org.apache.brooklyn.camp.brooklyn.spi.dsl.methods.DslComponent.Scope;
import org.apache.brooklyn.core.config.external.ExternalConfigSupplier;
import org.apache.brooklyn.core.entity.EntityDynamicType;
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.sensor.DependentConfiguration;
import org.apache.brooklyn.util.collections.MutableMap;
import org.apache.brooklyn.util.core.config.ConfigBag;
import org.apache.brooklyn.util.core.flags.ClassCoercionException;
import org.apache.brooklyn.util.core.flags.FlagUtils;
import org.apache.brooklyn.util.core.flags.TypeCoercions;
import org.apache.brooklyn.util.core.task.DeferredSupplier;
import org.apache.brooklyn.util.core.task.Tasks;
import org.apache.brooklyn.util.exceptions.Exceptions;
import org.apache.brooklyn.util.javalang.Reflections;
import org.apache.brooklyn.util.text.StringEscapes.JavaStringEscapes;
import org.apache.brooklyn.util.text.Strings;
import org.apache.commons.beanutils.BeanUtils;
import com.google.common.base.Function;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
/** static import functions which can be used in `$brooklyn:xxx` contexts */
public class BrooklynDslCommon {
// Access specific entities
public static DslComponent entity(String id) {
return new DslComponent(Scope.GLOBAL, id);
}
public static DslComponent parent() {
return new DslComponent(Scope.PARENT, null);
}
public static DslComponent child(String id) {
return new DslComponent(Scope.CHILD, id);
}
public static DslComponent sibling(String id) {
return new DslComponent(Scope.SIBLING, id);
}
public static DslComponent descendant(String id) {
return new DslComponent(Scope.DESCENDANT, id);
}
public static DslComponent ancestor(String id) {
return new DslComponent(Scope.ANCESTOR, id);
}
public static DslComponent root() {
return new DslComponent(Scope.ROOT, null);
}
public static DslComponent scopeRoot() {
return new DslComponent(Scope.SCOPE_ROOT, null);
}
// prefer the syntax above to the below now, but not deprecating the below
public static DslComponent component(String id) {
return component("global", id);
}
public static DslComponent component(String scope, String id) {
if (!DslComponent.Scope.isValid(scope)) {
throw new IllegalArgumentException(scope + " is not a valid scope");
}
return new DslComponent(DslComponent.Scope.fromString(scope), id);
}
// Access things on entities
public static BrooklynDslDeferredSupplier<?> config(String keyName) {
return new DslComponent(Scope.THIS, "").config(keyName);
}
public static BrooklynDslDeferredSupplier<?> attributeWhenReady(String sensorName) {
return new DslComponent(Scope.THIS, "").attributeWhenReady(sensorName);
}
/** Returns a {@link Sensor}, looking up the sensor on the context if available and using that,
* or else defining an untyped (Object) sensor */
public static BrooklynDslDeferredSupplier<Sensor<?>> sensor(String 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" })
public static Sensor<?> sensor(String clazzName, String sensorName) {
try {
// TODO Should use catalog's classloader, rather than Class.forName; how to get that? Should we return a future?!
String mappedClazzName = DeserializingClassRenamesProvider.findMappedName(clazzName);
Class<?> clazz = Class.forName(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);
}
}
// Build complex things
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 the arguments are not
* yet fully resolved.
*/
@SuppressWarnings("unchecked")
public static Object object(Map<String, Object> arguments) {
ConfigBag config = ConfigBag.newInstance(arguments);
String typeName = BrooklynYamlTypeInstantiator.InstantiatorFromKey.extractTypeName("object", config).orNull();
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());
try {
// TODO Should use catalog's classloader, rather than Class.forName; how to get that? Should we return a future?!
String mappedTypeName = DeserializingClassRenamesProvider.findMappedName(typeName);
Class<?> type = Class.forName(mappedTypeName);
if (!Reflections.hasNoArgConstructor(type)) {
throw new IllegalStateException(String.format("Cannot construct %s bean: No public no-arg constructor available", type));
}
if ((objectFields.isEmpty() || DslUtils.resolved(objectFields.values())) &&
(brooklynConfig.isEmpty() || DslUtils.resolved(brooklynConfig.values()))) {
return DslObject.create(type, objectFields, brooklynConfig);
} else {
return new DslObject(type, objectFields, brooklynConfig);
}
} catch (ClassNotFoundException e) {
throw Exceptions.propagate(e);
}
}
// String manipulation
/** Return the expression as a literal string without any further parsing. */
public static Object literal(Object expression) {
return expression;
}
/**
* Returns a formatted string or a {@link BrooklynDslDeferredSupplier} if the arguments
* are not yet fully resolved.
*/
public static Object formatString(final String pattern, final Object...args) {
if (DslUtils.resolved(args)) {
// if all args are resolved, apply the format string now
return String.format(pattern, args);
} else {
return new DslFormatString(pattern, args);
}
}
public static Object regexReplacement(final Object source, final Object pattern, final Object replacement) {
if (DslUtils.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 String formatting.
*
* @see DependentConfiguration#formatString(String, Object...)
*/
protected static class DslFormatString extends BrooklynDslDeferredSupplier<String> {
private static final long serialVersionUID = -4849297712650560863L;
private String pattern;
private Object[] args;
public DslFormatString(String pattern, Object ...args) {
this.pattern = pattern;
this.args = args;
}
@Override
public Task<String> newTask() {
return DependentConfiguration.formatString(pattern, args);
}
@Override
public String toString() {
return "$brooklyn:formatString("+
JavaStringEscapes.wrapJavaString(pattern)+
(args==null || args.length==0 ? "" : ","+Strings.join(args, ","))+")";
}
}
protected static class DslRegexReplacement extends BrooklynDslDeferredSupplier<String> {
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;
}
@Override
public Task<String> newTask() {
return DependentConfiguration.regexReplacement(source, pattern, replacement);
}
@Override
public String toString() {
return String.format("$brooklyn:regexReplace(%s:%s:%s)",source, pattern, replacement);
}
}
/** @deprecated since 0.7.0; use {@link DslFormatString} */
@SuppressWarnings("serial")
@Deprecated
protected static class FormatString extends DslFormatString {
public FormatString(String pattern, Object[] args) {
super(pattern, args);
}
}
/** Deferred execution of Object creation. */
protected static class DslObject extends BrooklynDslDeferredSupplier<Object> {
private static final long serialVersionUID = 8878388748085419L;
private Class<?> type;
private Map<String,Object> fields, config;
public DslObject(Class<?> type, Map<String,Object> fields, Map<String,Object> config) {
this.type = type;
this.fields = MutableMap.copyOf(fields);
this.config = MutableMap.copyOf(config);
}
@SuppressWarnings("unchecked")
@Override
public Task<Object> newTask() {
List<TaskAdaptable<Object>> tasks = Lists.newLinkedList();
for (Object value : Iterables.concat(fields.values(), config.values())) {
if (value instanceof TaskAdaptable) {
tasks.add((TaskAdaptable<Object>) value);
} else if (value instanceof TaskFactory) {
tasks.add(((TaskFactory<TaskAdaptable<Object>>) value).newTask());
}
}
Map<String,?> flags = MutableMap.<String,String>of("displayName", "building '"+type+"' with "+tasks.size()+" task"+(tasks.size()!=1?"s":""));
return DependentConfiguration.transformMultiple(flags, new Function<List<Object>, Object>() {
@Override
public Object apply(List<Object> input) {
Iterator<Object> values = input.iterator();
for (String name : fields.keySet()) {
Object value = fields.get(name);
if (value instanceof TaskAdaptable || value instanceof TaskFactory) {
fields.put(name, values.next());
} else if (value instanceof DeferredSupplier) {
fields.put(name, ((DeferredSupplier<?>) value).get());
}
}
for (String name : config.keySet()) {
Object value = config.get(name);
if (value instanceof TaskAdaptable || value instanceof TaskFactory) {
config.put(name, values.next());
} else if (value instanceof DeferredSupplier) {
config.put(name, ((DeferredSupplier<?>) value).get());
}
}
return create(type, fields, config);
}
}, tasks);
}
public static <T> T create(Class<T> type, Map<String,?> fields, Map<String,?> config) {
try {
T bean;
try {
bean = (T) TypeCoercions.coerce(fields, type);
} catch (ClassCoercionException ex) {
bean = Reflections.invokeConstructorWithArgs(type).get();
BeanUtils.populate(bean, fields);
}
if (bean instanceof Configurable && config.size() > 0) {
ConfigBag brooklyn = ConfigBag.newInstance(config);
FlagUtils.setFieldsFromFlags(bean, brooklyn);
FlagUtils.setAllConfigKeys((Configurable) bean, brooklyn, true);
}
return bean;
} catch (Exception e) {
throw Exceptions.propagate(e);
}
}
@Override
public String toString() {
return "$brooklyn:object(\""+type.getName()+"\")";
}
}
/**
* 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.
*/
public static DslExternal external(final String providerName, final String key) {
return new DslExternal(providerName, key);
}
protected 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;
}
@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 {
ManagementContextInternal managementContext = DslExternal.managementContext();
return managementContext.getExternalConfigProviderRegistry().getConfig(providerName, key);
}
})
.build();
}
@Override
public String toString() {
return "$brooklyn:external("+providerName+", "+key+")";
}
}
public static class Functions {
public static Object regexReplacement(final Object pattern, final Object replacement) {
if (DslUtils.resolved(pattern, replacement)) {
return new RegexReplacer(String.valueOf(pattern), String.valueOf(replacement));
} else {
return new DslRegexReplacer(pattern, replacement);
}
}
public static class RegexReplacer implements Function<String, String> {
private final String pattern;
private final String replacement;
public RegexReplacer(String pattern, String replacement) {
this.pattern = pattern;
this.replacement = replacement;
}
@Nullable
@Override
public String apply(@Nullable String s) {
return s == null ? null : Strings.replaceAllRegex(s, pattern, replacement);
}
}
protected static class DslRegexReplacer extends BrooklynDslDeferredSupplier<Function<String, String>> {
private Object pattern;
private Object replacement;
public DslRegexReplacer(Object pattern, Object replacement) {
this.pattern = pattern;
this.replacement = replacement;
}
@Override
public Task<Function<String, String>> newTask() {
return DependentConfiguration.regexReplacement(pattern, replacement);
}
@Override
public String toString() {
return String.format("$brooklyn:regexReplace(%s:%s)", pattern, replacement);
}
}
}
}