package org.unbrokendome.eventbus.components; import com.google.common.collect.Multimap; import com.google.common.collect.Multimaps; import org.springframework.integration.channel.NullChannel; import org.springframework.integration.core.MessageSelector; import org.springframework.integration.dsl.IntegrationFlow; import org.springframework.integration.dsl.IntegrationFlows; import org.springframework.integration.dsl.RecipientListRouterSpec; import org.springframework.integration.dsl.core.PollerSpec; import org.springframework.integration.dsl.core.Pollers; import org.springframework.integration.selector.MessageSelectorChain; import org.springframework.integration.store.ChannelMessageStore; import org.springframework.integration.store.SimpleMessageStore; import org.springframework.messaging.MessageChannel; import org.springframework.transaction.PlatformTransactionManager; import org.unbrokendome.eventbus.EventSubscriber; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Set; import java.util.stream.Collectors; public class EventBusBuilderImpl implements EventBusBuilder { private static final String DEFAULT_NAME = "eventBus"; private static final PollerSpec DEFAULT_EVENT_POLLER = Pollers.fixedRate(10).receiveTimeout(30000); private final MessageChannel inputChannel; private String name; private ChannelMessageStore messageStore; private PlatformTransactionManager transactionManager; private final List<EventSubscriber> subscribers = new ArrayList<>(); private PollerSpec eventPoller = Pollers.fixedDelay(1000); public EventBusBuilderImpl(MessageChannel inputChannel) { this.inputChannel = inputChannel; } @Override public EventBusBuilder setName(String name) { this.name = name; return this; } @Override public EventBusBuilder setMessageStore(ChannelMessageStore messageStore) { this.messageStore = messageStore; return this; } @Override public EventBusBuilder setTransactionManager(PlatformTransactionManager transactionManager) { this.transactionManager = transactionManager; return this; } @Override public EventBusBuilder addSubscribers(Iterable<? extends EventSubscriber> subscribers) { for (EventSubscriber subscriber : subscribers) { this.subscribers.add(subscriber); } return this; } @Override public EventBusBuilder setEventPoller(PollerSpec eventPoller) { this.eventPoller = eventPoller; return this; } private void applyDefaultsToMissingProperties() { if (name == null) { name = DEFAULT_NAME; } if (messageStore == null) { messageStore = new SimpleMessageStore(); } if (eventPoller == null) { eventPoller = DEFAULT_EVENT_POLLER; } } @Override public IntegrationFlow build() { applyDefaultsToMissingProperties(); if (subscribers.isEmpty()) { return f -> f .channel(new NullChannel()); } if (transactionManager != null) { eventPoller.transactional(transactionManager); } SyncSubscriberSet syncSubscribers = getSyncSubscribers(); AsyncSubscriberSet asyncSubscribers = getAsyncSubscribers(); return IntegrationFlows.from(inputChannel) .routeToRecipients( router -> { asyncSubscribers.configureFlow(router); syncSubscribers.configureFlow(router); }, spec -> spec.id(name + "_syncAsyncRouter")) .get(); } private SyncSubscriberSet getSyncSubscribers() { return new SyncSubscriberSet( subscribers.stream() .filter(s -> !s.isAsync()) .collect(Collectors.toList())); } private AsyncSubscriberSet getAsyncSubscribers() { return new AsyncSubscriberSet( subscribers.stream() .filter(EventSubscriber::isAsync) .collect(Collectors.toList())); } private abstract class SubscriberSet { private final Multimap<Class<?>, EventSubscriber> subscribersByEventType; public SubscriberSet(Collection<EventSubscriber> subscribers) { this.subscribersByEventType = Multimaps.index(subscribers, EventSubscriber::getEventType); } protected abstract String getMode(); public Set<Class<?>> getEventTypes() { return subscribersByEventType.keySet(); } public boolean isEmpty() { return subscribersByEventType.isEmpty(); } public MessageSelector createMessageSelector() { MessageSelectorChain chain = new MessageSelectorChain(); chain.setSelectors( getEventTypes().stream() .map(EventTypeMessageSelector::new) .collect(Collectors.toList())); chain.setVotingStrategy(MessageSelectorChain.VotingStrategy.ANY); return chain; } protected final IntegrationFlow createFlowForEventType(Class<?> eventType) { return f -> f .filter(p -> eventType.isAssignableFrom(p.getClass()), spec -> spec.id(name + "_" + getMode() + "EventTypeFilter_" + eventType.getName())) .publishSubscribeChannel(null, pubsub -> { pubsub.id(name + "_pubsub_" + getMode() + "Event_" + eventType.getName()); subscribersByEventType.get(eventType).stream() .map(this::createSubscriptionFlow) .forEach(pubsub::subscribe); }); } private IntegrationFlow createSubscriptionFlow(EventSubscriber subscriber) { return f -> f .handle(subscriber, spec -> spec.id(name + "_" + getMode() + "SubscriptionHandler_" + subscriber.hashCode())); } } private class SyncSubscriberSet extends SubscriberSet { public SyncSubscriberSet(Collection<EventSubscriber> subscribers) { super(subscribers); } @Override protected String getMode() { return "sync"; } public IntegrationFlow createEventsFlow() { return f -> f .publishSubscribeChannel(pubsub -> { pubsub.id(name + "_pubsub_allSyncEvents"); getEventTypes().stream() .map(this::createFlowForEventType) .forEach(pubsub::subscribe); }); } public void configureFlow(RecipientListRouterSpec router) { if (!isEmpty()) { router.recipientFlow( createMessageSelector(), createEventsFlow()); } } } private class AsyncSubscriberSet extends SubscriberSet { public AsyncSubscriberSet(Collection<EventSubscriber> subscribers) { super(subscribers); } @Override protected String getMode() { return "async"; } public IntegrationFlow createEventsFlow() { return f -> f .channel(ch -> ch.queue( name + "_asyncEventQueue", messageStore, name + ":AsyncEventQueue")) .bridge(spec -> spec.poller(eventPoller) .id(name + "_asyncPollingBridge")) .publishSubscribeChannel(pubsub -> { pubsub.id(name + "_pubsub_allAsyncEvents"); getEventTypes().stream() .map(this::createFlowForEventType) .forEach(pubsub::subscribe); }); } public void configureFlow(RecipientListRouterSpec router) { if (!isEmpty()) { router.recipientFlow( createMessageSelector(), createEventsFlow()); } } } }