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<>();
060
061        if (events instanceof ReconnectedEventBundleImpl) {
062
063            boolean doContinue = false;
064
065            // do pre-processing in a transaction
066            // a new CoreSession will be open by ReconnectedEventBundleImpl
067            ReconnectedEventBundleImpl preProcessBunle = new ReconnectedEventBundleImpl(events);
068            try {
069                doContinue = handleEventPreprocessing(preProcessBunle, data);
070            } finally {
071                preProcessBunle.disconnect();
072            }
073
074            if (!doContinue) {
075                return;
076            }
077
078            // do main-processing in a non transactional context
079            TransactionHelper.commitOrRollbackTransaction();
080            try {
081                doContinue = handleEventLongRunning(((ReconnectedEventBundleImpl) events).getEventNames(), data);
082            } finally {
083                TransactionHelper.startTransaction();
084            }
085
086            if (!doContinue) {
087                return;
088            }
089
090            // do final-processing in a new transaction
091            // a new CoreSession will be open by ReconnectedEventBundleImpl
092            ReconnectedEventBundleImpl postProcessEventBundle = new ReconnectedEventBundleImpl(events);
093            try {
094                handleEventPostprocessing(postProcessEventBundle, data);
095            } finally {
096                postProcessEventBundle.disconnect();
097            }
098            TransactionHelper.commitOrRollbackTransaction();
099            TransactionHelper.startTransaction();
100        } else {
101            log.error("Unable to execute long running listener, input EventBundle is not a ReconnectedEventBundle");
102        }
103    }
104
105    /**
106     * Handles first step of processing in a normal transactional way.
107     *
108     * @param events {@link EventBundle} received
109     * @param data an empty map to store data to share data between steps.
110     * @return true of processing should continue, false otherwise
111     */
112    protected abstract boolean handleEventPreprocessing(EventBundle events, Map<String, Object> data);
113
114    /**
115     * Will be executed in a non transactional context
116     * <p>
117     * Any access to a CoreSession will generate WARN in the the logs.
118     * <p>
119     * Documents passed with data should not be connected.
120     *
121     * @param eventNames list of event names
122     * @param data an map that may have been filled by handleEventPreprocessing
123     * @return true of processing should continue, false otherwise
124     */
125    protected abstract boolean handleEventLongRunning(List<String> eventNames, Map<String, Object> data);
126
127    /**
128     * Finish processing in a dedicated Transaction
129     *
130     * @param events {@link EventBundle} received
131     * @param data an map that may have been filled by handleEventPreprocessing and handleEventLongRunning
132     */
133    protected abstract void handleEventPostprocessing(EventBundle events, Map<String, Object> data);
134
135}