/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.event;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import org.apache.commons.pool.KeyedObjectPool;
import org.apache.commons.pool.KeyedPoolableObjectFactory;
import org.apache.commons.pool.PoolUtils;
import org.apache.commons.pool.impl.GenericKeyedObjectPool;
import org.apache.log4j.Logger;
import org.dspace.core.ConfigurationManager;
import org.dspace.core.Context;
/**
* Class for managing the content event environment. The EventManager mainly
* acts as a factory for Dispatchers, which are used by the Context to send
* events to consumers. It also contains generally useful utility methods.
*
* Version: $Revision$
*/
public class EventManager
{
/** log4j category */
private static Logger log = Logger.getLogger(EventManager.class);
// The name of the default dispatcher assigned to every new context unless
// overridden
public static final String DEFAULT_DISPATCHER = "default";
private static DispatcherPoolFactory dispatcherFactory = null;
private static GenericKeyedObjectPool.Config poolConfig = null;
// Keyed FIFO Pool of event dispatchers
private static KeyedObjectPool dispatcherPool = null;
private static Map<String, Integer> consumerIndicies = null;
private static final String CONSUMER_PFX = "event.consumer.";
public EventManager()
{
initPool();
log.info("Event Dispatcher Pool Initialized");
}
private static void initPool()
{
if (dispatcherPool == null)
{
// TODO EVENT Some of these pool configuration
// parameters can live in dspace.cfg or a
// separate configuration file
// TODO EVENT Eviction parameters should be set
poolConfig = new GenericKeyedObjectPool.Config();
poolConfig.maxActive = 100;
poolConfig.maxIdle = 5;
poolConfig.maxTotal = 100;
try
{
dispatcherFactory = new DispatcherPoolFactory();
dispatcherPool = PoolUtils
.synchronizedPool(new GenericKeyedObjectPool(
dispatcherFactory, poolConfig));
enumerateConsumers();
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
/**
* Get dispatcher for configuration named by "name". Returns cached instance
* if one exists.
*/
public static Dispatcher getDispatcher(String name)
{
if (dispatcherPool == null)
{
initPool();
}
if (name == null)
{
name = DEFAULT_DISPATCHER;
}
try
{
return (Dispatcher) dispatcherPool.borrowObject(name);
}
catch (Exception e)
{
throw new IllegalStateException("Unable to aquire dispatcher named " + name, e);
}
}
public static void returnDispatcher(String key, Dispatcher disp)
{
try
{
dispatcherPool.returnObject(key, disp);
}
catch (Exception e)
{
log.error(e.getMessage(), e);
}
}
protected static int getConsumerIndex(String consumerClass)
{
Integer index = (Integer) consumerIndicies.get(consumerClass);
return index != null ? index.intValue() : -1;
}
private static void enumerateConsumers()
{
Enumeration propertyNames = ConfigurationManager.propertyNames();
int bitSetIndex = 0;
if (consumerIndicies == null)
{
consumerIndicies = new HashMap<String, Integer>();
}
while (propertyNames.hasMoreElements())
{
String ckey = ((String) propertyNames.nextElement()).trim();
if (ckey.startsWith(CONSUMER_PFX) && ckey.endsWith(".class"))
{
String consumerName = ckey.substring(CONSUMER_PFX.length(),
ckey.length() - 6);
consumerIndicies.put(consumerName, (Integer) bitSetIndex);
bitSetIndex++;
}
}
}
static class DispatcherPoolFactory implements KeyedPoolableObjectFactory
{
// Prefix of keys in DSpace Configuration
private static final String PROP_PFX = "event.dispatcher.";
// Cache of event dispatchers, keyed by name, for re-use.
private static Map<String, String> dispatchers = new HashMap<String, String>();
public DispatcherPoolFactory()
{
parseEventConfig();
log.info("");
}
public Object makeObject(Object dispatcherName) throws Exception
{
Dispatcher dispatcher = null;
String dispClass = dispatchers.get(dispatcherName);
if (dispClass != null)
{
try
{
// all this to call a constructor with an argument
final Class argTypes[] = { String.class };
Constructor dc = Class.forName(dispClass).getConstructor(
argTypes);
Object args[] = new Object[1];
args[0] = dispatcherName;
dispatcher = (Dispatcher) dc.newInstance(args);
// OK, now get its list of consumers/filters
String consumerKey = PROP_PFX + dispatcherName
+ ".consumers";
String consumerList = ConfigurationManager
.getProperty(consumerKey);
if (consumerList == null)
{
throw new IllegalStateException(
"No Configuration entry found for consumer list of event Dispatcher: \""
+ consumerKey + "\"");
}
// Consumer list format:
// <consumer-name>:<mode>, ...
String[] consumerStanza = consumerList.trim().split(
"\\s*,\\s*");
// I think this should be a fatal error.. --lcs
if (consumerStanza.length < 1)
{
throw new IllegalStateException(
"Cannot initialize Dispatcher, malformed Configuration value for "
+ consumerKey);
}
ConsumerProfile consumerProfile = null;
// parts: 0 is name, part 1 is mode.
for (int i = 0; i < consumerStanza.length; i++)
{
consumerProfile = ConsumerProfile
.makeConsumerProfile(consumerStanza[i]);
consumerProfile.getConsumer().initialize();
dispatcher.addConsumerProfile(consumerProfile);
}
}
catch (NoSuchMethodException e)
{
throw new IllegalStateException(
"Constructor not found for event dispatcher="
+ dispatcherName, e);
}
catch (InvocationTargetException e)
{
throw new IllegalStateException(
"Error creating event dispatcher=" + dispatcherName,
e);
}
catch (ClassNotFoundException e)
{
throw new IllegalStateException(
"Dispatcher/Consumer class not found for event dispatcher="
+ dispatcherName, e);
}
catch (InstantiationException e)
{
throw new IllegalStateException(
"Dispatcher/Consumer instantiation failure for event dispatcher="
+ dispatcherName, e);
}
catch (IllegalAccessException e)
{
throw new IllegalStateException(
"Dispatcher/Consumer access failure for event dispatcher="
+ dispatcherName, e);
}
}
else
{
throw new IllegalStateException(
"Requested Dispatcher Does Not Exist In DSpace Configuration!");
}
return dispatcher;
}
public void activateObject(Object arg0, Object arg1) throws Exception
{
// No-op
return;
}
public void destroyObject(Object key, Object dispatcher)
throws Exception
{
Context ctx = new Context();
for (Iterator ci = ((Dispatcher) dispatcher).getConsumers()
.iterator(); ci.hasNext();)
{
ConsumerProfile cp = (ConsumerProfile) ci.next();
if (cp != null)
{
cp.getConsumer().finish(ctx);
}
}
return;
}
public void passivateObject(Object arg0, Object arg1) throws Exception
{
// No-op
return;
}
public boolean validateObject(Object arg0, Object arg1)
{
// No-op
return false;
}
/**
* Looks through the configuration for dispatcher configurations and
* loads one of each into a HashMap. This Map will be used to clone new
* objects when the pool needs them.
*
* Looks for configuration properties like:
*
* <pre>
* # class of dispatcher "default"
* event.dispatcher.default = org.dspace.event.BasicDispatcher
* # list of consumers followed by filters for each, format is
* # <consumerClass>:<filter>[:<anotherFilter>..] , ...
* # and each filter is expressed as:
* # <objectType>[|<objectType> ...] + <eventType>[|<eventType> ..]
* org.dspace.event.TestConsumer:all+all, \
* org.dspace.eperson.SubscribeConsumer:Item+CREATE|DELETE:Collection+ADD, ...
* </pre>
*
*/
private void parseEventConfig()
{
Enumeration propertyNames = ConfigurationManager.propertyNames();
while (propertyNames.hasMoreElements())
{
String ckey = ((String) propertyNames.nextElement()).trim();
if (ckey.startsWith(PROP_PFX) && ckey.endsWith(".class"))
{
String name = ckey.substring(PROP_PFX.length(), ckey
.length() - 6);
String dispatcherClass = ConfigurationManager
.getProperty(ckey);
// Can we grab all of the consumers configured for this
// dispatcher
// and store them also? Then there is no
// ConfigurationManager call
// upon other makeObject(key) requests resulting in a faster
// pool
// get.
dispatchers.put(name, dispatcherClass);
}
}
}
}
}