/*
* 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.enricher.stock;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import org.apache.brooklyn.api.entity.Entity;
import org.apache.brooklyn.api.entity.EntityLocal;
import org.apache.brooklyn.api.sensor.AttributeSensor;
import org.apache.brooklyn.api.sensor.Sensor;
import org.apache.brooklyn.api.sensor.SensorEvent;
import org.apache.brooklyn.api.sensor.SensorEventListener;
import org.apache.brooklyn.config.ConfigKey;
import org.apache.brooklyn.core.config.ConfigKeys;
import org.apache.brooklyn.core.enricher.AbstractEnricher;
import org.apache.brooklyn.core.entity.Attributes;
import org.apache.brooklyn.util.collections.MutableMap;
import org.apache.brooklyn.util.core.flags.SetFromFlag;
import org.apache.brooklyn.util.core.sensor.SensorPredicates;
import org.apache.brooklyn.util.core.task.Tasks;
import org.apache.brooklyn.util.core.task.ValueResolver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.reflect.TypeToken;
@SuppressWarnings("serial")
//@Catalog(name="Propagator", description="Propagates attributes from one entity to another; see Enrichers.builder().propagating(...)")
public class Propagator extends AbstractEnricher implements SensorEventListener<Object> {
private static final Logger LOG = LoggerFactory.getLogger(Propagator.class);
public static final Set<Sensor<?>> SENSORS_NOT_USUALLY_PROPAGATED = ImmutableSet.<Sensor<?>>of(
Attributes.SERVICE_UP, Attributes.SERVICE_NOT_UP_INDICATORS,
Attributes.SERVICE_STATE_ACTUAL, Attributes.SERVICE_STATE_EXPECTED, Attributes.SERVICE_PROBLEMS);
@SetFromFlag("producer")
public static ConfigKey<Entity> PRODUCER = ConfigKeys.newConfigKey(Entity.class, "enricher.producer");
@SetFromFlag("propagatingAllBut")
public static ConfigKey<Collection<Sensor<?>>> PROPAGATING_ALL_BUT = ConfigKeys.newConfigKey(new TypeToken<Collection<Sensor<?>>>() {}, "enricher.propagating.propagatingAllBut");
@SetFromFlag("propagatingAll")
public static ConfigKey<Boolean> PROPAGATING_ALL = ConfigKeys.newBooleanConfigKey("enricher.propagating.propagatingAll");
@SetFromFlag("propagating")
public static ConfigKey<Collection<? extends Sensor<?>>> PROPAGATING = ConfigKeys.newConfigKey(new TypeToken<Collection<? extends Sensor<?>>>() {}, "enricher.propagating.inclusions");
@SetFromFlag("sensorMapping")
public static ConfigKey<Map<? extends Sensor<?>, ? extends Sensor<?>>> SENSOR_MAPPING = ConfigKeys.newConfigKey(new TypeToken<Map<? extends Sensor<?>, ? extends Sensor<?>>>() {}, "enricher.propagating.sensorMapping");
protected Entity producer;
protected Map<? extends Sensor<?>, ? extends Sensor<?>> sensorMapping;
protected boolean propagatingAll;
protected Collection<Sensor<?>> propagatingAllBut;
protected Predicate<Sensor<?>> sensorFilter;
public Propagator() {
}
@Override
public void setEntity(EntityLocal entity) {
super.setEntity(entity);
this.producer = getConfig(PRODUCER) == null ? entity : getConfig(PRODUCER);
boolean sensorMappingSet = getConfig(SENSOR_MAPPING)!=null;
MutableMap<Sensor<?>,Sensor<?>> sensorMappingTemp = MutableMap.copyOf(getConfig(SENSOR_MAPPING));
this.propagatingAll = Boolean.TRUE.equals(getConfig(PROPAGATING_ALL)) || getConfig(PROPAGATING_ALL_BUT)!=null;
if (getConfig(PROPAGATING) != null) {
if (propagatingAll) {
throw new IllegalStateException("Propagator enricher "+this+" must not have 'propagating' set at same time as either 'propagatingAll' or 'propagatingAllBut'");
}
for (Object sensorO : getConfig(PROPAGATING)) {
Sensor<?> sensor = Tasks.resolving(sensorO).as(Sensor.class).timeout(ValueResolver.REAL_QUICK_WAIT).context(producer).get();
if (!sensorMappingTemp.containsKey(sensor)) {
sensorMappingTemp.put(sensor, sensor);
}
}
this.sensorMapping = ImmutableMap.copyOf(sensorMappingTemp);
this.sensorFilter = new Predicate<Sensor<?>>() {
@Override public boolean apply(Sensor<?> input) {
// TODO kept for deserialization of inner classes, but shouldn't be necessary, as with other inner classes (qv);
// NB: previously this did this check:
// return input != null && sensorMapping.keySet().contains(input);
// but those clauses seems wrong (when would input be null?) and unnecessary (we are doing an explicit subscribe in this code path)
return true;
}
};
} else if (sensorMappingSet) {
if (propagatingAll) {
throw new IllegalStateException("Propagator enricher "+this+" must not have 'sensorMapping' set at same time as either 'propagatingAll' or 'propagatingAllBut'");
}
this.sensorMapping = ImmutableMap.copyOf(sensorMappingTemp);
this.sensorFilter = Predicates.alwaysTrue();
} else {
this.sensorMapping = ImmutableMap.<Sensor<?>, Sensor<?>>of();
if (!propagatingAll) {
// default if nothing specified is to do all but the ones not usually propagated
propagatingAll = true;
// user specified nothing, so *set* the all_but to the default set
// if desired, we could allow this to be dynamically reconfigurable, remove this field and always look up;
// slight performance hit (always looking up), and might need to recompute subscriptions, so not supported currently
// TODO this default is @Beta behaviour! -- maybe better to throw?
propagatingAllBut = SENSORS_NOT_USUALLY_PROPAGATED;
} else {
propagatingAllBut = getConfig(PROPAGATING_ALL_BUT);
}
this.sensorFilter = new Predicate<Sensor<?>>() {
@Override public boolean apply(Sensor<?> input) {
Collection<Sensor<?>> exclusions = propagatingAllBut;
// TODO this anonymous inner class and getConfig check kept should be removed / confirmed for rebind compatibility.
// we *should* be regenerating these fields on each rebind (calling to this method),
// so serialization of this class shouldn't be needed (and should be skipped), but that needs to be checked.
if (propagatingAllBut==null) exclusions = getConfig(PROPAGATING_ALL_BUT);
return input != null && (exclusions==null || !exclusions.contains(input));
}
};
}
Preconditions.checkState(propagatingAll ^ sensorMapping.size() > 0,
"Nothing to propagate; detected: propagatingAll (%s, excluding %s), sensorMapping (%s)", propagatingAll, getConfig(PROPAGATING_ALL_BUT), sensorMapping);
if (propagatingAll) {
subscriptions().subscribe(producer, null, this);
} else {
for (Sensor<?> sensor : sensorMapping.keySet()) {
subscriptions().subscribe(producer, sensor, this);
}
}
emitAllAttributes();
}
@SuppressWarnings({ "rawtypes", "unchecked" })
@Override
public void onEvent(SensorEvent<Object> event) {
// propagate upwards
Sensor<?> sourceSensor = event.getSensor();
Sensor<?> destinationSensor = getDestinationSensor(sourceSensor);
if (!sensorFilter.apply(sourceSensor)) {
return; // ignoring excluded sensor
}
if (LOG.isTraceEnabled()) LOG.trace("enricher {} got {}, propagating via {}{}",
new Object[] {this, event, entity, (sourceSensor == destinationSensor ? "" : " (as "+destinationSensor+")")});
emit((Sensor)destinationSensor, event.getValue());
}
/** useful once sensors are added to emit all values */
public void emitAllAttributes() {
emitAllAttributes(false);
}
@SuppressWarnings({ "rawtypes", "unchecked" })
public void emitAllAttributes(boolean includeNullValues) {
Iterable<? extends Sensor<?>> sensorsToPopulate = propagatingAll
? Iterables.filter(producer.getEntityType().getSensors(), sensorFilter)
: sensorMapping.keySet();
for (Sensor<?> s : sensorsToPopulate) {
if (s instanceof AttributeSensor) {
AttributeSensor destinationSensor = (AttributeSensor<?>) getDestinationSensor(s);
Object v = producer.getAttribute((AttributeSensor<?>)s);
// TODO we should keep a timestamp for the source sensor and echo it
// (this pretends timestamps are current, which probably isn't the case when we are propagating)
if (v != null || includeNullValues) entity.sensors().set(destinationSensor, v);
}
}
}
private Sensor<?> getDestinationSensor(final Sensor<?> sourceSensor) {
// sensor equality includes the type; we want just name-equality so will use predicate.
Optional<? extends Sensor<?>> mappingSensor = Iterables.tryFind(sensorMapping.keySet(),
SensorPredicates.nameEqualTo(sourceSensor.getName()));
return mappingSensor.isPresent() ? sensorMapping.get(mappingSensor.get()) : sourceSensor;
}
}