001: /*
002: * JBoss, Home of Professional Open Source.
003: * Copyright 2006, Red Hat Middleware LLC, and individual contributors
004: * as indicated by the @author tags. See the copyright.txt file in the
005: * distribution for a full listing of individual contributors.
006: *
007: * This is free software; you can redistribute it and/or modify it
008: * under the terms of the GNU Lesser General Public License as
009: * published by the Free Software Foundation; either version 2.1 of
010: * the License, or (at your option) any later version.
011: *
012: * This software is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: *
017: * You should have received a copy of the GNU Lesser General Public
018: * License along with this software; if not, write to the Free
019: * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
021: */
022: package org.jboss.tm.usertx.client;
023:
024: import java.io.Serializable;
025:
026: import java.rmi.RemoteException;
027:
028: import java.util.LinkedList;
029: import java.util.Hashtable;
030:
031: import javax.naming.InitialContext;
032: import javax.naming.Reference;
033: import javax.naming.Referenceable;
034: import javax.naming.NamingException;
035:
036: import javax.transaction.UserTransaction;
037: import javax.transaction.Transaction;
038: import javax.transaction.Status;
039: import javax.transaction.NotSupportedException;
040: import javax.transaction.SystemException;
041: import javax.transaction.RollbackException;
042: import javax.transaction.HeuristicMixedException;
043: import javax.transaction.HeuristicRollbackException;
044:
045: import org.jboss.tm.TransactionPropagationContextFactory;
046:
047: import org.jboss.tm.usertx.interfaces.UserTransactionSession;
048: import org.jboss.tm.usertx.interfaces.UserTransactionSessionFactory;
049: import org.jboss.naming.NamingContextFactory;
050:
051: /**
052: * The client-side UserTransaction implementation. This will delegate all
053: * UserTransaction calls to the server.
054: *
055: * <em>Warning:</em> This is only for stand-alone clients that do not have their
056: * own transaction service. No local work is done in the context of transactions
057: * started here, only work done in beans at the server. Instantiating objects of
058: * this class outside the server will change the JRMP GenericProxy so that
059: * outgoing calls use the propagation contexts of the transactions started
060: * here.
061: * @author <a href="mailto:osh@sparre.dk">Ole Husgaard</a>
062: * @author Scott.Stark@jboss.org
063: * @version $Revision: 57209 $
064: */
065: public class ClientUserTransaction implements UserTransaction,
066: TransactionPropagationContextFactory, Referenceable,
067: Serializable {
068: // Static --------------------------------------------------------
069: /** @since at least jboss-3.2.0 */
070: private static final long serialVersionUID = 1747989355209242872L;
071:
072: /**
073: * Our singleton instance.
074: */
075: private static ClientUserTransaction singleton = null;
076:
077: /**
078: * Return a reference to the singleton instance.
079: */
080: public static ClientUserTransaction getSingleton() {
081: if (singleton == null)
082: singleton = new ClientUserTransaction();
083: return singleton;
084: }
085:
086: // Constructors --------------------------------------------------
087:
088: /**
089: * Create a new instance.
090: */
091: private ClientUserTransaction() {
092: }
093:
094: // Public --------------------------------------------------------
095:
096: //
097: // implements interface UserTransaction
098: //
099:
100: public void begin() throws NotSupportedException, SystemException {
101: ThreadInfo info = getThreadInfo();
102:
103: try {
104: Object tpc = getSession().begin(info.getTimeout());
105: info.push(tpc);
106: } catch (SystemException e) {
107: throw e;
108: } catch (RemoteException e) {
109: // destroy session gone bad.
110: destroySession();
111: throw new SystemException(e.toString());
112: } catch (Exception e) {
113: throw new SystemException(e.toString());
114: }
115: }
116:
117: public void commit() throws RollbackException,
118: HeuristicMixedException, HeuristicRollbackException,
119: SecurityException, IllegalStateException, SystemException {
120: ThreadInfo info = getThreadInfo();
121:
122: try {
123: getSession().commit(info.getTpc());
124: info.pop();
125: } catch (RollbackException e) {
126: info.pop();
127: throw e;
128: } catch (HeuristicMixedException e) {
129: throw e;
130: } catch (HeuristicRollbackException e) {
131: throw e;
132: } catch (SecurityException e) {
133: throw e;
134: } catch (SystemException e) {
135: throw e;
136: } catch (IllegalStateException e) {
137: throw e;
138: } catch (RemoteException e) {
139: // destroy session gone bad.
140: destroySession();
141: throw new SystemException(e.toString());
142: } catch (Exception e) {
143: throw new SystemException(e.toString());
144: }
145: }
146:
147: public void rollback() throws SecurityException,
148: IllegalStateException, SystemException {
149: ThreadInfo info = getThreadInfo();
150:
151: try {
152: getSession().rollback(info.getTpc());
153: info.pop();
154: } catch (SecurityException e) {
155: throw e;
156: } catch (SystemException e) {
157: throw e;
158: } catch (IllegalStateException e) {
159: throw e;
160: } catch (RemoteException e) {
161: // destroy session gone bad.
162: destroySession();
163: throw new SystemException(e.toString());
164: } catch (Exception e) {
165: throw new SystemException(e.toString());
166: }
167: }
168:
169: public void setRollbackOnly() throws IllegalStateException,
170: SystemException {
171: ThreadInfo info = getThreadInfo();
172:
173: try {
174: getSession().setRollbackOnly(info.getTpc());
175: } catch (SystemException e) {
176: throw e;
177: } catch (IllegalStateException e) {
178: throw e;
179: } catch (RemoteException e) {
180: // destroy session gone bad.
181: destroySession();
182: throw new SystemException(e.toString());
183: } catch (Exception e) {
184: throw new SystemException(e.toString());
185: }
186: }
187:
188: public int getStatus() throws SystemException {
189: ThreadInfo info = getThreadInfo();
190: Object tpc = info.getTpc();
191:
192: if (tpc == null) {
193: return Status.STATUS_NO_TRANSACTION;
194: }
195:
196: try {
197: return getSession().getStatus(tpc);
198: } catch (SystemException e) {
199: throw e;
200: } catch (RemoteException e) {
201: // destroy session gone bad.
202: destroySession();
203: throw new SystemException(e.toString());
204: } catch (Exception e) {
205: throw new SystemException(e.toString());
206: }
207: }
208:
209: public void setTransactionTimeout(int seconds)
210: throws SystemException {
211: getThreadInfo().setTimeout(seconds);
212: }
213:
214: //
215: // implements interface TransactionPropagationContextFactory
216: //
217:
218: public Object getTransactionPropagationContext() {
219: return getThreadInfo().getTpc();
220: }
221:
222: public Object getTransactionPropagationContext(Transaction tx) {
223: // No need to implement in a stand-alone client.
224: throw new InternalError("Should not have been used.");
225: }
226:
227: //
228: // implements interface Referenceable
229: //
230:
231: public Reference getReference() throws NamingException {
232: Reference ref = new Reference(
233: "org.jboss.tm.usertx.client.ClientUserTransaction",
234: "org.jboss.tm.usertx.client.ClientUserTransactionObjectFactory",
235: null);
236:
237: return ref;
238: }
239:
240: // Private -------------------------------------------------------
241:
242: /**
243: * The RMI remote interface to the real tx service session at the server.
244: */
245: private UserTransactionSession session = null;
246:
247: /**
248: * Storage of per-thread information used here.
249: */
250: private transient ThreadLocal threadInfo = new ThreadLocal();
251:
252: /**
253: * Create a new session.
254: */
255: private synchronized void createSession() {
256: // Destroy any old session.
257: if (session != null)
258: destroySession();
259:
260: try {
261: // Get a reference to the UT session factory.
262: UserTransactionSessionFactory factory;
263: Hashtable env = (Hashtable) NamingContextFactory.lastInitialContextEnv
264: .get();
265: InitialContext ctx = new InitialContext(env);
266: factory = (UserTransactionSessionFactory) ctx
267: .lookup("UserTransactionSessionFactory");
268: // Call factory to get a UT session.
269: session = factory.newInstance();
270: } catch (Exception ex) {
271: throw new RuntimeException("UT factory lookup failed", ex);
272: }
273: }
274:
275: /**
276: * Destroy the current session.
277: */
278: private synchronized void destroySession() {
279: if (session != null) {
280: try {
281: session.destroy();
282: } catch (RemoteException ex) {
283: // Ignore.
284: }
285: session = null;
286: }
287: }
288:
289: /**
290: * Get the session. This will create a session, if one does not already
291: * exist.
292: */
293: private synchronized UserTransactionSession getSession() {
294: if (session == null)
295: createSession();
296: return session;
297: }
298:
299: /**
300: * Return the per-thread information, possibly creating it if needed.
301: */
302: private ThreadInfo getThreadInfo() {
303: ThreadInfo ret = (ThreadInfo) threadInfo.get();
304:
305: if (ret == null) {
306: ret = new ThreadInfo();
307: threadInfo.set(ret);
308: }
309:
310: return ret;
311: }
312:
313: // Inner classes -------------------------------------------------
314:
315: /**
316: * Per-thread data holder class. This stores the stack of TPCs for the
317: * transactions started by this thread.
318: */
319: private class ThreadInfo {
320: /**
321: * A stack of TPCs for transactions started by this thread. If the
322: * underlying service does not support nested transactions, its size is
323: * never greater than 1. Last element of the list denotes the stack top.
324: */
325: private LinkedList tpcStack = new LinkedList();
326:
327: /**
328: * The timeout value (in seconds) for new transactions started by this
329: * thread.
330: */
331: private int timeout = 0;
332:
333: /**
334: * Override to terminate any transactions that the thread may have
335: * forgotten.
336: */
337: protected void finalize() throws Throwable {
338: try {
339: while (!tpcStack.isEmpty()) {
340: Object tpc = getTpc();
341: pop();
342:
343: try {
344: getSession().rollback(tpc);
345: } catch (Exception ex) {
346: // ignore
347: }
348: }
349: } catch (Throwable t) {
350: // ignore
351: }
352: super .finalize();
353: }
354:
355: /**
356: * Push the TPC of a newly started transaction on the stack.
357: */
358: void push(Object tpc) {
359: tpcStack.addLast(tpc);
360: }
361:
362: /**
363: * Pop the TPC of a newly terminated transaction from the stack.
364: */
365: void pop() {
366: tpcStack.removeLast();
367: }
368:
369: /**
370: * Get the TPC at the top of the stack.
371: */
372: Object getTpc() {
373: return (tpcStack.isEmpty()) ? null : tpcStack.getLast();
374: }
375:
376: /**
377: * Return the default transaction timeout in seconds to use for new
378: * transactions started by this thread. A value of <code>0</code> means
379: * that a default timeout value should be used.
380: */
381: int getTimeout() {
382: return timeout;
383: }
384:
385: /**
386: * Set the default transaction timeout in seconds to use for new
387: * transactions started by this thread.
388: */
389: void setTimeout(int seconds) {
390: timeout = seconds;
391: }
392: }
393:
394: }
|