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