/* SpringTransactionSynchronizationListener.java Purpose: Description: History: Tue Sep 15 13:55:11 2006, Created by henrichen Copyright (C) 2006 Potix Corporation. All Rights Reserved. {{IS_RIGHT This program is distributed under LGPL Version 2.1 in the hope that it will be useful, but WITHOUT ANY WARRANTY. }}IS_RIGHT */ package org.zkoss.zkplus.spring; import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.zkoss.lang.Classes; import org.zkoss.lang.SystemException; import org.zkoss.zk.ui.Component; import org.zkoss.zk.ui.Executions; import org.zkoss.zk.ui.UiException; import org.zkoss.zk.ui.WebApp; import org.zkoss.zk.ui.event.Event; import org.zkoss.zk.ui.event.EventThreadCleanup; import org.zkoss.zk.ui.event.EventThreadInit; import org.zkoss.zk.ui.event.EventThreadResume; import org.zkoss.zkplus.util.ThreadLocals; /** * <p>Listener to make sure each ZK thread got the same ThreadLocal value of the * spring's org.springframework.transaction.support.TransactionSynchronizationManager; * especially those thread bound resources. * </p> * <p> * This listener is used with Spring Framework (version 1.2.8+) "thread" bounded * resources. * * <pre><code> * <listener> * <description>Spring TransactionSynchronizationManager handler</description> * <listener-class>org.zkoss.zkplus.spring.SpringTransactionSynchronizationListener</listener-class> * </listener> * </code></pre> * </p> * <p>Applicable to Spring Framework version 2.x or later</p> * @author henrichen */ public class SpringTransactionSynchronizationListener implements EventThreadInit, EventThreadCleanup, EventThreadResume { private static final Logger log = LoggerFactory.getLogger(SpringTransactionSynchronizationListener.class); private Object[] _threadLocals = null; private final boolean _enabled; //whether event thread enabled public SpringTransactionSynchronizationListener() { final WebApp app = Executions.getCurrent().getDesktop().getWebApp(); _enabled = app.getConfiguration().isEventThreadEnabled(); } //-- EventThreadInit --// public void prepare(Component comp, Event evt) { if (_enabled) { getThreadLocals(); //get from servlet thread's ThreadLocal } } public boolean init(Component comp, Event evt) { if (_enabled) { setThreadLocals(); //copy to event thread's ThreadLocal } return true; } //-- EventThreadCleanup --// public void cleanup(Component comp, Event evt, List errs) { if (_enabled) { getThreadLocals(); //get from event thread's ThreadLocal //we don't handle the exception since the ZK engine will throw it again! } } public void complete(Component comp, Event evt) { if (_enabled) { setThreadLocals(); //copy to servlet thread's ThreadLocal } } //-- EventThreadResume --// public void beforeResume(Component comp, Event evt) { if (_enabled) { getThreadLocals(); //get from servlet thread's ThreadLocal } } public void afterResume(Component comp, Event evt) { if (_enabled) { setThreadLocals(); //copy to event thread's ThreadLocal } } public void abortResume(Component comp, Event evt) { //do nothing } //-- utilities --// private void getThreadLocals() { try { Class cls = Classes .forNameByThread("org.springframework.transaction.support.TransactionSynchronizationManager"); _threadLocals = new Object[7]; _threadLocals[0] = getThreadLocal(cls, "resources").get(); _threadLocals[1] = getThreadLocal(cls, "synchronizations").get(); _threadLocals[2] = getThreadLocal(cls, "currentTransactionName").get(); _threadLocals[3] = getThreadLocal(cls, "currentTransactionReadOnly").get(); _threadLocals[4] = getThreadLocal(cls, "actualTransactionActive").get(); //20070907, Henri Chen: bug 1785457, hibernate3 might not used try { cls = Classes.forNameByThread("org.springframework.orm.hibernate3.SessionFactoryUtils"); _threadLocals[5] = getThreadLocal(cls, "deferredCloseHolder").get(); } catch (ClassNotFoundException ex) { //ignore if hibernate 3 is not used. } cls = Classes.forNameByThread("org.springframework.transaction.interceptor.TransactionAspectSupport"); //Spring 1.2.8 and Spring 2.0.x, the ThreadLocal field name has changed, default use 2.0.x //2.0.x transactionInfoHolder //1.2.8 currentTransactionInfo try { _threadLocals[6] = getThreadLocal(cls, "transactionInfoHolder").get(); } catch (SystemException ex) { if (ex.getCause() instanceof NoSuchFieldException) { _threadLocals[6] = getThreadLocal(cls, "currentTransactionInfo").get(); } else { throw ex; } } } catch (ClassNotFoundException ex) { throw UiException.Aide.wrap(ex); } } @SuppressWarnings("unchecked") private void setThreadLocals() { if (_threadLocals != null) { try { Class cls = Classes .forNameByThread("org.springframework.transaction.support.TransactionSynchronizationManager"); getThreadLocal(cls, "resources").set(_threadLocals[0]); getThreadLocal(cls, "synchronizations").set(_threadLocals[1]); getThreadLocal(cls, "currentTransactionName").set(_threadLocals[2]); getThreadLocal(cls, "currentTransactionReadOnly").set(_threadLocals[3]); getThreadLocal(cls, "actualTransactionActive").set(_threadLocals[4]); //20070907, Henri Chen: bug 1785457, hibernate3 might not used try { cls = Classes.forNameByThread("org.springframework.orm.hibernate3.SessionFactoryUtils"); getThreadLocal(cls, "deferredCloseHolder").set(_threadLocals[5]); } catch (ClassNotFoundException ex) { //ignore if hibernate 3 is not used. } cls = Classes.forNameByThread("org.springframework.transaction.interceptor.TransactionAspectSupport"); //Spring 1.2.8 and Spring 2.0.x, the ThreadLocal field name has changed, default use 2.0.x //2.0.x transactionInfoHolder //1.2.8 currentTransactionInfo try { getThreadLocal(cls, "transactionInfoHolder").set(_threadLocals[6]); } catch (SystemException ex) { if (ex.getCause() instanceof NoSuchFieldException) { getThreadLocal(cls, "currentTransactionInfo").set(_threadLocals[6]); } else { throw ex; } } _threadLocals = null; } catch (ClassNotFoundException ex) { throw UiException.Aide.wrap(ex); } } } private ThreadLocal getThreadLocal(Class cls, String fldname) { return ThreadLocals.getThreadLocal(cls, fldname); } }