org.nuxeo.ecm.core.event.impl.EventServiceImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.nuxeo.ecm.core.event.impl.EventServiceImpl.java

Source

/*
 * Copyright (c) 2006-2013 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.nuxeo.ecm.core.event.impl;

import java.rmi.dgc.VMID;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;

import javax.naming.NamingException;
import javax.transaction.RollbackException;
import javax.transaction.Status;
import javax.transaction.Synchronization;
import javax.transaction.SystemException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.nuxeo.ecm.core.api.ClientException;
import org.nuxeo.ecm.core.api.RecoverableClientException;
import org.nuxeo.ecm.core.event.Event;
import org.nuxeo.ecm.core.event.EventBundle;
import org.nuxeo.ecm.core.event.EventContext;
import org.nuxeo.ecm.core.event.EventListener;
import org.nuxeo.ecm.core.event.EventService;
import org.nuxeo.ecm.core.event.EventServiceAdmin;
import org.nuxeo.ecm.core.event.EventStats;
import org.nuxeo.ecm.core.event.PostCommitEventListener;
import org.nuxeo.ecm.core.event.ReconnectedEventBundle;
import org.nuxeo.ecm.core.event.jms.AsyncProcessorConfig;
import org.nuxeo.runtime.api.Framework;
import org.nuxeo.runtime.transaction.TransactionHelper;

/**
 * Implementation of the event service.
 */
public class EventServiceImpl implements EventService, EventServiceAdmin, Synchronization {

    public static final VMID VMID = new VMID();

    private static final Log log = LogFactory.getLog(EventServiceImpl.class);

    protected static final ThreadLocal<CompositeEventBundle> threadBundles = new ThreadLocal<CompositeEventBundle>() {
        @Override
        protected CompositeEventBundle initialValue() {
            return new CompositeEventBundle();
        }
    };

    private static class CompositeEventBundle {

        boolean registeredSynchronization;

        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 EventListenerList listenerDescriptors;

    protected PostCommitEventExecutor postCommitExec;

    protected volatile AsyncEventExecutor asyncExec;

    protected final List<AsyncWaitHook> asyncWaitHooks = new CopyOnWriteArrayList<AsyncWaitHook>();

    protected boolean blockAsyncProcessing = false;

    protected boolean blockSyncPostCommitProcessing = false;

    protected boolean bulkModeEnabled = false;

    public EventServiceImpl() {
        listenerDescriptors = new EventListenerList();
        postCommitExec = new PostCommitEventExecutor();
        asyncExec = new AsyncEventExecutor();
    }

    public void init() {
        asyncExec.init();
    }

    public void shutdown(long timeoutMillis) throws InterruptedException {
        postCommitExec.shutdown(timeoutMillis);
        Set<AsyncWaitHook> notTerminated = new HashSet<AsyncWaitHook>();
        for (AsyncWaitHook hook : asyncWaitHooks) {
            if (hook.shutdown() == false) {
                notTerminated.add(hook);
            }
        }
        if (!notTerminated.isEmpty()) {
            throw new RuntimeException("Asynch services are still running : " + notTerminated);
        }
        if (asyncExec.shutdown(timeoutMillis) == false) {
            throw new RuntimeException("Async executor is still running, timeout expired");
        }
    }

    public void registerForAsyncWait(AsyncWaitHook callback) {
        asyncWaitHooks.add(callback);
    }

    public void unregisterForAsyncWait(AsyncWaitHook callback) {
        asyncWaitHooks.remove(callback);
    }

    /**
     * @deprecated use {@link #waitForAsyncCompletion()} instead.
     */
    @Deprecated
    public int getActiveAsyncTaskCount() {
        return asyncExec.getUnfinishedCount();
    }

    @Override
    public void waitForAsyncCompletion() {
        waitForAsyncCompletion(Long.MAX_VALUE);
    }

