/*
* Copyright (c) 2006-2011 Nuxeo SA (http://nuxeo.com/) and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Bogdan Stefanescu
* Thierry Delprat
* Florent Guillaume
*/
package org.eclipse.ecr.core.event.impl;
import java.rmi.dgc.VMID;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.eclipse.ecr.core.api.ClientException;
import org.eclipse.ecr.core.event.Event;
import org.eclipse.ecr.core.event.EventBundle;
import org.eclipse.ecr.core.event.EventContext;
import org.eclipse.ecr.core.event.EventListener;
import org.eclipse.ecr.core.event.EventService;
import org.eclipse.ecr.core.event.EventServiceAdmin;
import org.eclipse.ecr.core.event.EventStats;
import org.eclipse.ecr.core.event.EventTransactionListener;
import org.eclipse.ecr.core.event.PostCommitEventListener;
import org.eclipse.ecr.core.event.ReconnectedEventBundle;
import org.eclipse.ecr.core.event.jms.AsyncProcessorConfig;
import org.eclipse.ecr.core.event.tx.BulkExecutor;
import org.eclipse.ecr.core.event.tx.PostCommitSynchronousRunner;
import org.eclipse.ecr.runtime.api.Framework;
import org.nuxeo.common.collections.ListenerList;
/**
* Implementation of the event service.
*/
public class EventServiceImpl implements EventService, EventServiceAdmin{
public static final VMID VMID = new VMID();
private static final Log log = LogFactory.getLog(EventServiceImpl.class);
protected static final ThreadLocal<CompositeEventBundle> compositeBundle = new ThreadLocal<CompositeEventBundle>() {
@Override
protected CompositeEventBundle initialValue() {
return new CompositeEventBundle();
}
};
private static class CompositeEventBundle {
static final long serialVersionUID = 1L;
boolean transacted;
final Map<String, EventBundle> byRepository = new HashMap<String, EventBundle>();
void push(Event event) {
String repositoryName = event.getContext().getRepositoryName();
if (!byRepository.containsKey(repositoryName)) {
byRepository.put(repositoryName, new EventBundleImpl());
}
byRepository.get(repositoryName).push(event);
}
}
protected final ListenerList txListeners;
protected final EventListenerList listenerDescriptors;
protected final AsyncEventExecutor asyncExec;
protected boolean blockAsyncProcessing = false;
protected boolean blockSyncPostCommitProcessing = false;
protected boolean bulkModeEnabled = false;
public EventServiceImpl() {
txListeners = new ListenerList();
listenerDescriptors = new EventListenerList();
asyncExec = AsyncEventExecutor.create();
}
public void shutdown() {
shutdown(0);
}
public void shutdown(long timeout) {
// the possible timeout here is doubled. we don't really care.
waitForAsyncCompletion(timeout);
asyncExec.shutdown(timeout);
}
/**
* @deprecated use {@link #waitForAsyncCompletion()} instead.
*/
@Deprecated
public int getActiveAsyncTaskCount() {
return asyncExec.getUnfinishedCount();
}
@Override
public void waitForAsyncCompletion() {
waitForAsyncCompletion(0);
}
@Override
public void waitForAsyncCompletion(long timeout) {
long t0 = System.currentTimeMillis();
while (asyncExec.getUnfinishedCount() > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
if (timeout > 0 && System.currentTimeMillis() > t0 + timeout) {
break;
}
}
}
@Override
public void addEventListener(EventListenerDescriptor listener) {
try {
listenerDescriptors.add(listener);
log.debug("Registered event listener: " + listener.getName());
} catch (Exception e) {
log.error(
"Failed to register event listener: " + listener.getName(),
e);
}
}
@Override
public void removeEventListener(EventListenerDescriptor listener) {
try {
listenerDescriptors.removeDescriptor(listener);
log.debug("Unregistered event listener: " + listener.getName());
} catch (Exception e) {
log.error(
"Failed to unregister event listener: " + listener.getName(),
e);
}
}
protected EventStats getEventStats() {
try {
return Framework.getService(EventStats.class);
} catch (Exception e) {
log.warn("Failed to lookup event stats service", e);
}
return null;
}
@Override
public void fireEvent(String name, EventContext context)
throws ClientException {
fireEvent(new EventImpl(name, context));
}
@Override
public void fireEvent(Event event) throws ClientException {
if (!event.isInline()) { // record the event
// don't record the complete event, only a shallow copy
ShallowEvent shallowEvent = ShallowEvent.create(event);
if (event.isImmediate()) {
EventBundleImpl b = new EventBundleImpl();
b.push(shallowEvent);
fireEventBundle(b);
}
else {
CompositeEventBundle b = compositeBundle.get();
b.push(shallowEvent);
// check for commit events to flush the event bundle
if (!b.transacted && event.isCommitEvent()) {
handleTxCommited();
}
}
}
String ename = event.getName();
EventStats stats = getEventStats();
for (EventListenerDescriptor desc : listenerDescriptors.getEnabledInlineListenersDescriptors()) {
if (desc.acceptEvent(ename)) {
try {
long t0 = System.currentTimeMillis();
desc.asEventListener().handleEvent(event);
if (stats != null) {
stats.logSyncExec(desc, System.currentTimeMillis()-t0);
}
} catch (Throwable t) {
log.error("Error during sync listener execution", t);
} finally {
if (event.isMarkedForRollBack()) {
throw new RuntimeException(
"Exception during sync listener execution, rollingback");
}
if (event.isCanceled()) {
return;
}
}
}
}
}
@Override
public void fireEventBundle(EventBundle event) throws ClientException {
boolean comesFromJMS = false;
if (event instanceof ReconnectedEventBundle) {
if (((ReconnectedEventBundle) event).comesFromJMS()) {
comesFromJMS = true;
}
}
if (bulkModeEnabled) {
// run all listeners synchronously in one transaction
List<EventListenerDescriptor> listeners = new ArrayList<EventListenerDescriptor>();
if (!blockSyncPostCommitProcessing) {
listeners = listenerDescriptors.getEnabledSyncPostCommitListenersDescriptors();
}
if (!blockAsyncProcessing) {
listeners.addAll(listenerDescriptors.getEnabledAsyncPostCommitListenersDescriptors());
}
if (!listeners.isEmpty()) {
BulkExecutor bulkExecutor = new BulkExecutor(listeners, event);
bulkExecutor.run();
}
return;
}
// run sync listeners
if (blockSyncPostCommitProcessing) {
log.debug("Dropping PostCommit handler execution");
}
else if (comesFromJMS) {
// when called from JMS we must skip sync listeners
// - postComit listeners should be on the core
// - there is no transaction started by JMS listener
log.debug("Deactivating sync post-commit listener since we are called from JMS");
} else {
List<EventListenerDescriptor> syncPCDescs = listenerDescriptors.getEnabledSyncPostCommitListenersDescriptors();
if (syncPCDescs!=null && !syncPCDescs.isEmpty()) {
PostCommitSynchronousRunner syncRunner = new PostCommitSynchronousRunner(
syncPCDescs, event);
syncRunner.run();
}
}
if (blockAsyncProcessing) {
log.debug("Dopping bundle");
return;
}
// fire async listeners
if (AsyncProcessorConfig.forceJMSUsage() && !comesFromJMS) {
log.debug("Skipping async exec, this will be triggered via JMS");
} else {
asyncExec.run(listenerDescriptors.getEnabledAsyncPostCommitListenersDescriptors(), event);
}
}
@Override
public void fireEventBundleSync(EventBundle event) throws ClientException {
for (EventListenerDescriptor desc : listenerDescriptors.getEnabledSyncPostCommitListenersDescriptors()) {
desc.asPostCommitListener().handleEvent(event);
}
for (EventListenerDescriptor desc : listenerDescriptors.getEnabledAsyncPostCommitListenersDescriptors()) {
desc.asPostCommitListener().handleEvent(event);
}
}
@Override
public List<EventListener> getEventListeners() {
return listenerDescriptors.getInLineListeners();
}
@Override
public List<PostCommitEventListener> getPostCommitEventListeners() {
List<PostCommitEventListener> result = new ArrayList<PostCommitEventListener>();
result.addAll(listenerDescriptors.getSyncPostCommitListeners());
result.addAll(listenerDescriptors.getAsyncPostCommitListeners());
return result;
}
@Override
public void transactionStarted() {
handleTxStarted();
}
@Override
public void transactionCommitted() throws ClientException {
handleTxCommited();
}
@Override
public void transactionRolledback() {
handleTxRollbacked();
}
@Override
public boolean isTransactionStarted() {
return compositeBundle.get().transacted;
}
public EventListenerList getEventListenerList() {
return listenerDescriptors;
}
// methods for monitoring
@Override
public EventListenerList getListenerList() {
return listenerDescriptors;
}
@Override
public void setListenerEnabledFlag(String listenerName, boolean enabled) {
if (!listenerDescriptors.getListenerNames().contains(listenerName)) {
return;
}
for (EventListenerDescriptor desc : listenerDescriptors.getAsyncPostCommitListenersDescriptors()) {
if (desc.getName().equals(listenerName)) {
desc.setEnabled(enabled);
synchronized (this) {
listenerDescriptors.recomputeEnabledListeners();
}
return;
}
}
for (EventListenerDescriptor desc : listenerDescriptors.getSyncPostCommitListenersDescriptors()) {
if (desc.getName().equals(listenerName)) {
desc.setEnabled(enabled);
synchronized (this) {
listenerDescriptors.recomputeEnabledListeners();
}
return;
}
}
for (EventListenerDescriptor desc : listenerDescriptors.getInlineListenersDescriptors()) {
if (desc.getName().equals(listenerName)) {
desc.setEnabled(enabled);
synchronized (this) {
listenerDescriptors.recomputeEnabledListeners();
}
return;
}
}
}
@Override
public int getActiveThreadsCount() {
return asyncExec.getActiveCount();
}
@Override
public int getEventsInQueueCount() {
return asyncExec.getUnfinishedCount();
}
@Override
public boolean isBlockAsyncHandlers() {
return blockAsyncProcessing;
}
@Override
public boolean isBlockSyncPostCommitHandlers() {
return blockSyncPostCommitProcessing;
}
@Override
public void setBlockAsyncHandlers(boolean blockAsyncHandlers) {
blockAsyncProcessing = blockAsyncHandlers;
}
@Override
public void setBlockSyncPostCommitHandlers(boolean blockSyncPostComitHandlers) {
blockSyncPostCommitProcessing = blockSyncPostComitHandlers;
}
@Override
public boolean isBulkModeEnabled() {
return bulkModeEnabled;
}
@Override
public void setBulkModeEnabled(boolean bulkModeEnabled) {
this.bulkModeEnabled = bulkModeEnabled;
}
@Override
public void addTransactionListener(EventTransactionListener listener) {
txListeners.add(listener);
}
@Override
public void removeTransactionListener(EventTransactionListener listener) {
txListeners.remove(listener);
}
protected void handleTxStarted() {
compositeBundle.get().transacted = true;
for (Object listener : txListeners.getListeners()) {
((EventTransactionListener)listener).transactionStarted();
}
}
protected void handleTxRollbacked() {
compositeBundle.remove();
for (Object listener : txListeners.getListeners()) {
((EventTransactionListener)listener).transactionRollbacked();
}
}
protected void handleTxCommited() {
CompositeEventBundle b = compositeBundle.get();
try {
// notify post commit event listeners
for (EventBundle bundle : b.byRepository.values()) {
try {
fireEventBundle(bundle);
} catch (ClientException e) {
log.error("Error while processing " + bundle, e);
}
}
// notify post commit tx listeners
for (Object listener : txListeners.getListeners()) {
((EventTransactionListener) listener).transactionCommitted();
}
} finally {
compositeBundle.remove();
}
}
}