All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.netflix.eventbus.utils.EventBusUtils Maven / Gradle / Ivy

package com.netflix.eventbus.utils;

import com.google.common.base.Preconditions;
import com.netflix.config.DynamicIntProperty;
import com.netflix.config.DynamicPropertyFactory;
import com.netflix.eventbus.impl.EventBatch;
import com.netflix.eventbus.spi.DynamicSubscriber;
import com.netflix.eventbus.spi.EventBus;
import com.netflix.eventbus.spi.EventFilter;
import com.netflix.eventbus.spi.Subscribe;
import com.netflix.eventbus.spi.SubscriberConfigProvider;
import com.netflix.eventbus.spi.SubscriberInfo;
import com.netflix.servo.monitor.MonitorConfig;
import com.netflix.servo.monitor.StatsTimer;
import com.netflix.servo.monitor.Stopwatch;
import com.netflix.servo.stats.StatsConfig;
import org.slf4j.Logger;

import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Set;

/**
 * General utility methods for {@link com.netflix.eventbus.spi.EventBus}
 *
 * @author Nitesh Kant ([email protected])
 */
public class EventBusUtils {

    private static final DynamicIntProperty queueSizeDefault =
            DynamicPropertyFactory.getInstance().getIntProperty(EventBus.CONSUMER_QUEUE_SIZE_DEFAULT_PROP_NAME,
                    EventBus.CONSUMER_QUEUE_SIZE_DEFAULT);

    /**
     * Returns an appropriate consumer queue size for the passed subscribe annotation. This method defaults
     * the size to the one specified in the fast property {@link EventBus#CONSUMER_QUEUE_SIZE_DEFAULT_PROP_NAME}
     *
     *
     * @param subscribe The annotation for which the queue size is to be retrieved.
     *
     * @return The queue size.
     */
    public static int getQueueSize(SubscriberConfigProvider.SubscriberConfig subscribe) {
        int queueSize = subscribe.getQueueSize();
        if(queueSize <= 0) {
            queueSize = queueSizeDefault.get();
        }
        return queueSize;
    }

    /**
     * Returns configuration for the passed subscriber method. This configuration can be obtained from the
     * {@link Subscribe} annotation on the method or from {@link SubscriberConfigProvider} if the subscriber implements
     * that interface.
     *
     * @param subscriber The instance of the subscriber that contains the subscriber method.
     * @param  subMethod Method for which the configuration has to be found.
     *
     * @return Subscriber configuration.
     */
    public static SubscriberConfigProvider.SubscriberConfig getSubscriberConfig(Method subMethod, Object subscriber) {
        Preconditions.checkNotNull(subscriber);
        Preconditions.checkNotNull(subMethod);

        Subscribe annotation = subMethod.getAnnotation(Subscribe.class);
        if (null == annotation) {
            throw new IllegalArgumentException(String.format("Subscriber method %s does not contain a subscriber annotation.", subMethod.toGenericString()));
        }
        SubscriberConfigProvider.SubscriberConfig config = null;
        if (SubscriberConfigProvider.class.isAssignableFrom(subscriber.getClass())) {
            config = ((SubscriberConfigProvider) subscriber).getConfigForName(annotation.name());
        }

        if (null == config) {
            config = new AnnotationBasedSubscriberConfig(annotation);
        }

        return config;
    }

    /**
     * Same as calling {@link #getSubscriberConfig(java.lang.reflect.Method, Object)} with
     * {@link com.netflix.eventbus.spi.SubscriberInfo#getSubscriberMethod()} and
     * {@link com.netflix.eventbus.spi.SubscriberInfo#getSubscriberInstance()}
     *
     * @param subscriberInfo The instance of the subscriber that contains the subscriber method.
     *
     * @return Subscriber configuration.
     */
    public static SubscriberConfigProvider.SubscriberConfig getSubscriberConfig(SubscriberInfo subscriberInfo) {
        return getSubscriberConfig(subscriberInfo.getSubscriberMethod(), subscriberInfo.getSubscriberInstance());
    }

    /**
     * Deduce whether the passed event is an event batch.
     *
     * @param event The event to inspect.
     *
     * @return true if the event is a batch.
     */
    public static boolean isAnEventBatch(Object event) {
        return EventBatch.class.isAssignableFrom(event.getClass());
    }

