/*
* Copyright (c) 2006-2011 Nuxeo SA (http://nuxeo.com/) and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Florent Guillaume
*/
package org.eclipse.ecr.core.api;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import javax.naming.NamingException;
import javax.transaction.Status;
import javax.transaction.Synchronization;
import javax.transaction.Transaction;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.eclipse.ecr.core.api.CoreSession;
import org.eclipse.ecr.core.api.NoRollbackOnException;
import org.eclipse.ecr.runtime.transaction.TransactionHelper;
/**
* Wrapper around a CoreSession that gives it transactional behavior.
* <p>
* Transactional behavior:
* <ul>
* <li>notifies the event service on transaction start/stop</li>
* <li>throws RollbackException</li>
* </ul>
*/
public class TransactionalCoreSessionWrapper implements InvocationHandler,
Synchronization {
private static final Log log = LogFactory.getLog(TransactionalCoreSessionWrapper.class);
private static final Class<?>[] INTERFACES = new Class[] { CoreSession.class };
private final CoreSession session;
/**
* Per-thread flag with transaction status:
* <ul>
* <li>{@code null}: outside transaction</li>
* <li>{@code TRUE}: in a transaction</li>
* </ul>
*/
private final ThreadLocal<Boolean> threadBound = new ThreadLocal<Boolean>();
protected TransactionalCoreSessionWrapper(CoreSession session) {
this.session = session;
}
public static CoreSession wrap(CoreSession session) {
try {
TransactionHelper.lookupTransactionManager();
} catch (NamingException e) {
// no transactions, do not wrap
return session;
}
ClassLoader cl = session.getClass().getClassLoader();
return (CoreSession) Proxy.newProxyInstance(cl, INTERFACES,
new TransactionalCoreSessionWrapper(session));
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
Boolean b = threadBound.get();
if (b == null) {
// first call in thread
try {
Transaction tx = TransactionHelper.lookupTransactionManager().getTransaction();
if (tx != null
&& tx.getStatus() != Status.STATUS_MARKED_ROLLBACK) {
tx.registerSynchronization(this);
session.afterBegin();
threadBound.set(Boolean.TRUE);
}
} catch (NamingException e) {
// no transaction manager, ignore
} catch (Exception e) {
log.error("Error on transaction synchronizer registration", e);
}
}
try {
return method.invoke(session, args);
} catch (Throwable t) {
if (TransactionHelper.isTransactionActive()
&& needsRollback(method, t)) {
TransactionHelper.setTransactionRollbackOnly();
}
if (t instanceof InvocationTargetException) {
Throwable tt = ((InvocationTargetException) t).getTargetException();
if (tt != null) {
throw tt;
}
}
throw t;
}
}
protected boolean needsRollback(Method method, Throwable t) {
for (Annotation annotation : method.getAnnotations()) {
if (annotation.annotationType() == NoRollbackOnException.class) {
return false;
}
}
return true;
}
@Override
public void beforeCompletion() {
session.beforeCompletion();
}
@Override
public void afterCompletion(int status) {
threadBound.remove();
boolean committed;
if (status == Status.STATUS_COMMITTED) {
committed = true;
} else if (status == Status.STATUS_ROLLEDBACK) {
committed = false;
} else {
log.error("Unexpected status after completion: " + status);
return;
}
session.afterCompletion(committed);
}
@Override
public String toString() {
return this.getClass().getSimpleName() + '(' + session + ')';
}
}