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 (nkant@netflix.com)
*/
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 <code>subscribe</code> 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 <code>true</code> 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()}.
* <b>It is important that the subscriber method is valid as evaluated by
* {@link com.netflix.eventbus.impl.SubscriberValidator}</b>
*
* @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<List<String>>
// 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 <code>true</code> if the event should be processed, <code>false</code> 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<EventFilter> 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();
}
}
}