    /**
     * Returns the event class the passed subscriber is interested in. This will generally be the argument of the
     * subscriber method, except when the subscriber is a {@link DynamicSubscriber}, in which case the event type will
     * be as returned by {@link com.netflix.eventbus.spi.DynamicSubscriber#getEventType()}.
     * It is important that the subscriber method is valid as evaluated by
     * {@link com.netflix.eventbus.impl.SubscriberValidator}
     *
     * @param subscriber The subscriber instance in question.
     * @param subMethod The subscriber method is question.
     *
     * @return The event class this subscriber is interested in.
     */
    public static Class getInterestedEventType(Object subscriber, Method subMethod) {
        Class interestedEventType = (DynamicSubscriber.class.isAssignableFrom(subscriber.getClass()))
                          ? ((DynamicSubscriber) subscriber).getEventType()
                          : subMethod.getParameterTypes()[0];/* The subscriber method must be valid here. */
        Subscribe annotation = subMethod.getAnnotation(Subscribe.class);
        if (annotation.batchingStrategy() != Subscribe.BatchingStrategy.None
            && Iterable.class.isAssignableFrom(interestedEventType)) {
            // Batch consumer, the parameter type of Iterable is the actual event type.
            Type[] genericMethodParams = subMethod.getGenericParameterTypes();
            ParameterizedType interestedEventParam = (ParameterizedType) genericMethodParams[0]; // Validation ensures that the argument is generic Iterable.
            Type[] iterableTypeParams = interestedEventParam.getActualTypeArguments();
            // Now we have the generic parameter for the Iterable, which essentially is always 1.
            if (iterableTypeParams[0] instanceof ParameterizedType) {
                // This is the case where the Iterable paramter itself is a generic, eg:
                // Iterable>
                // in such a case, the iterableTypeParams will not be the class of the interested event type.
                return (Class) ((ParameterizedType)iterableTypeParams[0]).getRawType();
            } else {
                // The iterableTypeParams[0] itself is the class of the actual event.
                return (Class) iterableTypeParams[0];
            }
        } else {
            return interestedEventType;
        }
    }

    /**
     * Utility method to apply filters for an event, this can be used both by publisher & subscriber code.
     *
     * @param event The event to apply filter on.
     * @param filters Filters to apply.
     * @param filterStats Stats timer for applying the filter.
     * @param invokerDesc A string description for the invoker, this is required just for logging.
     * @param logger Logger instance to use for logging.
     *
     * @return true if the event should be processed, false if the event should not be
     * processed any further i.e. it is filtered out. This will log a debug message when the event is filtered.
     */
    public static boolean applyFilters(Object event, Set filters, StatsTimer filterStats,
                                       String invokerDesc, Logger logger) {
        if (filters.isEmpty()) {
            return true;
        }
        Stopwatch filterStart = filterStats.start();
        try {
            for (EventFilter filter : filters) {
                if (!filter.apply(event)) {
                    logger.debug(
                            "Event: " + event + " filtered out for : " + invokerDesc + " due to the filter: " + filter);
                    return false;
                }
            }
            return true;
        } finally {
            filterStart.stop();
        }
    }

    public static StatsTimer newStatsTimer(String monitorName, long collectionDurationInMillis) {
        return new StatsTimer(
                MonitorConfig.builder(monitorName).build(), 
                new StatsConfig.Builder()
                    .withComputeFrequencyMillis(collectionDurationInMillis)
                    .withPublishMean(true)
                    .withPublishMin(true)
                    .withPublishMax(true)
                    .withPublishStdDev(true)
                    .withPublishVariance(true)
                    .build());
    }

    private static class AnnotationBasedSubscriberConfig implements SubscriberConfigProvider.SubscriberConfig {

        private final Subscribe annotation;

        public AnnotationBasedSubscriberConfig(Subscribe annotation) {
            this.annotation = annotation;
        }

        @Override
        public Subscribe.BatchingStrategy getBatchingStrategy() {
            return annotation.batchingStrategy();
        }

        @Override
        public int getBatchAge() {
            return annotation.batchAge();
        }

        @Override
        public int getBatchSize() {
            return annotation.batchSize();
        }

        @Override
        public int getQueueSize() {
            return annotation.queueSize();
        }

        @Override
        public boolean syncIfAllowed() {
            return annotation.syncIfAllowed();
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy