/*
* 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.sensor;
import org.apache.brooklyn.api.entity.Entity;
import org.apache.brooklyn.api.mgmt.ManagementContext;
import org.apache.brooklyn.api.sensor.Sensor;
import org.apache.brooklyn.config.ConfigKey;
import org.apache.brooklyn.core.config.BasicConfigKey;
import org.apache.brooklyn.core.config.BasicConfigKey.Builder;
import org.apache.brooklyn.core.config.ConfigKeys;
import org.apache.brooklyn.core.entity.AbstractEntity;
import org.apache.brooklyn.core.entity.BrooklynConfigKeys;
import org.apache.brooklyn.core.feed.ConfigToAttributes;
import org.apache.brooklyn.util.core.flags.TypeCoercions;
import org.apache.brooklyn.util.exceptions.Exceptions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.reflect.TypeToken;
/**
* A {@link Sensor} describing an attribute that can be configured with inputs that are used to derive the final value.
* <p>
* The {@link ConfigKey} will have the same name and description as the sensor but not necessarily the same type.
* Conversion to set the sensor value from the config key must be supplied in a subclass.
* <p>
* {@link ConfigToAttributes#apply(EntityLocal, AttributeSensorAndConfigKey)} is useful to set the attribute from the sensor.
*/
public abstract class AttributeSensorAndConfigKey<ConfigType,SensorType> extends BasicAttributeSensor<SensorType>
implements ConfigKey.HasConfigKey<ConfigType> {
private static final long serialVersionUID = -3103809215973264600L;
private static final Logger log = LoggerFactory.getLogger(AttributeSensorAndConfigKey.class);
private ConfigKey<ConfigType> configKey;
public AttributeSensorAndConfigKey(Class<ConfigType> configType, Class<SensorType> sensorType, String name) {
this(configType, sensorType, name, name, null);
}
public AttributeSensorAndConfigKey(Class<ConfigType> configType, Class<SensorType> sensorType, String name, String description) {
this(TypeToken.of(configType), TypeToken.of(sensorType), name, description, null);
}
public AttributeSensorAndConfigKey(Class<ConfigType> configType, Class<SensorType> sensorType, String name, String description, Object defaultValue) {
this(TypeToken.of(configType), TypeToken.of(sensorType), name, description, defaultValue);
}
public AttributeSensorAndConfigKey(TypeToken<ConfigType> configType, TypeToken<SensorType> sensorType, String name) {
this(configType, sensorType, name, null);
}
public AttributeSensorAndConfigKey(TypeToken<ConfigType> configType, TypeToken<SensorType> sensorType, String name, String description) {
this(configType, sensorType, name, description, null);
}
public AttributeSensorAndConfigKey(TypeToken<ConfigType> configType, TypeToken<SensorType> sensorType, String name, String description, Object defaultValue) {
super(sensorType, name, description);
ConfigType defaultValueTyped;
try {
defaultValueTyped = TypeCoercions.coerce(defaultValue, configType);
} catch (Exception e) {
log.warn("Invalid default value '"+defaultValue+"' for "+name+" (rethrowing: "+e, e);
throw Exceptions.propagate(e);
}
configKey = new BasicConfigKey<ConfigType>(configType, name, description, defaultValueTyped);
}
public AttributeSensorAndConfigKey(AttributeSensorAndConfigKey<ConfigType,SensorType> orig, ConfigType defaultValue) {
super(orig.getTypeToken(), orig.getName(), orig.getDescription());
configKey = ConfigKeys.newConfigKeyWithDefault(orig.configKey,
TypeCoercions.coerce(defaultValue, orig.configKey.getTypeToken()));
}
public AttributeSensorAndConfigKey(Builder<ConfigType> configKeyBuilder, TypeToken<SensorType> sensorType) {
super(sensorType, configKeyBuilder.getName(), configKeyBuilder.getDescription());
configKey = new BasicConfigKey<ConfigType>(configKeyBuilder);
}
public ConfigKey<ConfigType> getConfigKey() { return configKey; }
/** returns the sensor value for this attribute on the given entity, if present,
* otherwise works out what the sensor value should be based on the config key's value
* <p>
* calls to this may allocate resources (e.g. ports) so should be called only once and
* then (if non-null) assigned as the sensor's value
* <p>
* <b>(for this reason this method should generally not be invoked by callers except in tests and by the framework,
* and similarly should not be overridden; implement {@link #convertConfigToSensor(Object, Entity)} instead for single-execution calls.
* the framework calls this from {@link AbstractEntity#setAttribute(AttributeSensorAndConfigKey)}
* typically via {@link ConfigToAttributes#apply(EntityLocal)} e.g. from SoftwareProcessImpl.preStart().)
* </b>
*/
public SensorType getAsSensorValue(Entity e) {
SensorType sensorValue = e.getAttribute(this);
if (sensorValue!=null) return sensorValue;
ConfigType v = e.config().get(this);
try {
return convertConfigToSensor(v, e);
} catch (Throwable t) {
throw new IllegalArgumentException("Cannot convert config value "+v+" for sensor "+this+": "+t, t);
}
}
/**
* @see {@link #getAsSensorValue(Entity)}
*
* Differs in that the config value is converted based on just the management context, rather
* than for a specific entity. For example, useful if using {@link BrooklynConfigKeys} in BrooklynWebServer.
* </b>
*/
public SensorType getAsSensorValue(ManagementContext managementContext) {
ConfigType v = managementContext.getConfig().getConfig(this);
try {
return convertConfigToSensor(v, managementContext);
} catch (Throwable t) {
throw new IllegalArgumentException("Cannot convert config value "+v+" for sensor "+this+": "+t, t);
}
}
/** converts the given ConfigType value to the corresponding SensorType value,
* with respect to the given entity
* <p>
* this is invoked after checks whether the entity already has a value for the sensor,
* and the entity-specific config value is passed for convenience if set,
* otherwise the config key default value is passed for convenience
* <p>
* this message should be allowed to return null if the conversion cannot be completed at this time */
protected abstract SensorType convertConfigToSensor(ConfigType value, Entity entity);
/**
* @see {@link #convertConfigToSensor(Object, Entity)}
*/
protected abstract SensorType convertConfigToSensor(ConfigType value, ManagementContext entity);
}