/*
* 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.entity.internal;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.apache.brooklyn.util.groovy.GroovyJavaMethods.elvis;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import org.apache.brooklyn.api.mgmt.ExecutionContext;
import org.apache.brooklyn.api.mgmt.Task;
import org.apache.brooklyn.config.ConfigInheritance;
import org.apache.brooklyn.config.ConfigKey;
import org.apache.brooklyn.core.config.Sanitizer;
import org.apache.brooklyn.core.config.StructuredConfigKey;
import org.apache.brooklyn.core.config.internal.AbstractConfigMapImpl;
import org.apache.brooklyn.core.entity.AbstractEntity;
import org.apache.brooklyn.util.collections.MutableMap;
import org.apache.brooklyn.util.core.config.ConfigBag;
import org.apache.brooklyn.util.core.flags.FlagUtils;
import org.apache.brooklyn.util.core.flags.SetFromFlag;
import org.apache.brooklyn.util.core.flags.TypeCoercions;
import org.apache.brooklyn.util.core.internal.ConfigKeySelfExtracting;
import org.apache.brooklyn.util.guava.Maybe;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Predicate;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
public class EntityConfigMap extends AbstractConfigMapImpl {
private static final Logger LOG = LoggerFactory.getLogger(EntityConfigMap.class);
/** entity against which config resolution / task execution will occur */
private final AbstractEntity entity;
/**
* Map of configuration information that is defined at start-up time for the entity. These
* configuration parameters are shared and made accessible to the "children" of this
* entity.
*/
private final Map<ConfigKey<?>,Object> inheritedConfig = Collections.synchronizedMap(new LinkedHashMap<ConfigKey<?>, Object>());
// TODO do we really want to have *both* bags and maps for these? danger that they get out of synch.
// have added some logic (Oct 2014) so that the same changes are applied to both, in most places at least;
// i (alex) think we should prefer ConfigBag (the input keys don't matter, it is more a question of retrieval keys),
// but first we need ConfigBag to support StructuredConfigKeys
private final ConfigBag localConfigBag;
private final ConfigBag inheritedConfigBag;
public EntityConfigMap(AbstractEntity entity) {
// Not using ConcurrentMap, because want to (continue to) allow null values.
// Could use ConcurrentMapAcceptingNullVals (with the associated performance hit on entrySet() etc).
this(entity, Collections.synchronizedMap(Maps.<ConfigKey<?>, Object>newLinkedHashMap()));
}
public EntityConfigMap(AbstractEntity entity, Map<ConfigKey<?>, Object> storage) {
this.entity = checkNotNull(entity, "entity must be specified");
this.ownConfig = checkNotNull(storage, "storage map must be specified");
// TODO store ownUnused in backing-storage
this.localConfigBag = ConfigBag.newInstance();
this.inheritedConfigBag = ConfigBag.newInstance();
}
@SuppressWarnings("unchecked")
public <T> T getConfig(ConfigKey<T> key, T defaultValue) {
// FIXME What about inherited task in config?!
// alex says: think that should work, no?
// FIXME What if someone calls getConfig on a task, before setting parent app?
// alex says: not supported (throw exception, or return the task)
// In case this entity class has overridden the given key (e.g. to set default), then retrieve this entity's key
// TODO If ask for a config value that's not in our configKeys, should we really continue with rest of method and return key.getDefaultValue?
// e.g. SshBasedJavaAppSetup calls setAttribute(JMX_USER), which calls getConfig(JMX_USER)
// but that example doesn't have a default...
ConfigKey<T> ownKey = entity!=null ? (ConfigKey<T>)elvis(entity.getEntityType().getConfigKey(key.getName()), key) : key;
ConfigInheritance inheritance = key.getInheritance();
if (inheritance==null) inheritance = ownKey.getInheritance();
if (inheritance==null) {
// TODO we could warn by introducing a temporary "ALWAYS_BUT_WARNING" instance
inheritance = getDefaultInheritance();
}
// TODO We're notifying of config-changed because currently persistence needs to know when the
// attributeWhenReady is complete (so it can persist the result).
// Long term, we'll just persist tasks properly so the call to onConfigChanged will go!
// Don't use groovy truth: if the set value is e.g. 0, then would ignore set value and return default!
if (ownKey instanceof ConfigKeySelfExtracting) {
Object rawval = ownConfig.get(key);
T result = null;
boolean complete = false;
if (((ConfigKeySelfExtracting<T>)ownKey).isSet(ownConfig)) {
ExecutionContext exec = entity.getExecutionContext();
result = ((ConfigKeySelfExtracting<T>)ownKey).extractValue(ownConfig, exec);
complete = true;
} else if (isInherited(ownKey, inheritance) &&
((ConfigKeySelfExtracting<T>)ownKey).isSet(inheritedConfig)) {
ExecutionContext exec = entity.getExecutionContext();
result = ((ConfigKeySelfExtracting<T>)ownKey).extractValue(inheritedConfig, exec);
complete = true;
} else if (localConfigBag.containsKey(ownKey)) {
// TODO configBag.get doesn't handle tasks/attributeWhenReady - it only uses TypeCoercions
result = localConfigBag.get(ownKey);
complete = true;
} else if (isInherited(ownKey, inheritance) &&
inheritedConfigBag.containsKey(ownKey)) {
result = inheritedConfigBag.get(ownKey);
complete = true;
}
if (rawval instanceof Task) {
entity.getManagementSupport().getEntityChangeListener().onConfigChanged(key);
}
if (complete) {
return result;
}
} else {
LOG.warn("Config key {} of {} is not a ConfigKeySelfExtracting; cannot retrieve value; returning default", ownKey, this);
}
return TypeCoercions.coerce((defaultValue != null) ? defaultValue : ownKey.getDefaultValue(), key.getTypeToken());
}
private <T> boolean isInherited(ConfigKey<T> key) {
return isInherited(key, key.getInheritance());
}
private <T> boolean isInherited(ConfigKey<T> key, ConfigInheritance inheritance) {
if (inheritance==null) inheritance = getDefaultInheritance();
return inheritance.isInherited(key, entity.getParent(), entity);
}
private ConfigInheritance getDefaultInheritance() {
return ConfigInheritance.ALWAYS;
}
@Override
public Maybe<Object> getConfigRaw(ConfigKey<?> key, boolean includeInherited) {
if (ownConfig.containsKey(key)) return Maybe.of(ownConfig.get(key));
if (includeInherited && inheritedConfig.containsKey(key)) return Maybe.of(inheritedConfig.get(key));
return Maybe.absent();
}
/** an immutable copy of the config visible at this entity, local and inherited (preferring local) */
public Map<ConfigKey<?>,Object> getAllConfig() {
Map<ConfigKey<?>,Object> result = new LinkedHashMap<ConfigKey<?>,Object>(inheritedConfig.size()+ownConfig.size());
result.putAll(inheritedConfig);
result.putAll(ownConfig);
return Collections.unmodifiableMap(result);
}
/** an immutable copy of the config defined at this entity, ie not inherited */
public Map<ConfigKey<?>,Object> getLocalConfig() {
Map<ConfigKey<?>,Object> result = new LinkedHashMap<ConfigKey<?>,Object>(ownConfig.size());
result.putAll(ownConfig);
return Collections.unmodifiableMap(result);
}
/** Creates an immutable copy of the config visible at this entity, local and inherited (preferring local), including those that did not match config keys */
public ConfigBag getAllConfigBag() {
return ConfigBag.newInstanceCopying(localConfigBag)
.putAll(ownConfig)
.putIfAbsent(inheritedConfig)
.putIfAbsent(inheritedConfigBag)
.seal();
}
/** Creates an immutable copy of the config defined at this entity, ie not inherited, including those that did not match config keys */
public ConfigBag getLocalConfigBag() {
return ConfigBag.newInstanceCopying(localConfigBag)
.putAll(ownConfig)
.seal();
}
@SuppressWarnings("unchecked")
public Object setConfig(ConfigKey<?> key, Object v) {
Object val = coerceConfigVal(key, v);
Object oldVal;
if (key instanceof StructuredConfigKey) {
oldVal = ((StructuredConfigKey)key).applyValueToMap(val, ownConfig);
// TODO ConfigBag does not handle structured config keys; quick fix is to remove (and should also remove any subkeys;
// as it stands if someone set string a.b.c in the config bag then removed structured key a.b, then got a.b.c they'd get a vale);
// long term fix is to support structured config keys in ConfigBag, at which point i think we could remove ownConfig altogether
localConfigBag.remove(key);
} else {
oldVal = ownConfig.put(key, val);
localConfigBag.put((ConfigKey<Object>)key, v);
}
entity.config().refreshInheritedConfigOfChildren();
return oldVal;
}
public void setLocalConfig(Map<ConfigKey<?>, ?> vals) {
ownConfig.clear();
localConfigBag.clear();
ownConfig.putAll(vals);
localConfigBag.putAll(vals);
}
public void setInheritedConfig(Map<ConfigKey<?>, ?> valsO, ConfigBag configBagVals) {
Map<ConfigKey<?>, ?> vals = filterUninheritable(valsO);
inheritedConfig.clear();
inheritedConfig.putAll(vals);
// The configBagVals contains all inherited, including strings that did not match a config key on the parent.
// They might match a config-key on this entity though, so need to check that:
// - if it matches one of our keys, set it in inheritedConfig
// - otherwise add it to our inheritedConfigBag
Set<String> valKeyNames = Sets.newLinkedHashSet();
for (ConfigKey<?> key : vals.keySet()) {
valKeyNames.add(key.getName());
}
Map<String,Object> valsUnmatched = MutableMap.<String,Object>builder()
.putAll(configBagVals.getAllConfig())
.removeAll(valKeyNames)
.build();
inheritedConfigBag.clear();
Map<ConfigKey<?>, SetFromFlag> annotatedConfigKeys = FlagUtils.getAnnotatedConfigKeys(entity.getClass());
Map<String, ConfigKey<?>> renamedConfigKeys = Maps.newLinkedHashMap();
for (Map.Entry<ConfigKey<?>, SetFromFlag> entry: annotatedConfigKeys.entrySet()) {
String rename = entry.getValue().value();
if (rename != null) {
renamedConfigKeys.put(rename, entry.getKey());
}
}
for (Map.Entry<String,Object> entry : valsUnmatched.entrySet()) {
String name = entry.getKey();
Object value = entry.getValue();
ConfigKey<?> key = renamedConfigKeys.get(name);
if (key == null) key = entity.getEntityType().getConfigKey(name);
if (key != null) {
if (!isInherited(key)) {
// no-op
} else if (inheritedConfig.containsKey(key)) {
LOG.warn("Entity "+entity+" inherited duplicate config for key "+key+", via explicit config and string name "+name+"; using value of key");
} else {
inheritedConfig.put(key, value);
}
} else {
// a config bag has discarded the keys, so we must assume default inheritance for things given that way
// unless we can infer a key; not a big deal, as we should have the key in inheritedConfig for everything
// which originated with a key ... but still, it would be nice to clean up the use of config bag!
inheritedConfigBag.putStringKey(name, value);
}
}
}
private Map<ConfigKey<?>, ?> filterUninheritable(Map<ConfigKey<?>, ?> vals) {
Map<ConfigKey<?>, Object> result = Maps.newLinkedHashMap();
for (Map.Entry<ConfigKey<?>, ?> entry : vals.entrySet()) {
if (isInherited(entry.getKey())) {
result.put(entry.getKey(), entry.getValue());
}
}
return result;
}
public void addToLocalBag(Map<String,?> vals) {
localConfigBag.putAll(vals);
// quick fix for problem that ownConfig can get out of synch
ownConfig.putAll(localConfigBag.getAllConfigAsConfigKeyMap());
}
public void removeFromLocalBag(String key) {
localConfigBag.remove(key);
ownConfig.remove(key);
}
public void clearInheritedConfig() {
inheritedConfig.clear();
inheritedConfigBag.clear();
}
@Override
public EntityConfigMap submap(Predicate<ConfigKey<?>> filter) {
EntityConfigMap m = new EntityConfigMap(entity, Maps.<ConfigKey<?>, Object>newLinkedHashMap());
for (Map.Entry<ConfigKey<?>,Object> entry: inheritedConfig.entrySet())
if (filter.apply(entry.getKey()))
m.inheritedConfig.put(entry.getKey(), entry.getValue());
synchronized (ownConfig) {
for (Map.Entry<ConfigKey<?>,Object> entry: ownConfig.entrySet())
if (filter.apply(entry.getKey()))
m.ownConfig.put(entry.getKey(), entry.getValue());
}
return m;
}
@Override
public String toString() {
Map<ConfigKey<?>, Object> sanitizeConfig;
synchronized (ownConfig) {
sanitizeConfig = Sanitizer.sanitize(ownConfig);
}
return super.toString()+"[own="+sanitizeConfig+"; inherited="+Sanitizer.sanitize(inheritedConfig)+"]";
}
}