    @Override
    public void waitForAsyncCompletion(long timeout) {
        Set<AsyncWaitHook> notCompleted = new HashSet<AsyncWaitHook>();
        for (AsyncWaitHook hook : asyncWaitHooks) {
            if (!hook.waitForAsyncCompletion()) {
                notCompleted.add(hook);
            }
        }
        if (!notCompleted.isEmpty()) {
            throw new RuntimeException("Async tasks are still running : " + notCompleted);
        }
        try {
            if (!asyncExec.waitForCompletion(timeout)) {
                throw new RuntimeException("Async event listeners thread pool is not terminated");
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            // TODO change signature
            throw new RuntimeException(e);
        }
    }

    @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 {

        String ename = event.getName();
        EventStats stats = getEventStats();
        for (EventListenerDescriptor desc : listenerDescriptors.getEnabledInlineListenersDescriptors()) {
            if (desc.acceptEvent(ename)) {
                Throwable rollbackException = null;
                try {
                    long t0 = System.currentTimeMillis();
                    desc.asEventListener().handleEvent(event);
                    if (stats != null) {
                        stats.logSyncExec(desc, System.currentTimeMillis() - t0);
                    }
                } catch (Throwable t) {
                    String message;
                    if (event.isBubbleException() || event.isMarkedForRollBack()) {
                        message = "Error during " + desc.getName()
                                + " sync listener execution, transaction will be rolled back";
                        rollbackException = t;
                    } else {
                        message = "Error during " + desc.getName()
                                + " sync listener execution, transaction won't be rolled back "
                                + "since event.markRollBack() was not called by the Listener";
                    }
                    if (t instanceof RecoverableClientException) {
                        log.info(message + "\n" + t.getMessage());
                        log.debug(message, t);
                    } else {
                        log.error(message, t);
                    }
                } finally {
                    if (event.isBubbleException()) {
                        throw new RuntimeException(rollbackException);
                    } else if (event.isMarkedForRollBack()) {

                        String message = "Exception during " + desc.getName()
                                + " sync listener execution, rolling back";
                        if (event.getRollbackMessage() != null) {
                            message = message + " (" + event.getRollbackMessage() + ")";
                        }
                        if (event.getRollbackException() != null) {
                            rollbackException = event.getRollbackException();
                        }

                        if (rollbackException != null) {
                            throw new RuntimeException(message, rollbackException);
                        } else {
                            throw new RuntimeException(message);
                        }
                    }
                    if (event.isCanceled()) {
                        return;
                    }
                }
            }
        }

        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 {
                recordEvent(shallowEvent);
            }
        }
    }

    @Override
    public void fireEventBundle(EventBundle event) throws ClientException {
        boolean comesFromJMS = false;

        if (event instanceof ReconnectedEventBundle) {
            if (((ReconnectedEventBundle) event).comesFromJMS()) {
                comesFromJMS = true;
            }
        }

        List<EventListenerDescriptor> postCommitSync = listenerDescriptors
                .getEnabledSyncPostCommitListenersDescriptors();
        List<EventListenerDescriptor> postCommitAsync = listenerDescriptors
                .getEnabledAsyncPostCommitListenersDescriptors();

        if (bulkModeEnabled) {
            // run all listeners synchronously in one transaction
            List<EventListenerDescriptor> listeners = new ArrayList<EventListenerDescriptor>();
            if (!blockSyncPostCommitProcessing) {
                listeners = postCommitSync;
            }
            if (!blockAsyncProcessing) {
                listeners.addAll(postCommitAsync);
            }
            if (!listeners.isEmpty()) {
                postCommitExec.runBulk(listeners, event);
            }
            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 {
            if (!postCommitSync.isEmpty()) {
                postCommitExec.run(postCommitSync, event);
            }
        }

        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(postCommitAsync, 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;
    }

    public EventListenerList getEventListenerList() {
        return listenerDescriptors;
    }

    @Override
    public EventListenerDescriptor getEventListener(String name) {
        return listenerDescriptors.getDescriptor(name);
    }

    // methods for monitoring

    @Override
    public EventListenerList getListenerList() {
        return listenerDescriptors;
    }

    @Override
    public void setListenerEnabledFlag(String listenerName, boolean enabled) {
        if (!listenerDescriptors.hasListener(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;
    }

    protected void recordEvent(Event event) {
        CompositeEventBundle b = threadBundles.get();
        b.push(event);
        if (TransactionHelper.isTransactionActive()) {
            if (!b.registeredSynchronization) {
                // register as synchronization
                try {
                    TransactionHelper.lookupTransactionManager().getTransaction().registerSynchronization(this);
                } catch (NamingException | SystemException | RollbackException e) {
                    throw new RuntimeException("Cannot register Synchronization", e);
                }
                b.registeredSynchronization = true;
            }
        } else if (event.isCommitEvent()) {
            handleTxCommited();
        }
    }

    @Override
    public void beforeCompletion() {
    }

    @Override
    public void afterCompletion(int status) {
        if (status == Status.STATUS_COMMITTED) {
            handleTxCommited();
        } else if (status == Status.STATUS_ROLLEDBACK) {
            handleTxRollbacked();
        } else {
            log.error("Unexpected afterCompletion status: " + status);
        }
    }

    protected void handleTxRollbacked() {
        threadBundles.remove();
    }

    protected void handleTxCommited() {
        CompositeEventBundle b = threadBundles.get();
        threadBundles.remove();

        // notify post commit event listeners
        for (EventBundle bundle : b.byRepository.values()) {
            try {
                fireEventBundle(bundle);
            } catch (ClientException e) {
                log.error("Error while processing " + bundle, e);
            }
        }
    }

}