/**
* 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.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.pool2.KeyedObjectPool;
import org.apache.commons.pool2.KeyedPooledObjectFactory;
import org.apache.commons.pool2.PoolUtils;
import org.apache.commons.pool2.PooledObject;
import org.apache.commons.pool2.impl.DefaultPooledObject;
import org.apache.commons.pool2.impl.GenericKeyedObjectPool;
import org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig;
import org.apache.log4j.Logger;
import org.dspace.core.Context;
import org.dspace.event.service.EventService;
import org.dspace.services.ConfigurationService;
import org.dspace.services.factory.DSpaceServicesFactory;
/**
* 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 EventServiceImpl implements EventService
{
/** log4j category */
private Logger log = Logger.getLogger(EventServiceImpl.class);
protected DispatcherPoolFactory dispatcherFactory = null;
protected GenericKeyedObjectPoolConfig poolConfig = null;
// Keyed FIFO Pool of event dispatchers
protected KeyedObjectPool dispatcherPool = null;
protected Map<String, Integer> consumerIndicies = null;
protected String CONSUMER_PFX = "event.consumer";
private static final ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService();
protected EventServiceImpl()
{
initPool();
log.info("EventService dispatcher pool initialized");
}
private 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 GenericKeyedObjectPoolConfig();
poolConfig.setMaxTotalPerKey(100);
poolConfig.setMaxIdlePerKey(5);
poolConfig.setMaxTotal(100);
try
{
dispatcherFactory = new DispatcherPoolFactory();
dispatcherPool = PoolUtils
.synchronizedPool(new GenericKeyedObjectPool(
dispatcherFactory, poolConfig));
enumerateConsumers();
}
catch (Exception e)
{
log.error("Could not initialize EventService dispatcher pool", e);
}
}
}
@Override
public 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);
}
}
@Override
public void returnDispatcher(String key, Dispatcher disp)
{
try
{
dispatcherPool.returnObject(key, disp);
}
catch (Exception e)
{
throw new IllegalStateException("Unable to return dispatcher named " + key, e);
}
}
@Override
public int getConsumerIndex(String consumerClass)
{
Integer index = (Integer) consumerIndicies.get(consumerClass);
return index != null ? index.intValue() : -1;
}
protected void enumerateConsumers()
{
// Get all configs starting with CONSUMER_PFX
List<String> propertyNames = configurationService.getPropertyKeys(CONSUMER_PFX);
int bitSetIndex = 0;
if (consumerIndicies == null)
{
consumerIndicies = new HashMap<String, Integer>();
}
for(String ckey : propertyNames)
{
if (ckey.endsWith(".class"))
{
String consumerName = ckey.substring(CONSUMER_PFX.length()+1,
ckey.length() - 6);
consumerIndicies.put(consumerName, (Integer) bitSetIndex);
bitSetIndex++;
}
}
}
protected class DispatcherPoolFactory implements KeyedPooledObjectFactory<String,Dispatcher>
{
// Prefix of keys in DSpace Configuration
private static final String PROP_PFX = "event.dispatcher";
// Cache of event dispatchers, keyed by name, for re-use.
protected Map<String, String> dispatchers = new HashMap<String, String>();
public DispatcherPoolFactory()
{
parseEventConfig();
}
public PooledObject<Dispatcher> wrap(Dispatcher d) {
return new DefaultPooledObject<>(d);
}
@Override
public PooledObject<Dispatcher> makeObject(String 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[] consumers = configurationService
.getArrayProperty(consumerKey);
if (ArrayUtils.isEmpty(consumers))
{
throw new IllegalStateException(
"No Configuration entry found for consumer list of event Dispatcher: \""
+ consumerKey + "\"");
}
ConsumerProfile consumerProfile = null;
for (String consumer : consumers)
{
consumerProfile = ConsumerProfile
.makeConsumerProfile(consumer);
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 wrap(dispatcher);
}
@Override
public void activateObject(String arg0, PooledObject<Dispatcher> arg1) throws Exception
{
// No-op
return;
}
@Override
public void destroyObject(String key, PooledObject<Dispatcher> pooledDispatcher)
throws Exception
{
Context ctx = new Context();
try {
Dispatcher dispatcher = pooledDispatcher.getObject();
for (Iterator ci = dispatcher.getConsumers()
.iterator(); ci.hasNext();)
{
ConsumerProfile cp = (ConsumerProfile) ci.next();
if (cp != null)
{
cp.getConsumer().finish(ctx);
}
}
} catch (Exception e) {
ctx.abort();
throw e;
}
}
@Override
public void passivateObject(String arg0, PooledObject<Dispatcher> arg1) throws Exception
{
// No-op
return;
}
@Override
public boolean validateObject(String arg0, PooledObject<Dispatcher> 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.class = org.dspace.event.BasicDispatcher
* </pre>
*
*/
private void parseEventConfig()
{
// Get all configs starting with PROP_PFX
List<String> propertyNames = configurationService.getPropertyKeys(PROP_PFX);
for(String ckey : propertyNames)
{
// If it ends with ".class", append it to our list of dispatcher classes
if (ckey.endsWith(".class"))
{
String name = ckey.substring(PROP_PFX.length()+1, ckey
.length() - 6);
String dispatcherClass = configurationService
.getProperty(ckey);
dispatchers.put(name, dispatcherClass);
}
}
}
}
}