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;
}
}
}
}
}