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}