package org.zaproxy.zap.eventBus; import java.security.InvalidParameterException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import org.apache.log4j.Logger; /** * A very simple event bus * @author simon * */ public class SimpleEventBus implements EventBus { private Map<String, RegisteredPublisher> nameToPublisher = new HashMap<String, RegisteredPublisher>(); private List<RegisteredConsumer> danglingConsumers = new ArrayList<RegisteredConsumer>(); /** * The read/write {@code Lock} for registration management and publishing of events. * * @see #regMgmtLock * @see #publishLock */ private final ReadWriteLock rwLock = new ReentrantReadWriteLock(true); /** * The {@code Lock} for registration management (register and unregister) of publishers and consumers. * * @see #publishLock */ private final Lock regMgmtLock = rwLock.writeLock(); /** * The {@code Lock} for publishing of events. * * @see #regMgmtLock */ private final Lock publishLock = rwLock.readLock(); private static Logger log = Logger.getLogger(SimpleEventBus.class); @Override public void registerPublisher(EventPublisher publisher, String[] eventTypes) { if (publisher == null) { throw new InvalidParameterException("Publisher must not be null"); } if (eventTypes == null || eventTypes.length == 0) { throw new InvalidParameterException("At least one event type must be specified"); } regMgmtLock.lock(); try { if (this.nameToPublisher.get(publisher.getPublisherName()) != null) { throw new InvalidParameterException("Publisher with name " + publisher.getPublisherName() + " already registered by " + this.nameToPublisher.get(publisher.getPublisherName()) .getPublisher().getClass().getCanonicalName()); } log.debug("registerPublisher " + publisher.getPublisherName()); RegisteredPublisher regProd = new RegisteredPublisher(publisher, eventTypes); this.nameToPublisher.put(publisher.getPublisherName(), regProd); // Check to see if there are any cached consumers for (RegisteredConsumer regCon : this.danglingConsumers) { if (regCon.getPublisherName().equals(publisher.getPublisherName())) { regProd.addComsumer(regCon); this.danglingConsumers.remove(regCon); break; } } } finally { regMgmtLock.unlock(); } } @Override public void unregisterPublisher(EventPublisher publisher) { if (publisher == null) { throw new InvalidParameterException("Publisher must not be null"); } regMgmtLock.lock(); try { log.debug("unregisterPublisher " + publisher.getPublisherName()); RegisteredPublisher regPub = nameToPublisher.remove(publisher.getPublisherName()); if (regPub == null) { throw new InvalidParameterException("Publisher with name " + publisher.getPublisherName() + " not registered"); } } finally { regMgmtLock.unlock(); } } @Override public void registerConsumer(EventConsumer consumer, String publisherName) { this.registerConsumer(consumer, publisherName, null); } @Override public void registerConsumer(EventConsumer consumer, String publisherName, String[] eventTypes) { if (consumer == null) { throw new InvalidParameterException("Consumer must not be null"); } regMgmtLock.lock(); try { log.debug("registerConsumer " + consumer.getClass().getCanonicalName() + " for " + publisherName); RegisteredPublisher publisher = this.nameToPublisher.get(publisherName); if (publisher == null) { // Cache until the publisher registers this.danglingConsumers.add(new RegisteredConsumer(consumer, eventTypes, publisherName)); } else { publisher.addComsumer(consumer, eventTypes); } } finally { regMgmtLock.unlock(); } } @Override public void unregisterConsumer(EventConsumer consumer, String publisherName) { if (consumer == null) { throw new InvalidParameterException("Consumer must not be null"); } regMgmtLock.lock(); try { log.debug("unregisterConsumer " + consumer.getClass().getCanonicalName() + " for " + publisherName); RegisteredPublisher publisher = this.nameToPublisher.get(publisherName); if (publisher != null) { publisher.removeComsumer(consumer); } else { // Check to see if its cached waiting for the publisher for (RegisteredConsumer regCon : this.danglingConsumers) { if (regCon.getConsumer().equals(consumer)) { this.danglingConsumers.remove(regCon); break; } } } } finally { regMgmtLock.unlock(); } } @Override public void publishSyncEvent(EventPublisher publisher, Event event) { if (publisher == null) { throw new InvalidParameterException("Publisher must not be null"); } publishLock.lock(); try { RegisteredPublisher regPublisher = this.nameToPublisher.get(publisher.getPublisherName()); if (regPublisher == null) { throw new InvalidParameterException("Publisher not registered: " + publisher.getPublisherName()); } log.debug("publishSyncEvent " + event.getEventType() + " from " + publisher.getPublisherName()); boolean foundType = false; for (String type : regPublisher.getEventTypes()) { if (event.getEventType().equals(type)) { foundType = true; break; } } if (! foundType) { throw new InvalidParameterException("Event type: " + event.getEventType() + " not registered for publisher: " + publisher.getPublisherName()); } for (RegisteredConsumer regCon : regPublisher.getConsumers()) { String[] eventTypes = regCon.getEventTypes(); boolean isListeningforEvent = false; if (eventTypes == null) { // They are listening for all events from this publisher isListeningforEvent = true; } else { for (String type : eventTypes) { if (event.getEventType().equals(type)) { isListeningforEvent = true; break; } } } if (isListeningforEvent) { try { regCon.getConsumer().eventReceived(event); } catch (Exception e) { log.error(e.getMessage(), e); } } } }finally { publishLock.unlock(); } } private static class RegisteredConsumer { private EventConsumer consumer; private String[] eventTypes; private String publisherName; public RegisteredConsumer(EventConsumer consumer, String[] eventTypes) { this.consumer = consumer; this.eventTypes = eventTypes; } public RegisteredConsumer(EventConsumer consumer, String[] eventTypes, String publisherName) { this.consumer = consumer; this.eventTypes = eventTypes; this.publisherName = publisherName; } public EventConsumer getConsumer() { return consumer; } public String[] getEventTypes() { return eventTypes; } public String getPublisherName() { return publisherName; } } private static class RegisteredPublisher { private EventPublisher publisher; private String[] eventTypes; private List<RegisteredConsumer> consumers = new ArrayList<RegisteredConsumer>(); public RegisteredPublisher(EventPublisher publisher, String[] eventTypes) { super(); this.publisher = publisher; this.eventTypes = eventTypes; } public EventPublisher getPublisher() { return publisher; } public String[] getEventTypes() { return eventTypes; } public List<RegisteredConsumer> getConsumers() { return consumers; } public void addComsumer(RegisteredConsumer consumer) { this.consumers.add(consumer); } public void addComsumer(EventConsumer consumer, String [] eventTypes) { this.consumers.add(new RegisteredConsumer(consumer, eventTypes)); } public void removeComsumer(EventConsumer consumer) { for (RegisteredConsumer cons : consumers) { if (cons.getConsumer().equals(consumer)) { this.consumers.remove(cons); return; } } } } }