org.apache.brooklyn.enricher.stock.Aggregator.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.brooklyn.enricher.stock.Aggregator.java

Source

/*
 * 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.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

import org.apache.brooklyn.api.entity.Entity;
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.BrooklynLogging;
import org.apache.brooklyn.core.config.ConfigKeys;
import org.apache.brooklyn.util.collections.MutableList;
import org.apache.brooklyn.util.collections.MutableMap;
import org.apache.brooklyn.util.collections.QuorumCheck.QuorumChecks;
import org.apache.brooklyn.util.core.flags.SetFromFlag;
import org.apache.brooklyn.util.exceptions.Exceptions;
import org.apache.brooklyn.util.text.StringPredicates;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
import com.google.common.reflect.TypeToken;

/** Building on {@link AbstractAggregator} for a single source sensor (on multiple children and/or members) */
@SuppressWarnings("serial")
//@Catalog(name="Aggregator", description="Aggregates attributes from multiple entities into a single attribute value; see Enrichers.builder().aggregating(...)")
public class Aggregator<T, U> extends AbstractAggregator<T, U> implements SensorEventListener<T> {

    private static final Logger LOG = LoggerFactory.getLogger(Aggregator.class);

    public static final ConfigKey<Sensor<?>> SOURCE_SENSOR = ConfigKeys.newConfigKey(new TypeToken<Sensor<?>>() {
    }, "enricher.sourceSensor");

    @SetFromFlag("transformation")
    public static final ConfigKey<Object> TRANSFORMATION_UNTYPED = ConfigKeys.newConfigKey(Object.class,
            "enricher.transformation.untyped",
            "Specifies a transformation, as a function from a collection to the value, or as a string matching a pre-defined named transformation, "
                    + "such as 'average' (for numbers), 'sum' (for numbers), or 'list' (the default, putting any collection of items into a list)");
    public static final ConfigKey<Function<? super Collection<?>, ?>> TRANSFORMATION = ConfigKeys
            .newConfigKey(new TypeToken<Function<? super Collection<?>, ?>>() {
            }, "enricher.transformation");

    public static final ConfigKey<Boolean> EXCLUDE_BLANK = ConfigKeys.newBooleanConfigKey(
            "enricher.aggregator.excludeBlank",
            "Whether explicit nulls or blank strings should be excluded (default false); this only applies if no value filter set",
            false);

    /**
     * @see QuorumChecks
     */
    public static final ConfigKey<String> QUORUM_CHECK_TYPE = ConfigKeys.newStringConfigKey("quorum.check.type",
            "The requirement to be considered quorate -- possible values: 'all', 'allAndAtLeastOne', 'atLeastOne', 'atLeastOneUnlessEmpty', 'alwaysHealthy'",
            "allAndAtLeastOne");

    public static final ConfigKey<Integer> QUORUM_TOTAL_SIZE = ConfigKeys.newIntegerConfigKey("quorum.total.size",
            "The total size to consider when determining if quorate", 1);

    protected Sensor<T> sourceSensor;
    protected Function<? super Collection<T>, ? extends U> transformation;

