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

reactor.bus.EventBus Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2011-2014 Pivotal Software, Inc.
 *
 *  Licensed 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 reactor.bus;

import org.reactivestreams.Publisher;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import reactor.Environment;
import reactor.bus.filter.PassThroughFilter;
import reactor.bus.publisher.BusPublisher;
import reactor.bus.registry.CachingRegistry;
import reactor.bus.registry.Registration;
import reactor.bus.registry.Registries;
import reactor.bus.registry.Registry;
import reactor.bus.routing.ConsumerFilteringRouter;
import reactor.bus.routing.Router;
import reactor.bus.selector.ClassSelector;
import reactor.bus.selector.Selector;
import reactor.bus.selector.Selectors;
import reactor.bus.spec.EventBusSpec;
import reactor.core.Dispatcher;
import reactor.core.dispatch.SynchronousDispatcher;
import reactor.core.support.Assert;
import reactor.core.support.UUIDUtils;
import reactor.fn.Consumer;
import reactor.fn.Function;
import reactor.fn.Supplier;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
import java.util.UUID;

/**
 * A reactor is an event gateway that allows other components to register {@link Event} {@link Consumer}s that can
 * subsequently be notified of events. A consumer is typically registered with a {@link Selector} which, by matching on
 * the notification key, governs which events the consumer will receive. 

When a {@literal Reactor} is notified of * an {@link Event}, a task is dispatched using the reactor's {@link Dispatcher} which causes it to be executed on a * thread based on the implementation of the {@link Dispatcher} being used. * * @author Jon Brisbin * @author Stephane Maldini * @author Andy Wilkinson */ @SuppressWarnings({"unchecked", "rawtypes"}) public class EventBus implements Bus>, Consumer> { private static final Router DEFAULT_EVENT_ROUTER = new ConsumerFilteringRouter( new PassThroughFilter() ); private final Dispatcher dispatcher; private final Registry>> consumerRegistry; private final Router router; private final Consumer dispatchErrorHandler; private final Consumer uncaughtErrorHandler; private volatile UUID id; /** * Create a new {@link reactor.bus.spec.EventBusSpec} to configure a Reactor. * * @return The Reactor spec */ public static EventBusSpec config() { return new EventBusSpec(); } /** * Create a new synchronous {@link EventBus} * * @return A new {@link EventBus} */ public static EventBus create() { return new EventBus(SynchronousDispatcher.INSTANCE); } /** * Create a new {@link EventBus} using the given {@link reactor.Environment}. * * @param env The {@link reactor.Environment} to use. * @return A new {@link EventBus} */ public static EventBus create(Environment env) { return new EventBusSpec().env(env).dispatcher(env.getDefaultDispatcher()).get(); } /** * Create a new {@link EventBus} using the given {@link reactor.core.Dispatcher}. * * @param dispatcher The name of the {@link reactor.core.Dispatcher} to use. * @return A new {@link EventBus} */ public static EventBus create(Dispatcher dispatcher) { return new EventBusSpec().dispatcher(dispatcher).get(); } /** * Create a new {@link EventBus} using the given {@link reactor.Environment} and dispatcher name. * * @param env The {@link reactor.Environment} to use. * @param dispatcher The name of the {@link reactor.core.Dispatcher} to use. * @return A new {@link EventBus} */ public static EventBus create(Environment env, String dispatcher) { return new EventBusSpec().env(env).dispatcher(dispatcher).get(); } /** * Create a new {@link EventBus} using the given {@link reactor.Environment} and {@link * reactor.core.Dispatcher}. * * @param env The {@link reactor.Environment} to use. * @param dispatcher The {@link reactor.core.Dispatcher} to use. * @return A new {@link EventBus} */ public static EventBus create(Environment env, Dispatcher dispatcher) { return new EventBusSpec().env(env).dispatcher(dispatcher).get(); } /** * Create a new {@literal Reactor} that uses the given {@link Dispatcher}. The reactor will use a default {@link * reactor.bus.routing.Router} that broadcast events to all of the registered consumers that {@link * Selector#matches(Object) match} * the notification key and does not perform any type conversion. * * @param dispatcher The {@link Dispatcher} to use. May be {@code null} in which case a new {@link * SynchronousDispatcher} is used */ public EventBus(@Nullable Dispatcher dispatcher) { this(dispatcher, null); } /** * Create a new {@literal Reactor} that uses the given {@link Dispatcher}. The reactor will use a default {@link * CachingRegistry}. * * @param dispatcher The {@link Dispatcher} to use. May be {@code null} in which case a new synchronous dispatcher * is used. * @param router The {@link Router} used to route events to {@link Consumer Consumers}. May be {@code null} in * which case the * default event router that broadcasts events to all of the registered consumers that {@link * Selector#matches(Object) match} the notification key and does not perform any type conversion * will be used. */ public EventBus(@Nullable Dispatcher dispatcher, @Nullable Router router) { this(dispatcher, router, null, null); } public EventBus(@Nullable Dispatcher dispatcher, @Nullable Router router, @Nullable Consumer dispatchErrorHandler, @Nullable final Consumer uncaughtErrorHandler) { this(Registries.>>create(), dispatcher, router, dispatchErrorHandler, uncaughtErrorHandler); } /** * Create a new {@literal Reactor} that uses the given {@code dispatacher} and {@code eventRouter}. * * @param dispatcher The {@link Dispatcher} to use. May be {@code null} in which case a new synchronous * dispatcher is used. * @param router The {@link Router} used to route events to {@link Consumer Consumers}. May be {@code * null} in which case the * default event router that broadcasts events to all of the registered consumers that {@link * Selector#matches(Object) match} the notification key and does not perform any type * conversion will be used. * @param consumerRegistry The {@link Registry} to be used to match {@link Selector} and dispatch to {@link * Consumer}. */ public EventBus(@Nonnull Registry>> consumerRegistry, @Nullable Dispatcher dispatcher, @Nullable Router router, @Nullable Consumer dispatchErrorHandler, @Nullable final Consumer uncaughtErrorHandler) { Assert.notNull(consumerRegistry, "Consumer Registry cannot be null."); this.consumerRegistry = consumerRegistry; this.dispatcher = (null == dispatcher ? SynchronousDispatcher.INSTANCE : dispatcher); this.router = (null == router ? DEFAULT_EVENT_ROUTER : router); if (null == dispatchErrorHandler) { this.dispatchErrorHandler = new Consumer() { @Override public void accept(Throwable t) { Class type = t.getClass(); EventBus.this.router.route(type, Event.wrap(t), EventBus.this.consumerRegistry.select(type), null, null); } }; } else { this.dispatchErrorHandler = dispatchErrorHandler; } this.uncaughtErrorHandler = uncaughtErrorHandler; this.on(new ClassSelector(Throwable.class), new Consumer>() { Logger log; @Override public void accept(Event ev) { if (null == uncaughtErrorHandler) { if (null == log) { log = LoggerFactory.getLogger(EventBus.class); } log.error(ev.getData().getMessage(), ev.getData()); } else { uncaughtErrorHandler.accept(ev.getData()); } } }); } /** * Get the unique, time-used {@link UUID} of this {@literal Reactor}. * * @return The {@link UUID} of this {@literal Reactor}. */ public synchronized UUID getId() { if (null == id) { id = UUIDUtils.create(); } return id; } /** * Get the {@link Registry} is use to maintain the {@link Consumer}s currently listening for events on this {@literal * Reactor}. * * @return The {@link Registry} in use. */ public Registry>> getConsumerRegistry() { return consumerRegistry; } /** * Get the {@link Dispatcher} currently in use. * * @return The {@link Dispatcher}. */ public Dispatcher getDispatcher() { return dispatcher; } /** * Get the {@link reactor.bus.routing.Router} used to route events to {@link Consumer Consumers}. * * @return The {@link reactor.bus.routing.Router}. */ public Router getRouter() { return router; } public Consumer getDispatchErrorHandler() { return dispatchErrorHandler; } public Consumer getUncaughtErrorHandler() { return uncaughtErrorHandler; } @Override public boolean respondsToKey(Object key) { List>>> registrations = consumerRegistry.select(key); if (registrations.isEmpty()) return false; for (Registration reg : registrations) { if (!reg.isCancelled()) { return true; } } return false; } @Override public > Registration>> on(final Selector selector, final Consumer consumer) { Assert.notNull(selector, "Selector cannot be null."); Assert.notNull(consumer, "Consumer cannot be null."); final Class tClass = extractGeneric(consumer); Consumer proxyConsumer = new Consumer() { @Override public void accept(T e) { if (null != selector.getHeaderResolver()) { e.getHeaders().setAll(selector.getHeaderResolver().resolve(e.getKey())); } if (tClass == null || e.getData() == null || tClass.isAssignableFrom(e.getData().getClass())) { consumer.accept(e); } } }; return consumerRegistry.register(selector, proxyConsumer); } private Class extractGeneric(Consumer> consumer) { if(consumer.getClass().getGenericInterfaces().length == 0) return null; Type t = consumer.getClass().getGenericInterfaces()[0]; if (ParameterizedType.class.isAssignableFrom(t.getClass())) { ParameterizedType pt = (ParameterizedType) t; if(pt.getActualTypeArguments().length == 0) return null; t = pt.getActualTypeArguments()[0]; if (ParameterizedType.class.isAssignableFrom(t.getClass())) { pt = (ParameterizedType) t; if(pt.getActualTypeArguments().length == 0) return null; Type t1 = pt.getActualTypeArguments()[0]; if (t1 instanceof ParameterizedType) { return (Class) ((ParameterizedType) t1).getRawType(); } else if (t1 instanceof Class) { return (Class) t1; } } } return null; } /** * Attach a Publisher to the {@link Bus} with the specified {@link Selector}. * * @param broadcastSelector the {@link Selector}/{@literal Object} tuple to listen to * @return a new {@link Publisher} * @since 2.0 */ public Publisher> on(Selector broadcastSelector) { return new BusPublisher<>(this, broadcastSelector); } @Override public EventBus notify(Object key, Event ev) { Assert.notNull(key, "Key cannot be null."); Assert.notNull(ev, "Event cannot be null."); ev.setKey(key); dispatcher.dispatch(ev, this, dispatchErrorHandler); return this; } /** * Pass values accepted by this {@code Stream} into the given {@link Bus}, notifying with the given key. * * @param key the key to notify on * @param source the {@link Publisher} to consume * @return {@literal new Stream} * @since 1.1, 2.0 */ public final EventBus notify(@Nonnull final Publisher source, @Nonnull final Object key) { return notify(source, new Function(){ @Override public Object apply(Object o) { return key; } }); } /** * Pass values accepted by this {@code Stream} into the given {@link Bus}, notifying with the given key. * * @param source the {@link Publisher} to consume * @param keyMapper the key function mapping each incoming data to a key to notify on * @return {@literal new Stream} * @since 2.0 */ public final EventBus notify(@Nonnull final Publisher source, @Nonnull final Function keyMapper) { source.subscribe(new Subscriber() { Subscription s; @Override public void onSubscribe(Subscription s) { this.s = s; s.request(Long.MAX_VALUE); } @Override public void onNext(T t) { EventBus.this.notify(keyMapper.apply(t), Event.wrap(t)); } @Override public void onError(Throwable t) { if(s != null) s.cancel(); } @Override public void onComplete() { if(s != null) s.cancel(); } }); return this; } /** * Assign a {@link reactor.fn.Function} to receive an {@link Event} and produce a reply of the given type. * * @param sel The {@link Selector} to be used for matching * @param fn The transformative {@link reactor.fn.Function} to call to receive an {@link Event} * @return A {@link Registration} object that allows the caller to interact with the given mapping */ public , V> Registration>> receive(Selector sel, Function fn) { return on(sel, new ReplyToConsumer<>(fn)); } /** * Notify this component that the given {@link reactor.fn.Supplier} can provide an event that's ready to be * processed. * * @param key The key to be matched by {@link Selector Selectors} * @param supplier The {@link reactor.fn.Supplier} that will provide the actual {@link Event} * @return {@literal this} */ public EventBus notify(Object key, Supplier> supplier) { return notify(key, supplier.get()); } /** * Notify this component that the consumers registered with a {@link Selector} that matches the {@code key} should be * triggered with a {@literal null} input argument. * * @param key The key to be matched by {@link Selector Selectors} * @return {@literal this} */ public EventBus notify(Object key) { return notify(key, new Event<>(Void.class)); } /** * Notify this component of the given {@link Event} and register an internal {@link Consumer} that will take the * output of a previously-registered {@link Function} and respond using the key set on the {@link Event}'s {@literal * replyTo} property. * * @param key The key to be matched by {@link Selector Selectors} * @param ev The {@literal Event} * @return {@literal this} */ public EventBus send(Object key, Event ev) { return notify(key, new ReplyToEvent(ev, this)); } /** * Notify this component that the given {@link Supplier} will provide an {@link Event} and register an internal * {@link * Consumer} that will take the output of a previously-registered {@link Function} and respond using the key set on * the {@link Event}'s {@literal replyTo} property. * * @param key The key to be matched by {@link Selector Selectors} * @param supplier The {@link Supplier} that will provide the actual {@link Event} instance * @return {@literal this} */ public EventBus send(Object key, Supplier> supplier) { return notify(key, new ReplyToEvent(supplier.get(), this)); } /** * Notify this component of the given {@link Event} and register an internal {@link Consumer} that will take the * output of a previously-registered {@link Function} and respond to the key set on the {@link Event}'s {@literal * replyTo} property and will call the {@code notify} method on the given {@link Bus}. * * @param key The key to be matched by {@link Selector Selectors} * @param ev The {@literal Event} * @param replyTo The {@link Bus} on which to invoke the notify method * @return {@literal this} */ public EventBus send(Object key, Event ev, Bus replyTo) { return notify(key, new ReplyToEvent(ev, replyTo)); } /** * Notify this component that the given {@link Supplier} will provide an {@link Event} and register an internal * {@link * Consumer} that will take the output of a previously-registered {@link Function} and respond to the key set on the * {@link Event}'s {@literal replyTo} property and will call the {@code notify} method on the given {@link * Bus}. * * @param key The key to be matched by {@link Selector Selectors} * @param supplier The {@link Supplier} that will provide the actual {@link Event} instance * @param replyTo The {@link Bus} on which to invoke the notify method * @return {@literal this} */ public EventBus send(Object key, Supplier> supplier, Bus replyTo) { return notify(key, new ReplyToEvent(supplier.get(), replyTo)); } /** * Register the given {@link reactor.fn.Consumer} on an anonymous {@link reactor.bus.selector.Selector} and * set the given event's {@code replyTo} property to the corresponding anonymous key, then register the consumer to * receive replies from the {@link reactor.fn.Function} assigned to handle the given key. * * @param key The key to be matched by {@link Selector Selectors} * @param event The event to notify. * @param reply The consumer to register as a reply handler. * @return {@literal this} */ public > EventBus sendAndReceive(Object key, Event event, Consumer reply) { Selector sel = Selectors.anonymous(); on(sel, reply).cancelAfterUse(); notify(key, event.setReplyTo(sel.getObject())); return this; } /** * Register the given {@link reactor.fn.Consumer} on an anonymous {@link reactor.bus.selector.Selector} and * set the event's {@code replyTo} property to the corresponding anonymous key, then register the consumer to receive * replies from the {@link reactor.fn.Function} assigned to handle the given key. * * @param key The key to be matched by {@link Selector Selectors} * @param supplier The supplier to supply the event. * @param reply The consumer to register as a reply handler. * @return {@literal this} */ public > EventBus sendAndReceive(Object key, Supplier> supplier, Consumer reply) { return sendAndReceive(key, supplier.get(), reply); } /** * Create an optimized path for publishing notifications to the given key. * * @param key The key to be matched by {@link Selector Selectors} * @return a {@link Consumer} to invoke with the {@link Event Events} to publish */ public Consumer> prepare(final Object key) { return new Consumer>() { final List>>> regs = consumerRegistry.select(key); final int size = regs.size(); @Override public void accept(Event ev) { for (int i = 0; i < size; i++) { Registration>> reg = (Registration>>) regs.get(i); ev.setKey(key); dispatcher.dispatch(ev, reg.getObject(), dispatchErrorHandler); } } }; } /** * Schedule an arbitrary {@link reactor.fn.Consumer} to be executed on the current Reactor {@link * reactor.core.Dispatcher}, passing the given {@param data}. * * @param consumer The {@link reactor.fn.Consumer} to invoke. * @param data The data to pass to the consumer. * @param The type of the data. */ public void schedule(final Consumer consumer, final T data) { dispatcher.dispatch(null, new Consumer>() { @Override public void accept(Event event) { consumer.accept(data); } }, dispatchErrorHandler); } @Override public void accept(Event event) { router.route(event.getKey(), event, consumerRegistry.select(event.getKey()), null, dispatchErrorHandler); } public static class ReplyToEvent extends Event { private static final long serialVersionUID = 1937884784799135647L; private final Bus replyToObservable; private ReplyToEvent(Headers headers, T data, Object replyTo, Bus replyToObservable, Consumer errorConsumer) { super(headers, data, errorConsumer); setReplyTo(replyTo); this.replyToObservable = replyToObservable; } private ReplyToEvent(Event delegate, Bus replyToObservable) { this(delegate.getHeaders(), delegate.getData(), delegate.getReplyTo(), replyToObservable, delegate.getErrorConsumer()); } @Override public Event copy(X data) { return new ReplyToEvent(getHeaders(), data, getReplyTo(), replyToObservable, getErrorConsumer()); } public Bus getReplyToObservable() { return replyToObservable; } } public class ReplyToConsumer, V> implements Consumer { private final Function fn; private ReplyToConsumer(Function fn) { this.fn = fn; } @Override public void accept(E ev) { Bus replyToObservable = EventBus.this; if (ReplyToEvent.class.isAssignableFrom(ev.getClass())) { Bus o = ((ReplyToEvent) ev).getReplyToObservable(); if (null != o) { replyToObservable = o; } } try { V reply = fn.apply(ev); Event replyEv; if (null == reply) { replyEv = new Event(Void.class); } else { replyEv = (Event.class.isAssignableFrom(reply.getClass()) ? (Event) reply : Event.wrap(reply)); } replyToObservable.notify(ev.getReplyTo(), replyEv); } catch (Throwable x) { replyToObservable.notify(x.getClass(), Event.wrap(x)); } } public Function getDelegate() { return fn; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy