001: /* ThreadLocalListener.java
002:
003: {{IS_NOTE
004: Purpose:
005:
006: Description:
007:
008: History:
009: Sun Jun 10 23:48:51 2007, Created by henrichen
010: }}IS_NOTE
011:
012: Copyright (C) 2007 Potix Corporation. All Rights Reserved.
013:
014: {{IS_RIGHT
015: This program is distributed under GPL Version 2.0 in the hope that
016: it will be useful, but WITHOUT ANY WARRANTY.
017: }}IS_RIGHT
018: */
019: package org.zkoss.zkplus.util;
020:
021: import org.zkoss.zk.ui.WebApp;
022: import org.zkoss.zk.ui.Component;
023: import org.zkoss.zk.ui.UiException;
024: import org.zkoss.zk.ui.Execution;
025: import org.zkoss.zk.ui.Executions;
026: import org.zkoss.zk.ui.event.Event;
027: import org.zkoss.zk.ui.event.EventThreadInit;
028: import org.zkoss.zk.ui.event.EventThreadResume;
029: import org.zkoss.zk.ui.event.EventThreadCleanup;
030: import org.zkoss.zk.ui.util.Configuration;
031: import org.zkoss.lang.Classes;
032: import org.zkoss.lang.ThreadLocals;
033: import org.zkoss.lang.SystemException;
034: import org.zkoss.util.CollectionsX;
035: import org.zkoss.util.logging.Log;
036:
037: import java.util.Map;
038: import java.util.Map.Entry;
039: import java.util.Iterator;
040: import java.util.HashMap;
041: import java.lang.reflect.Field;
042:
043: import java.util.List;
044: import java.util.Collection;
045:
046: /**
047: * <p>Listener to make sure servlet thread and ZK event thread got the same ThreadLocal values. You
048: * have to declare this listener in WEB-INF/zk.xml as follows.</p>
049: * <pre><code>
050: * <listener>
051: * <description>ThreadLocal Synchronization Listener</description>
052: * <listener-class>org.zkoss.zkplus.util.ThreadLocalListener</listener-class>
053: * </listener>
054: * </code></pre>
055: * <p>Besides that, you have to specify what ThreadLocal variables you want to sync. They are also
056: * spcified in WEB-INF/zk.xml file in the form as below.</p>
057: * <pre><code>
058: * <preference>
059: * <name>ThreadLocal</name>
060: * <value>
061: * class1=field1,field2,...;
062: * class2=field1,field2,...;
063: * ...
064: * </value>
065: * </preference>
066: * </code></pre>
067: * <p>For example, to support synchronizing Spring's thread bounded resources, you have to specify the following
068: * ThreadLocal variables:</p>
069: * <pre><code>
070: * <preference>
071: * <name>ThreadLocal</name>
072: * <value>
073: * org.springframework.transaction.support.TransactionSynchronizationManager=resources,synchronizations,currentTransactionName,currentTransactionReadOnly,actualTransactionActive;
074: * org.springframework.orm.hibernate3.SessionFactoryUtils=deferredCloseHolder;
075: * org.springframework.transaction.interceptor.TransactionAspectSupport=transactionInfoHolder; <!-- ver. 2+ -->
076: * <!--org.springframework.transaction.interceptor.TransactionAspectSupport=currentTransactionInfo; ver. 1.28 -->
077: * </value>
078: * </preference>
079: * <p>
080: * <p>Another example, when you specify the Spring's bean as scope="session", you have to specify the following
081: * ThreadLocal variables since Spring 2.0 use RequestContextHolder to handle the bean's scope.</p>
082: * <pre><code>
083: * <preference>
084: * <name>ThreadLocal</name>
085: * <value>
086: * org.springframework.web.context.request.RequestContextHolder=requestAttributesHolder,inheritableRequestAttributesHolder;
087: * </value>
088: * </code></pre>
089: *
090: * @author henrichen
091: * @since 2.4.1
092: */
093: public class ThreadLocalListener implements EventThreadInit,
094: EventThreadCleanup, EventThreadResume {
095: private static final Log log = Log
096: .lookup(ThreadLocalListener.class);
097: private Map _fieldsMap; //(class name, String[] of fields)
098: private Map _threadLocalsMap; //(class name, ThreadLocal_Contents[] for fields)
099:
100: public ThreadLocalListener() {
101: final WebApp app = Executions.getCurrent().getDesktop()
102: .getWebApp();
103: _fieldsMap = (Map) app
104: .getAttribute("zkplus.util.ThreadLocalListener.fieldsMap");
105: if (_fieldsMap == null) {
106: _fieldsMap = new HashMap(8);
107: app.setAttribute(
108: "zkplus.util.ThreadLocalListener.fieldsMap",
109: _fieldsMap);
110: //read preference
111: final Configuration config = app.getConfiguration();
112: final String val = config
113: .getPreference("ThreadLocal", null);
114: if (val != null) {
115: final Collection klassSets = CollectionsX.parse(null,
116: val, ';');
117: for (Iterator its = klassSets.iterator(); its.hasNext();) {
118: final String klassSetStr = (String) its.next();
119: final Collection klassSet = CollectionsX.parse(
120: null, klassSetStr, '=');
121: final Iterator itz = klassSet.iterator();
122: final String klass = (String) itz.next();
123: final String fieldsStr = (String) itz.next();
124: final Collection fields = CollectionsX.parse(null,
125: fieldsStr, ',');
126: _fieldsMap.put(klass, fields
127: .toArray(new String[fields.size()]));
128: }
129: }
130: }
131: _threadLocalsMap = new HashMap(_fieldsMap.size());
132: }
133:
134: //-- EventThreadInit --//
135: public void prepare(Component comp, Event evt) {
136: getThreadLocals(); //get from servlet thread's ThreadLocal
137: }
138:
139: public boolean init(Component comp, Event evt) {
140: setThreadLocals(); //copy to event thread's ThreadLocal
141: return true;
142: }
143:
144: //-- EventThreadCleanup --//
145: public void cleanup(Component comp, Event evt, List errs) {
146: getThreadLocals(); //get from event thread's ThreadLocal
147: //we don't handle the exception since the ZK engine will throw it again!
148: }
149:
150: public void complete(Component comp, Event evt) {
151: setThreadLocals(); //copy to servlet thread's ThreadLocal
152: }
153:
154: //-- EventThreadResume --//
155: public void beforeResume(Component comp, Event evt) {
156: getThreadLocals(); //get from servlet thread's ThreadLocal
157: }
158:
159: public void afterResume(Component comp, Event evt) {
160: setThreadLocals(); //copy to event thread's ThreadLocal
161: }
162:
163: public void abortResume(Component comp, Event evt) {
164: //do nothing
165: }
166:
167: //-- utilities --//
168: private void getThreadLocals() {
169: for (final Iterator it = _fieldsMap.entrySet().iterator(); it
170: .hasNext();) {
171: final Entry me = (Entry) it.next();
172: try {
173: final String clsName = (String) me.getKey();
174: final Class cls = Classes.forNameByThread(clsName);
175: final String[] fields = (String[]) me.getValue();
176: final Object[] threadLocals = new Object[fields.length];
177: _threadLocalsMap.put(clsName, threadLocals);
178: for (int j = 0; j < threadLocals.length; ++j) {
179: try {
180: threadLocals[j] = getThreadLocal(cls, fields[j])
181: .get();
182: } catch (SystemException ex) {
183: ex.printStackTrace();
184: //ignore
185: continue;
186: }
187: }
188: } catch (ClassNotFoundException ex) {
189: ex.printStackTrace();
190: //ignore
191: continue;
192: }
193: }
194: }
195:
196: private void setThreadLocals() {
197: for (Iterator it = _threadLocalsMap.entrySet().iterator(); it
198: .hasNext();) {
199: final Entry me = (Entry) it.next();
200: try {
201: final String clsName = (String) me.getKey();
202: final Class cls = Classes.forNameByThread(clsName);
203: final Object[] threadLocals = (Object[]) me.getValue();
204: final String[] fields = (String[]) _fieldsMap
205: .get(clsName);
206:
207: for (int j = 0; j < threadLocals.length; ++j) {
208: getThreadLocal(cls, fields[j]).set(threadLocals[j]);
209: }
210: } catch (ClassNotFoundException ex) {
211: ex.printStackTrace();
212: continue;
213: }
214: }
215: _threadLocalsMap.clear();
216: }
217:
218: private ThreadLocal getThreadLocal(Class cls, String fldname) {
219: return ThreadLocals.getThreadLocal(cls, fldname);
220: }
221: }
|