001/*
002 * (C) Copyright 2006-2013 Nuxeo SAS (http://nuxeo.com/) and contributors.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the GNU Lesser General Public License
006 * (LGPL) version 2.1 which accompanies this distribution, and is available at
007 * http://www.gnu.org/licenses/lgpl.html
008 *
009 * This library is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012 * Lesser General Public License for more details.
013 *
014 * Contributors:
015 *     Nuxeo - initial API and implementation
016 *
017 */
018
019package org.nuxeo.ecm.core.event.impl;
020
021import java.util.HashMap;
022import java.util.List;
023import java.util.Map;
024
025import org.apache.commons.logging.Log;
026import org.apache.commons.logging.LogFactory;
027import org.nuxeo.ecm.core.event.EventBundle;
028import org.nuxeo.ecm.core.event.PostCommitFilteringEventListener;
029import org.nuxeo.runtime.transaction.TransactionHelper;
030
031/**
032 * Abstract class that helps building an Asynchronous listeners that will handle a long running process.
033 * <p/>
034 * By default, {@link org.nuxeo.ecm.core.event.PostCommitEventListener} are executed in a
035 * {@link org.nuxeo.ecm.core.work.api.Work} that will take care of starting/comitting the transaction.
036 * <p/>
037 * If the listener requires a long processing this will create long transactions that are not good. To avoid this
038 * behavior, this base class split the processing in 3 steps :
039 * <ul>
040 * <li>pre processing : transactional first step</li>
041 * <li>long running : long running processing that should not require transactional resources</li>
042 * <li>post processing : transactional final step
043 * </ul>
044 * <p/>
045 * To manage sharing between the 3 steps, a simple Map is provided.
046 *
047 * @since 5.7.2
048 * @author <a href="mailto:tdelprat@nuxeo.com">Tiry</a>
049 */
050public abstract class AbstractLongRunningListener implements PostCommitFilteringEventListener {
051
052    protected static final Log log = LogFactory.getLog(AbstractLongRunningListener.class);
053
054    @Override
055    public void handleEvent(EventBundle events) {
056
057        Map<String, Object> data = new HashMap<String, Object>();
058
059        if (events instanceof ReconnectedEventBundleImpl) {
060
061            boolean doContinue = false;
062            // do pre-processing and commit transaction
063            ReconnectedEventBundleImpl preProcessBunle = new ReconnectedEventBundleImpl(events);
064            try {
065                doContinue = handleEventPreprocessing(preProcessBunle, data);
066            } finally {
067                TransactionHelper.commitOrRollbackTransaction();
068                preProcessBunle.disconnect();
069            }
070            if (!doContinue) {
071                return;
072            }
073
074            // do main-processing in a non transactional context
075            // a new CoreSession will be open by ReconnectedEventBundleImpl
076            try {
077                doContinue = handleEventLongRunning(((ReconnectedEventBundleImpl) events).getEventNames(), data);
078            } finally {
079                ((ReconnectedEventBundleImpl) events).disconnect();
080            }
081
082            if (!doContinue) {
083                return;
084            }
085
086            // do final-processing in a new transaction
087            // a new CoreSession will be open by ReconnectedEventBundleImpl
088            ReconnectedEventBundleImpl postProcessEventBundle = new ReconnectedEventBundleImpl(events);
089            try {
090                TransactionHelper.startTransaction();
091                handleEventPostprocessing(postProcessEventBundle, data);
092            } finally {
093                TransactionHelper.commitOrRollbackTransaction();
094                postProcessEventBundle.disconnect();
095            }
096        } else {
097            log.error("Unable to execute long running listener, input EventBundle is not a ReconnectedEventBundle");
098        }
099    }
100
101    /**
102     * Handles first step of processing in a normal transactional way.
103     *
104     * @param events {@link EventBundle} received
105     * @param data an empty map to store data to share data between steps.
106     * @return true of processing should continue, false otherwise
107     */
108    protected abstract boolean handleEventPreprocessing(EventBundle events, Map<String, Object> data);
109
110    /**
111     * Will be executed in a non transactional context
112     * <p/>
113     * Any acess to a CoreSession will generate WARN in the the logs.
114     * <p/>
115     * Documents passed with data should not be connected.
116     *
117     * @param eventNames list of event names
118     * @param data an map that may have been filled by handleEventPreprocessing
119     * @return true of processing should continue, false otherwise
120     */
121    protected abstract boolean handleEventLongRunning(List<String> eventNames, Map<String, Object> data);
122
123    /**
124     * Finish processing in a dedicated Transaction
125     *
126     * @param events {@link EventBundle} received
127     * @param data an map that may have been filled by handleEventPreprocessing and handleEventLongRunning
128     */
129    protected abstract void handleEventPostprocessing(EventBundle events, Map<String, Object> data);
130
131}