/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.brooklyn.core.config;
import static com.google.common.base.Preconditions.checkNotNull;
import java.io.Serializable;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.apache.brooklyn.api.mgmt.ExecutionContext;
import org.apache.brooklyn.config.ConfigInheritance;
import org.apache.brooklyn.config.ConfigKey;
import org.apache.brooklyn.util.core.internal.ConfigKeySelfExtracting;
import org.apache.brooklyn.util.core.task.Tasks;
import org.apache.brooklyn.util.exceptions.Exceptions;
import org.apache.brooklyn.util.guava.TypeTokens;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.annotations.Beta;
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.Splitter;
import com.google.common.collect.Lists;
import com.google.common.reflect.TypeToken;
public class BasicConfigKey<T> implements ConfigKeySelfExtracting<T>, Serializable {
private static final Logger log = LoggerFactory.getLogger(BasicConfigKey.class);
private static final long serialVersionUID = -1762014059150215376L;
private static final Splitter dots = Splitter.on('.');
public static <T> Builder<T> builder(TypeToken<T> type) {
return new Builder<T>().type(type);
}
public static <T> Builder<T> builder(Class<T> type) {
return new Builder<T>().type(type);
}
public static <T> Builder<T> builder(TypeToken<T> type, String name) {
return new Builder<T>().type(type).name(name);
}
public static <T> Builder<T> builder(Class<T> type, String name) {
return new Builder<T>().type(type).name(name);
}
public static <T> Builder<T> builder(ConfigKey<T> key) {
return new Builder<T>()
.name(checkNotNull(key.getName(), "name"))
.type(checkNotNull(key.getTypeToken(), "type"))
.description(key.getDescription())
.defaultValue(key.getDefaultValue())
.reconfigurable(key.isReconfigurable())
.inheritance(key.getInheritance())
.constraint(key.getConstraint());
}
public static class Builder<T> {
private String name;
private TypeToken<T> type;
private String description;
private T defaultValue;
private boolean reconfigurable;
private Predicate<? super T> constraint = Predicates.alwaysTrue();
private ConfigInheritance inheritance;
public Builder<T> name(String val) {
this.name = val; return this;
}
public Builder<T> type(Class<T> val) {
this.type = TypeToken.of(val); return this;
}
public Builder<T> type(TypeToken<T> val) {
this.type = val; return this;
}
public Builder<T> description(String val) {
this.description = val; return this;
}
public Builder<T> defaultValue(T val) {
this.defaultValue = val; return this;
}
public Builder<T> reconfigurable(boolean val) {
this.reconfigurable = val; return this;
}
public Builder<T> inheritance(ConfigInheritance val) {
this.inheritance = val; return this;
}
@Beta
public Builder<T> constraint(Predicate<? super T> constraint) {
this.constraint = checkNotNull(constraint, "constraint"); return this;
}
public BasicConfigKey<T> build() {
return new BasicConfigKey<T>(this);
}
public String getName() {
return name;
}
public String getDescription() {
return description;
}
}
private String name;
private TypeToken<T> typeToken;
private Class<? super T> type;
private String description;
private T defaultValue;
private boolean reconfigurable;
private ConfigInheritance inheritance;
private Predicate<? super T> constraint;
// FIXME In groovy, fields were `public final` with a default constructor; do we need the gson?
public BasicConfigKey() { /* for gson */ }
public BasicConfigKey(Class<T> type, String name) {
this(TypeToken.of(type), name);
}
public BasicConfigKey(Class<T> type, String name, String description) {
this(TypeToken.of(type), name, description);
}
public BasicConfigKey(Class<T> type, String name, String description, T defaultValue) {
this(TypeToken.of(type), name, description, defaultValue);
}
public BasicConfigKey(TypeToken<T> type, String name) {
this(type, name, name, null);
}
public BasicConfigKey(TypeToken<T> type, String name, String description) {
this(type, name, description, null);
}
public BasicConfigKey(TypeToken<T> type, String name, String description, T defaultValue) {
this.description = description;
this.name = checkNotNull(name, "name");
this.type = TypeTokens.getRawTypeIfRaw(checkNotNull(type, "type"));
this.typeToken = TypeTokens.getTypeTokenIfNotRaw(type);
this.defaultValue = defaultValue;
this.reconfigurable = false;
this.constraint = Predicates.alwaysTrue();
}
public BasicConfigKey(Builder<T> builder) {
this.name = checkNotNull(builder.name, "name");
this.type = TypeTokens.getRawTypeIfRaw(checkNotNull(builder.type, "type"));
this.typeToken = TypeTokens.getTypeTokenIfNotRaw(builder.type);
this.description = builder.description;
this.defaultValue = builder.defaultValue;
this.reconfigurable = builder.reconfigurable;
this.inheritance = builder.inheritance;
// Note: it's intentionally possible to have default values that are not valid
// per the configured constraint. If validity were checked here any class that
// contained a weirdly-defined config key would fail to initialise.
this.constraint = checkNotNull(builder.constraint, "constraint");
}
/** @see ConfigKey#getName() */
@Override public String getName() { return name; }
/** @see ConfigKey#getTypeName() */
@Override public String getTypeName() { return getType().getName(); }
/** @see ConfigKey#getType() */
@Override public Class<? super T> getType() { return TypeTokens.getRawType(typeToken, type); }
/** @see ConfigKey#getTypeToken() */
@Override public TypeToken<T> getTypeToken() { return TypeTokens.getTypeToken(typeToken, type); }
/** @see ConfigKey#getDescription() */
@Override public String getDescription() { return description; }
/** @see ConfigKey#getDefaultValue() */
@Override public T getDefaultValue() { return defaultValue; }
/** @see ConfigKey#hasDefaultValue() */
@Override public boolean hasDefaultValue() {
return defaultValue != null;
}
/** @see ConfigKey#isReconfigurable() */
@Override
public boolean isReconfigurable() {
return reconfigurable;
}
/** @see ConfigKey#getInheritance() */
@Override @Nullable
public ConfigInheritance getInheritance() {
return inheritance;
}
/** @see ConfigKey#getConstraint() */
@Override @Nonnull
public Predicate<? super T> getConstraint() {
// Could be null after rebinding
if (constraint != null) {
return constraint;
} else {
return Predicates.alwaysTrue();
}
}
/** @see ConfigKey#isValueValid(T) */
@Override
public boolean isValueValid(T value) {
// The likeliest source of an exception is a constraint from Guava that expects a non-null input.
try {
return getConstraint().apply(value);
} catch (Exception e) {
log.debug("Suppressing exception when testing validity of " + this, e);
return false;
}
}
/** @see ConfigKey#getNameParts() */
@Override public Collection<String> getNameParts() {
return Lists.newArrayList(dots.split(name));
}
@Override
public boolean equals(Object obj) {
if (obj == this) return true;
if (!(obj instanceof BasicConfigKey)) return false;
BasicConfigKey<?> o = (BasicConfigKey<?>) obj;
return Objects.equal(name, o.name);
}
@Override
public int hashCode() {
return Objects.hashCode(name);
}
@Override
public String toString() {
return String.format("%s[ConfigKey:%s]", name, getTypeName());
}
/**
* Retrieves the value corresponding to this config key from the given map.
* Could be overridden by more sophisticated config keys, such as MapConfigKey etc.
*/
@SuppressWarnings("unchecked")
@Override
public T extractValue(Map<?,?> vals, ExecutionContext exec) {
Object v = vals.get(this);
try {
return (T) resolveValue(v, exec);
} catch (Exception e) {
throw Exceptions.propagate(e);
}
}
@Override
public boolean isSet(Map<?,?> vals) {
return vals.containsKey(this);
}
protected Object resolveValue(Object v, ExecutionContext exec) throws ExecutionException, InterruptedException {
if (v instanceof Collection || v instanceof Map) {
return Tasks.resolveDeepValue(v, Object.class, exec, "config "+name);
} else {
return Tasks.resolveValue(v, getType(), exec, "config "+name);
}
}
/** used to record a key which overwrites another; only needed at disambiguation time
* if a class declares a key and an equivalent one (often inherited) which overwrites it.
* See org.apache.brooklyn.core.entity.ConfigEntityInheritanceTest, and uses of this class, for more explanation.
*/
public static class BasicConfigKeyOverwriting<T> extends BasicConfigKey<T> {
private static final long serialVersionUID = -3458116971918128018L;
private final ConfigKey<T> parentKey;
/** builder here should be based on the same key passed in as parent */
@Beta
public BasicConfigKeyOverwriting(Builder<T> builder, ConfigKey<T> parent) {
super(builder);
parentKey = parent;
Preconditions.checkArgument(Objects.equal(builder.name, parent.getName()), "Builder must use key of the same name.");
}
public BasicConfigKeyOverwriting(ConfigKey<T> key, T defaultValue) {
this(builder(key).defaultValue(defaultValue), key);
}
public BasicConfigKeyOverwriting(ConfigKey<T> key, String newDescription, T defaultValue) {
this(builder(key).description(newDescription).defaultValue(defaultValue), key);
}
public ConfigKey<T> getParentKey() {
return parentKey;
}
}
}