    /**
     * 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, T> values = Collections.synchronizedMap(new LinkedHashMap<Entity, T>());

    public Aggregator() {
    }

    @SuppressWarnings("unchecked")
    protected void setEntityLoadingConfig() {
        super.setEntityLoadingConfig();
        this.sourceSensor = (Sensor<T>) getRequiredConfig(SOURCE_SENSOR);

        this.transformation = (Function<? super Collection<T>, ? extends U>) config().get(TRANSFORMATION);

        Object t1 = config().get(TRANSFORMATION_UNTYPED);
        Function<? super Collection<?>, ?> t2 = null;
        if (t1 instanceof String) {
            t2 = lookupTransformation((String) t1);
            if (t2 == null) {
                LOG.warn("Unknown transformation '" + t1 + "' for " + this + "; will use default transformation");
            }
        }

        if (this.transformation == null) {
            this.transformation = (Function<? super Collection<T>, ? extends U>) t2;
        } else if (t1 != null && !Objects.equals(t2, this.transformation)) {
            throw new IllegalStateException("Cannot supply both " + TRANSFORMATION_UNTYPED + " and "
                    + TRANSFORMATION + " unless they are equal.");
        }
    }

    @SuppressWarnings({ "rawtypes", "unchecked" })
    protected Function<? super Collection<?>, ?> lookupTransformation(String t1) {
        if ("average".equalsIgnoreCase(t1))
            return new Enrichers.ComputingAverage(null, null, targetSensor.getTypeToken());
        if ("sum".equalsIgnoreCase(t1))
            return new Enrichers.ComputingSum(null, null, targetSensor.getTypeToken());
        if ("isQuorate".equalsIgnoreCase(t1))
            return new Enrichers.ComputingIsQuorate(targetSensor.getTypeToken(),
                    QuorumChecks.of(config().get(QUORUM_CHECK_TYPE)), config().get(QUORUM_TOTAL_SIZE));
        if ("list".equalsIgnoreCase(t1))
            return new ComputingList();
        return null;
    }

    private class ComputingList<TT> implements Function<Collection<TT>, List<TT>> {
        @Override
        public List<TT> apply(Collection<TT> input) {
            if (input == null)
                return null;
            return MutableList.copyOf(input).asUnmodifiable();
        }

    }

    @Override
    protected void setEntityBeforeSubscribingProducerChildrenEvents() {
        BrooklynLogging.log(LOG, BrooklynLogging.levelDebugOrTraceIfReadOnly(producer),
                "{} subscribing to children of {}", this, producer);
        subscriptions().subscribeToChildren(producer, sourceSensor, this);
    }

    @Override
    protected void addProducerHardcoded(Entity producer) {
        subscriptions().subscribe(producer, sourceSensor, this);
        onProducerAdded(producer);
    }

    @Override
    protected void addProducerChild(Entity producer) {
        // no subscription needed here, due to the subscribeToChildren call
        onProducerAdded(producer);
    }

    @Override
    protected void addProducerMember(Entity producer) {
        subscriptions().subscribe(producer, sourceSensor, this);
        onProducerAdded(producer);
    }

    @Override
    protected void onProducerAdded(Entity producer) {
        BrooklynLogging.log(LOG, BrooklynLogging.levelDebugOrTraceIfReadOnly(producer), "{} listening to {}", this,
                producer);
        synchronized (values) {
            T vo = values.get(producer);
            if (vo == null) {
                T initialVal;
                if (sourceSensor instanceof AttributeSensor) {
                    initialVal = producer.getAttribute((AttributeSensor<T>) sourceSensor);
                } else {
                    initialVal = null;
                }
                values.put(producer, initialVal != null ? initialVal : defaultMemberValue);
                //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 });
            }
        }
    }

    @Override
    protected Predicate<?> getDefaultValueFilter() {
        if (getConfig(EXCLUDE_BLANK))
            return StringPredicates.isNonBlank();
        else
            return Predicates.alwaysTrue();
    }

    @Override
    protected void onProducerRemoved(Entity producer) {
        values.remove(producer);
        onUpdated();
    }

    @Override
    public void onEvent(SensorEvent<T> 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();
    }

    protected void onUpdated() {
        try {
            emit(targetSensor, compute());
        } catch (Throwable t) {
            LOG.warn("Error calculating and setting aggregate for enricher " + this, t);
            throw Exceptions.propagate(t);
        }
    }

    @Override
    protected Object compute() {
        synchronized (values) {
            // TODO Could avoid copying when filter not needed
            List<T> vs = MutableList.copyOf(Iterables.filter(values.values(), valueFilter));
            if (transformation == null)
                return vs;
            return transformation.apply(vs);
        }
    }

    protected Map<Entity, T> copyOfValues() {
        // Don't use ImmutableMap, as can contain null values
        synchronized (values) {
            return Collections.unmodifiableMap(MutableMap.copyOf(values));
        }
    }

}