001: /*
002: * CoadunationLib: The coaduntion implementation library.
003: * Copyright (C) 2006 Rift IT Contracting
004: *
005: * This library is free software; you can redistribute it and/or
006: * modify it under the terms of the GNU Lesser General Public
007: * License as published by the Free Software Foundation; either
008: * version 2.1 of the License, or (at your option) any later version.
009: *
010: * This library is distributed in the hope that it will be useful,
011: * but WITHOUT ANY WARRANTY; without even the implied warranty of
012: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
013: * Lesser General Public License for more details.
014: *
015: * You should have received a copy of the GNU Lesser General Public
016: * License along with this library; if not, write to the Free Software
017: * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
018: *
019: * TransactionRMICache.java
020: */
021:
022: // package path
023: package com.rift.coad.lib.deployment.rmi;
024:
025: // java imports
026: import java.util.ArrayList;
027: import java.util.Date;
028: import java.util.HashMap;
029: import java.util.Iterator;
030: import java.util.List;
031: import java.util.Map;
032: import java.util.concurrent.ConcurrentHashMap;
033: import javax.rmi.PortableRemoteObject;
034: import javax.transaction.xa.XAException;
035: import javax.transaction.xa.XAResource;
036: import javax.transaction.xa.Xid;
037:
038: // logging import
039: import org.apache.log4j.Logger;
040:
041: // coad imports
042: import com.rift.coad.lib.cache.Cache;
043: import com.rift.coad.lib.cache.CacheEntry;
044: import com.rift.coad.lib.common.RandomGuid;
045: import com.rift.coad.lib.configuration.ConfigurationFactory;
046: import com.rift.coad.lib.configuration.Configuration;
047: import com.rift.coad.lib.thread.ThreadStateMonitor;
048: import com.rift.coad.util.lock.LockRef;
049: import com.rift.coad.util.lock.ObjectLockFactory;
050: import com.rift.coad.util.transaction.TransactionManager;
051:
052: /**
053: * This object is responsible for controlling the rmi cache entries bound to
054: * various transactions.
055: *
056: * @author Brett Chaldecott
057: */
058: public class TransactionRMICache implements Cache, XAResource {
059:
060: /**
061: * This object tracks the changes for a transaction
062: */
063: public class ChangeEntry {
064: // private member variables
065: private Xid transactionId = null;
066: private List newEntries = new ArrayList();
067:
068: /**
069: * The constructor of the change entry
070: *
071: * @param transactionId The id of the transaction.
072: */
073: public ChangeEntry(Xid transactionId) {
074: this .transactionId = transactionId;
075: }
076:
077: /**
078: * This method returns the transaction id.
079: *
080: * @return The object containing the transaction id.
081: */
082: public Xid getTransactionId() {
083: return transactionId;
084: }
085:
086: /**
087: * This method addes the cache entry.
088: *
089: * @param entry The RMI cache entry to add.
090: */
091: public void addEntry(RMICacheEntry entry) {
092: newEntries.add(entry);
093: }
094:
095: /**
096: * This method returns the list of added entries.
097: */
098: public List getEntries() {
099: return newEntries;
100: }
101: }
102:
103: // class constants
104: private final static String CACHE_EXPIRY_TIME = "rmi_cache_expiry";
105: private final static long CACHE_EXPIRY_TIME_DEFAULT = 60 * 1000;
106:
107: // private member variables
108: protected static Logger log = Logger
109: .getLogger(TransactionRMICache.class.getName());
110:
111: // private member variables
112: private ThreadLocal currentTransaction = new ThreadLocal();
113: private Map cacheEntries = new ConcurrentHashMap();
114: private Map transactionChanges = new ConcurrentHashMap();
115: private long defaultCacheExpiryTime = 0;
116: private ThreadStateMonitor status = new ThreadStateMonitor();
117:
118: /**
119: * Creates a new instance of TransactionRMICache
120: */
121: public TransactionRMICache() {
122: }
123:
124: /**
125: * This method is called to perform garbage collection on the cache entries.
126: */
127: public void garbageCollect() {
128: Map currentEntryList = new HashMap();
129: synchronized (cacheEntries) {
130: currentEntryList.putAll(cacheEntries);
131: }
132: Date expiryDate = new Date();
133: for (Iterator iter = currentEntryList.keySet().iterator(); iter
134: .hasNext();) {
135: Object key = iter.next();
136: RMICacheEntry cacheEntry = (RMICacheEntry) currentEntryList
137: .get(key);
138: if (cacheEntry.isExpired(expiryDate)) {
139: try {
140: PortableRemoteObject.unexportObject(cacheEntry
141: .getRemoteInterface());
142: synchronized (cacheEntries) {
143: cacheEntries.remove(key);
144: }
145: cacheEntry.cacheRelease();
146: } catch (java.rmi.NoSuchObjectException ex) {
147: log.warn("The object was never exported : "
148: + ex.getMessage(), ex);
149: // remove from cache
150: synchronized (cacheEntries) {
151: cacheEntries.remove(key);
152: }
153: cacheEntry.cacheRelease();
154: } catch (Exception ex) {
155: log.error(
156: "Failed to remove a cache entry because : "
157: + ex.getMessage(), ex);
158: }
159: }
160: }
161: }
162:
163: /**
164: * This method is called to forcibly remove everything from the cache.
165: */
166: public void clear() {
167: LockRef lockRef = null;
168: try {
169: lockRef = ObjectLockFactory.getInstance().acquireReadLock(
170: this );
171: status.terminate(false);
172: Map currentEntryList = new HashMap();
173: synchronized (cacheEntries) {
174: currentEntryList.putAll(cacheEntries);
175: cacheEntries.clear();
176: }
177: for (Iterator iter = currentEntryList.keySet().iterator(); iter
178: .hasNext();) {
179: Object key = iter.next();
180: RMICacheEntry cacheEntry = (RMICacheEntry) currentEntryList
181: .get(key);
182: try {
183: PortableRemoteObject.unexportObject(cacheEntry
184: .getRemoteInterface());
185: } catch (java.rmi.NoSuchObjectException ex) {
186: log.warn("The object was never exported : "
187: + ex.getMessage(), ex);
188: } catch (Exception ex) {
189: log.error(
190: "Failed to remove a cache entry because : "
191: + ex.getMessage(), ex);
192: }
193: try {
194: cacheEntry.cacheRelease();
195: } catch (Exception ex) {
196: log.error("Failed to release cache info : "
197: + ex.getMessage(), ex);
198: }
199: }
200: } catch (Exception ex) {
201: log
202: .error("Failed clear the cache : "
203: + ex.getMessage(), ex);
204: } finally {
205: try {
206: if (lockRef != null) {
207: lockRef.release();
208: }
209: } catch (Exception ex) {
210: log.error("Failed to release the lock : "
211: + ex.getMessage(), ex);
212: }
213: }
214: }
215:
216: /**
217: * This method is responsible for adding an entry to the cache.
218: *
219: * @param entry The entry to add to the cache.
220: */
221: public void addCacheEntry(long timeout, CacheEntry entry)
222: throws RMIException {
223: try {
224: checkStatus();
225: TransactionManager.getInstance().bindResource(this , false);
226: long cacheTimeout = timeout;
227: if (timeout == -1) {
228: cacheTimeout = defaultCacheExpiryTime;
229: }
230: RMICacheEntry newEntry = new RMICacheEntry(cacheTimeout,
231: entry);
232: ChangeEntry changeEntry = (ChangeEntry) transactionChanges
233: .get(currentTransaction.get());
234: changeEntry.addEntry(newEntry);
235: } catch (Exception ex) {
236: log.error("Failed to add the cache entry : "
237: + ex.getMessage(), ex);
238: throw new RMIException("Failed to add the cache entry : "
239: + ex.getMessage(), ex);
240: }
241: }
242:
243: /**
244: * This method will check to see if the cache has been terminated or not.
245: *
246: * @exception BeanException
247: */
248: private void checkStatus() throws RMIException {
249: if (status.isTerminated()) {
250: throw new RMIException("The RMI cache has been shut down.");
251: }
252: }
253:
254: /**
255: * This mehtod returns true if the cache contains the checked entry.
256: *
257: * @return TRUE if the cache contains the checked entry.
258: * @param cacheEntry The entry to perform the check for.
259: */
260: public boolean contains(Object cacheEntry) {
261: if (!status.isTerminated()) {
262: return cacheEntries.containsKey(cacheEntry);
263: }
264: return false;
265: }
266:
267: /**
268: * This method returns the bean cache entry.
269: *
270: * @return The reference to the bean cache object.
271: * @param key The key to retrieve.
272: * @exception BeanException
273: */
274: public void commit(Xid xid, boolean b) throws XAException {
275: try {
276: ChangeEntry changeEntry = (ChangeEntry) transactionChanges
277: .get(xid);
278: List entries = changeEntry.getEntries();
279: for (Iterator iter = entries.iterator(); iter.hasNext();) {
280: RMICacheEntry rmiCacheEntry = (RMICacheEntry) iter
281: .next();
282: cacheEntries.put(rmiCacheEntry.getCacheEntry(),
283: rmiCacheEntry);
284: }
285: } catch (Exception ex) {
286: log.error("Failed to commit the changes : "
287: + ex.getMessage(), ex);
288: throw new XAException("Failed to commit the changes : "
289: + ex.getMessage());
290: }
291: }
292:
293: /**
294: * The resource manager has dissociated this object from the transaction.
295: *
296: * @param xid The id of the transaction that is getting ended.
297: * @param flags The flags associated with this operation.
298: * @exception XAException
299: */
300: public void end(Xid xid, int i) throws XAException {
301: }
302:
303: /**
304: * The transaction has been completed and must be forgotten.
305: *
306: * @param xid The id of the transaction to forget.
307: * @exception XAException
308: */
309: public void forget(Xid xid) throws XAException {
310: try {
311: transactionChanges.remove(xid);
312: } catch (Exception ex) {
313: log.error("Failed to forget the changes : "
314: + ex.getMessage(), ex);
315: throw new XAException("Failed to forget the changes : "
316: + ex.getMessage());
317: }
318: }
319:
320: /**
321: * This method returns the transaction timeout for this object.
322: *
323: * @return The int containing the transaction timeout.
324: * @exception XAException
325: */
326: public int getTransactionTimeout() throws XAException {
327: return -1;
328: }
329:
330: /**
331: * This method returns true if this object is the resource manager getting
332: * queried.
333: *
334: * @return TRUE if this is the resource manager, FALSE if not.
335: * @param xaResource The resource to perform the check against.
336: * @exception XAException
337: */
338: public boolean isSameRM(XAResource xAResource) throws XAException {
339: return this == xAResource;
340: }
341:
342: /**
343: * This is called before a transaction is committed.
344: *
345: * @return The results of the transaction.
346: * @param xid The id of the transaction to check against.
347: * @exception XAException
348: */
349: public int prepare(Xid xid) throws XAException {
350: return XAResource.XA_OK;
351: }
352:
353: /**
354: * This method returns the list of transaction branches for this resource
355: * manager.
356: *
357: * @return The list of resource branches.
358: * @param flags The flags
359: * @exception XAException
360: */
361: public Xid[] recover(int i) throws XAException {
362: return null;
363: }
364:
365: /**
366: * This method is called to roll back the specified transaction.
367: *
368: * @param xid The id of the transaction to roll back.
369: * @exception XAException
370: */
371: public void rollback(Xid xid) throws XAException {
372: try {
373: ChangeEntry changeEntry = (ChangeEntry) transactionChanges
374: .get(xid);
375: List entries = changeEntry.getEntries();
376: for (Iterator iter = entries.iterator(); iter.hasNext();) {
377: RMICacheEntry rmiCacheEntry = (RMICacheEntry) iter
378: .next();
379: try {
380: PortableRemoteObject.unexportObject(rmiCacheEntry
381: .getRemoteInterface());
382: } catch (java.rmi.NoSuchObjectException ex) {
383: log.warn("The object was never exported : "
384: + ex.getMessage(), ex);
385: } catch (Exception ex) {
386: log.error(
387: "Failed to rollback a cache entry because : "
388: + ex.getMessage(), ex);
389: }
390: try {
391: rmiCacheEntry.cacheRelease();
392: } catch (Exception ex) {
393: log.error("Failed to release the entry");
394: }
395: }
396: transactionChanges.remove(xid);
397: } catch (Exception ex) {
398: log.error("Failed to rollback the changes : "
399: + ex.getMessage(), ex);
400: throw new XAException("Failed to rollback the changes : "
401: + ex.getMessage());
402: }
403: }
404:
405: /**
406: * This method sets the transaction timeout for this resource manager.
407: *
408: * @return TRUE if the transaction timeout can be set successfully.
409: * @param transactionTimeout The new transaction timeout value.
410: * @exception XAException
411: */
412: public boolean setTransactionTimeout(int i) throws XAException {
413: return true;
414: }
415:
416: /**
417: * This method is called to start a transaction on a resource manager.
418: *
419: * @param xid The id of the new transaction.
420: * @param flags The flags associated with the transaction.
421: * @exception XAException
422: */
423: public void start(Xid xid, int i) throws XAException {
424: if (!transactionChanges.containsKey(xid)) {
425: transactionChanges.put(xid, new ChangeEntry(xid));
426: }
427: currentTransaction.set(xid);
428: }
429:
430: }
|