/* * (C) Copyright 2006-2013 Nuxeo SAS (http://nuxeo.com/) and contributors. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the GNU Lesser General Public License * (LGPL) version 2.1 which accompanies this distribution, and is available at * http://www.gnu.org/licenses/lgpl.html * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * Contributors: * Nuxeo - initial API and implementation * */ package org.nuxeo.ecm.core.event.impl; 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.nuxeo.ecm.core.api.ClientException; import org.nuxeo.ecm.core.event.EventBundle; import org.nuxeo.ecm.core.event.PostCommitFilteringEventListener; import org.nuxeo.runtime.transaction.TransactionHelper; /** * Abstract class that helps building an Asynchronous listeners that will handle * a long running process. * <p/> * By default, {@link org.nuxeo.ecm.core.event.PostCommitEventListener} are executed in a {@link org.nuxeo.ecm.core.work.api.Work} * that will take care of starting/comitting the transaction. * <p/> * If the listener requires a long processing this will create long transactions * that are not good. To avoid this behavior, this base class split the * processing in 3 steps : * <ul> * <li>pre processing : transactional first step</li> * <li>long running : long running processing that should not require * transactional resources</li> * <li>post processing : transactional final step * </ul> * <p/> * To manage sharing between the 3 steps, a simple Map is provided. * * @since 5.7.2 * @author <a href="mailto:tdelprat@nuxeo.com">Tiry</a> * */ public abstract class AbstractLongRunningListener implements PostCommitFilteringEventListener { protected static final Log log = LogFactory.getLog(AbstractLongRunningListener.class); @Override public void handleEvent(EventBundle events) throws ClientException { Map<String, Object> data = new HashMap<String, Object>(); if (events instanceof ReconnectedEventBundleImpl) { boolean doContinue = false; // do pre-processing and commit transaction ReconnectedEventBundleImpl preProcessBunle = new ReconnectedEventBundleImpl( events); try { doContinue = handleEventPreprocessing(preProcessBunle, data); } catch (ClientException e) { log.error( "Long Running listener canceled after failed execution of preprocessing", e); throw e; } finally { TransactionHelper.commitOrRollbackTransaction(); preProcessBunle.disconnect(); } if (!doContinue) { return; } // do main-processing in a non transactional context // a new CoreSession will be open by ReconnectedEventBundleImpl try { doContinue = handleEventLongRunning( ((ReconnectedEventBundleImpl) events).getEventNames(), data); } catch (ClientException e) { log.error( "Long Running listener canceled after failed execution of main run", e); throw e; } finally { ((ReconnectedEventBundleImpl) events).disconnect(); } if (!doContinue) { return; } // do final-processing in a new transaction // a new CoreSession will be open by ReconnectedEventBundleImpl ReconnectedEventBundleImpl postProcessEventBundle = new ReconnectedEventBundleImpl( events); try { TransactionHelper.startTransaction(); handleEventPostprocessing(postProcessEventBundle, data); } catch (ClientException e) { log.error( "Long Running listener canceled after failed execution of main run", e); throw e; } finally { TransactionHelper.commitOrRollbackTransaction(); postProcessEventBundle.disconnect(); } } else { log.error("Unable to execute long running listener, input EventBundle is not a ReconnectedEventBundle"); } } /** * Handles first step of processing in a normal transactional way. * * @param events {@link EventBundle} received * @param data an empty map to store data to share data between steps. * @return true of processing should continue, false otherwise * @throws ClientExceptions */ protected abstract boolean handleEventPreprocessing(EventBundle events, Map<String, Object> data) throws ClientException; /** * Will be executed in a non transactional context * <p/> * Any acess to a CoreSession will generate WARN in the the logs. * <p/> * Documents passed with data should not be connected. * * @param eventNames list of event names * @param data an map that may have been filled by handleEventPreprocessing * @return true of processing should continue, false otherwise * @throws ClientException */ protected abstract boolean handleEventLongRunning(List<String> eventNames, Map<String, Object> data) throws ClientException; /** * Finish processing in a dedicated Transaction * * @param events {@link EventBundle} received * @param data an map that may have been filled by handleEventPreprocessing * and handleEventLongRunning * @throws ClientException */ protected abstract void handleEventPostprocessing(EventBundle events, Map<String, Object> data) throws ClientException; }