/*
* 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.Collections;
import java.util.LinkedHashMap;
import java.util.List;
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.entity.Group;
import org.apache.brooklyn.api.sensor.AttributeSensor;
import org.apache.brooklyn.api.sensor.SensorEvent;
import org.apache.brooklyn.api.sensor.SensorEventListener;
import org.apache.brooklyn.core.enricher.AbstractEnricher;
import org.apache.brooklyn.core.entity.trait.Changeable;
import org.apache.brooklyn.util.groovy.GroovyJavaMethods;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.ImmutableMap;
/**
* AggregatingEnrichers implicitly subscribes to the same sensor<S> on all entities inside an
* {@link Group} and should emit an aggregate<T> on the target sensor
*
* @deprecated since 0.7.0; use {@link Enrichers.builder()}
* @see Aggregator if need to sub-class
*/
public abstract class AbstractAggregatingEnricher<S,T> extends AbstractEnricher implements SensorEventListener<S> {
private static final Logger LOG = LoggerFactory.getLogger(AbstractAggregatingEnricher.class);
AttributeSensor<? extends S> source;
protected AttributeSensor<T> target;
protected S defaultValue;
Set<Entity> producers;
List<Entity> hardCodedProducers;
boolean allMembers;
Predicate<Entity> filter;
/**
* Users of values should either on it synchronize when iterating over its entries or use
* copyOfValues to obtain an immutable copy of the map.
*/
// We use a synchronizedMap over a ConcurrentHashMap for entities that store null values.
protected final Map<Entity, S> values = Collections.synchronizedMap(new LinkedHashMap<Entity, S>());
public AbstractAggregatingEnricher(Map<String,?> flags, AttributeSensor<? extends S> source, AttributeSensor<T> target) {
this(flags, source, target, null);
}
@SuppressWarnings("unchecked")
public AbstractAggregatingEnricher(Map<String,?> flags, AttributeSensor<? extends S> source, AttributeSensor<T> target, S defaultValue) {
super(flags);
this.source = source;
this.target = target;
this.defaultValue = defaultValue;
hardCodedProducers = (List<Entity>) (flags.containsKey("producers") ? flags.get("producers") : Collections.emptyList());
allMembers = (Boolean) (flags.containsKey("allMembers") ? flags.get("allMembers") : false);
filter = flags.containsKey("filter") ? GroovyJavaMethods.<Entity>castToPredicate(flags.get("filter")) : Predicates.<Entity>alwaysTrue();
}
public void addProducer(Entity producer) {
if (LOG.isDebugEnabled()) LOG.debug("{} linked ({}, {}) to {}", new Object[] {this, producer, source, target});
subscriptions().subscribe(producer, source, this);
synchronized (values) {
S vo = values.get(producer);
if (vo==null) {
S initialVal = ((EntityLocal)producer).getAttribute(source);
values.put(producer, initialVal != null ? initialVal : defaultValue);
//we might skip in onEvent in the short window while !values.containsKey(producer)
//but that's okay because the put which would have been done there is done here now
} else {
//vo will be null unless some weird race with addProducer+removeProducer is occuring
//(and that's something we can tolerate i think)
if (LOG.isDebugEnabled()) LOG.debug("{} already had value ({}) for producer ({}); but that producer has just been added", new Object[] {this, vo, producer});
}
}
onUpdated();
}
// TODO If producer removed but then get (queued) event from it after this method returns,
public S removeProducer(Entity producer) {
if (LOG.isDebugEnabled()) LOG.debug("{} unlinked ({}, {}) from {}", new Object[] {this, producer, source, target});
subscriptions().unsubscribe(producer);
S removed = values.remove(producer);
onUpdated();
return removed;
}
@Override
public void onEvent(SensorEvent<S> event) {
Entity e = event.getSource();
synchronized (values) {
if (values.containsKey(e)) {
values.put(e, event.getValue());
} else {
if (LOG.isDebugEnabled()) LOG.debug("{} received event for unknown producer ({}); presumably that producer has recently been removed", this, e);
}
}
onUpdated();
}
/**
* Called whenever the values for the set of producers changes (e.g. on an event, or on a member added/removed).
* Defaults to no-op
*/
// TODO should this be abstract?
protected void onUpdated() {
// no-op
}
@Override
public void setEntity(EntityLocal entity) {
super.setEntity(entity);
for (Entity producer : hardCodedProducers) {
if (filter.apply(producer)) {
addProducer(producer);
}
}
if (allMembers) {
subscriptions().subscribe(entity, Changeable.MEMBER_ADDED, new SensorEventListener<Entity>() {
@Override public void onEvent(SensorEvent<Entity> it) {
if (filter.apply(it.getValue())) addProducer(it.getValue());
}
});
subscriptions().subscribe(entity, Changeable.MEMBER_REMOVED, new SensorEventListener<Entity>() {
@Override public void onEvent(SensorEvent<Entity> it) {
removeProducer(it.getValue());
}
});
if (entity instanceof Group) {
for (Entity member : ((Group)entity).getMembers()) {
if (filter.apply(member)) {
addProducer(member);
}
}
}
}
}
protected Map<Entity, S> copyOfValues() {
synchronized (values) {
return ImmutableMap.copyOf(values);
}
}
}