/*
* (C) Copyright 2006-2013 Nuxeo SA (http://nuxeo.com/) and others.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* 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.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) {
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);
} 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);
} 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);
} 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
*/
protected abstract boolean handleEventPreprocessing(EventBundle events, Map<String, Object> data);
/**
* 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
*/
protected abstract boolean handleEventLongRunning(List<String> eventNames, Map<String, Object> data);
/**
* Finish processing in a dedicated Transaction
*
* @param events {@link EventBundle} received
* @param data an map that may have been filled by handleEventPreprocessing and handleEventLongRunning
*/
protected abstract void handleEventPostprocessing(EventBundle events, Map<String, Object> data);
}