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: * TransactionProxyCache.java
020: */
021:
022: // package path
023: package com.rift.coad.lib.bean;
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.Vector;
033: import java.util.concurrent.ConcurrentHashMap;
034: import javax.rmi.PortableRemoteObject;
035: import javax.transaction.xa.XAException;
036: import javax.transaction.xa.XAResource;
037: import javax.transaction.xa.Xid;
038:
039: // logging import
040: import org.apache.log4j.Logger;
041:
042: // coadunation imports
043: import com.rift.coad.lib.cache.Cache;
044: import com.rift.coad.lib.cache.CacheEntry;
045: import com.rift.coad.lib.common.RandomGuid;
046: import com.rift.coad.lib.configuration.ConfigurationFactory;
047: import com.rift.coad.lib.configuration.Configuration;
048: import com.rift.coad.lib.thread.ThreadStateMonitor;
049: import com.rift.coad.util.lock.LockRef;
050: import com.rift.coad.util.lock.ObjectLockFactory;
051: import com.rift.coad.util.transaction.TransactionManager;
052:
053: /**
054: * This object acts as the transaction proxy cache for the container.
055: *
056: * @author Brett Chaldecott
057: */
058: public class TransactionProxyCache 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 proxy cache entry to add.
090: */
091: public void addEntry(ProxyCacheEntry 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 = "proxy_cache_expiry";
105: private final static long CACHE_EXPIRY_TIME_DEFAULT = 60 * 1000;
106:
107: // private member variables
108: protected static Logger log = Logger.getLogger(ProxyCache.class
109: .getName());
110:
111: // private member variables
112: private ThreadLocal currentTransaction = new ThreadLocal();
113: private ThreadStateMonitor status = new ThreadStateMonitor();
114: private Map cacheEntries = new ConcurrentHashMap();
115: private long defaultCacheExpiryTime = 0;
116: private Map transactionChanges = new ConcurrentHashMap();
117:
118: /**
119: * Creates a new instance of TransactionProxyCache
120: */
121: public TransactionProxyCache() throws BeanException {
122: try {
123: Configuration config = ConfigurationFactory.getInstance()
124: .getConfig(ProxyCache.class);
125: defaultCacheExpiryTime = config.getLong(CACHE_EXPIRY_TIME,
126: CACHE_EXPIRY_TIME_DEFAULT);
127: } catch (Exception ex) {
128: log.error(
129: "Failed to start the TransactionProxyCache object "
130: + "because : " + ex.getMessage(), ex);
131: throw new BeanException(
132: "Failed to start the TransactionProxyCache object "
133: + "because : " + ex.getMessage(), ex);
134: }
135: }
136:
137: /**
138: * This method is called to perform garbage collection on the cache entries.
139: */
140: public void garbageCollect() {
141: Map cacheEntries = new HashMap();
142: cacheEntries.putAll(this .cacheEntries);
143:
144: // the start time
145: Date currentTime = new Date();
146: for (Iterator iter = cacheEntries.keySet().iterator(); iter
147: .hasNext();) {
148: Object key = iter.next();
149: ProxyCacheEntry cacheEntry = (ProxyCacheEntry) cacheEntries
150: .get(key);
151: if (cacheEntry.isExpired(currentTime)) {
152: synchronized (this .cacheEntries) {
153: this .cacheEntries.remove(key);
154: }
155: cacheEntry.cacheRelease();
156: }
157: }
158: }
159:
160: /**
161: * This method is called to forcibly remove everything from the cache.
162: */
163: public void clear() {
164: LockRef lockRef = null;
165: try {
166: lockRef = ObjectLockFactory.getInstance().acquireReadLock(
167: this );
168: Map cacheEntries = new HashMap();
169: status.terminate(false);
170: cacheEntries.putAll(this .cacheEntries);
171: this .cacheEntries.clear();
172:
173: for (Iterator iter = cacheEntries.keySet().iterator(); iter
174: .hasNext();) {
175: ProxyCacheEntry cacheEntry = (ProxyCacheEntry) cacheEntries
176: .get(iter.next());
177: try {
178: cacheEntry.cacheRelease();
179: } catch (Exception ex) {
180: log.error("Failed to release the cache entry : "
181: + ex.getMessage(), ex);
182: }
183: }
184: } catch (Exception ex) {
185: log
186: .error("Failed clear the cache : "
187: + ex.getMessage(), ex);
188: } finally {
189: try {
190: if (lockRef != null) {
191: lockRef.release();
192: }
193: } catch (Exception ex) {
194: log.error("Failed to release the lock : "
195: + ex.getMessage(), ex);
196: }
197: }
198: }
199:
200: /**
201: * This method adds a new entry to the proxy cache.
202: *
203: * @param timeout The timeout for this object.
204: * @param proxy The proxy to add to the cache.
205: * @param handler The handler to perform the check for.
206: */
207: public void addCacheEntry(long timeout, Object proxy,
208: CacheEntry handler) throws BeanException {
209: try {
210: checkStatus();
211: TransactionManager.getInstance().bindResource(this , false);
212: long cacheTimeout = timeout;
213: if (timeout == -1) {
214: cacheTimeout = defaultCacheExpiryTime;
215: }
216: ProxyCacheEntry newEntry = new ProxyCacheEntry(timeout,
217: proxy, handler);
218: ChangeEntry changeEntry = (ChangeEntry) transactionChanges
219: .get(currentTransaction.get());
220: changeEntry.addEntry(newEntry);
221: } catch (Exception ex) {
222: log.error("Failed to add the cache entry : "
223: + ex.getMessage(), ex);
224: throw new BeanException("Failed to add the cache entry : "
225: + ex.getMessage(), ex);
226: }
227: }
228:
229: /**
230: * This mehtod returns true if the cache contains the checked entry.
231: *
232: * @return TRUE if the cache contains the checked entry.
233: * @param cacheEntry The entry to perform the check for.
234: */
235: public boolean contains(Object cacheEntry) {
236: if (!status.isTerminated()) {
237: return cacheEntries.containsKey(cacheEntry);
238: }
239: return false;
240: }
241:
242: /**
243: * This method returns the bean cache entry.
244: *
245: * @return The reference to the bean cache object.
246: * @param key The key to retrieve.
247: * @exception BeanException
248: */
249: public void commit(Xid xid, boolean b) throws XAException {
250: try {
251: ChangeEntry changeEntry = (ChangeEntry) transactionChanges
252: .get(xid);
253: List entries = changeEntry.getEntries();
254: for (Iterator iter = entries.iterator(); iter.hasNext();) {
255: ProxyCacheEntry proxyCacheEntry = (ProxyCacheEntry) iter
256: .next();
257: cacheEntries.put(proxyCacheEntry.getCacheEntry(),
258: proxyCacheEntry);
259: }
260: } catch (Exception ex) {
261: log.error("Failed to commit the changes : "
262: + ex.getMessage(), ex);
263: throw new XAException("Failed to commit the changes : "
264: + ex.getMessage());
265: }
266: }
267:
268: /**
269: * The resource manager has dissociated this object from the transaction.
270: *
271: * @param xid The id of the transaction that is getting ended.
272: * @param flags The flags associated with this operation.
273: * @exception XAException
274: */
275: public void end(Xid xid, int i) throws XAException {
276: }
277:
278: /**
279: * The transaction has been completed and must be forgotten.
280: *
281: * @param xid The id of the transaction to forget.
282: * @exception XAException
283: */
284: public void forget(Xid xid) throws XAException {
285: try {
286: transactionChanges.remove(xid);
287: } catch (Exception ex) {
288: log.error("Failed to forget the changes : "
289: + ex.getMessage(), ex);
290: throw new XAException("Failed to forget the changes : "
291: + ex.getMessage());
292: }
293: }
294:
295: /**
296: * This method returns the transaction timeout for this object.
297: *
298: * @return The int containing the transaction timeout.
299: * @exception XAException
300: */
301: public int getTransactionTimeout() throws XAException {
302: return -1;
303: }
304:
305: /**
306: * This method returns true if this object is the resource manager getting
307: * queried.
308: *
309: * @return TRUE if this is the resource manager, FALSE if not.
310: * @param xaResource The resource to perform the check against.
311: * @exception XAException
312: */
313: public boolean isSameRM(XAResource xAResource) throws XAException {
314: return this == xAResource;
315: }
316:
317: /**
318: * This is called before a transaction is committed.
319: *
320: * @return The results of the transaction.
321: * @param xid The id of the transaction to check against.
322: * @exception XAException
323: */
324: public int prepare(Xid xid) throws XAException {
325: return XAResource.XA_OK;
326: }
327:
328: /**
329: * This method returns the list of transaction branches for this resource
330: * manager.
331: *
332: * @return The list of resource branches.
333: * @param flags The flags
334: * @exception XAException
335: */
336: public Xid[] recover(int i) throws XAException {
337: return null;
338: }
339:
340: /**
341: * This method is called to roll back the specified transaction.
342: *
343: * @param xid The id of the transaction to roll back.
344: * @exception XAException
345: */
346: public void rollback(Xid xid) throws XAException {
347: try {
348: ChangeEntry changeEntry = (ChangeEntry) transactionChanges
349: .get(xid);
350: List entries = changeEntry.getEntries();
351: for (Iterator iter = entries.iterator(); iter.hasNext();) {
352: ProxyCacheEntry proxyCacheEntry = (ProxyCacheEntry) iter
353: .next();
354: try {
355: proxyCacheEntry.cacheRelease();
356: } catch (Exception ex) {
357: log.error("Failed to release the entry");
358: }
359: }
360: transactionChanges.remove(xid);
361: } catch (Exception ex) {
362: log.error("Failed to rollback the changes : "
363: + ex.getMessage(), ex);
364: throw new XAException("Failed to rollback the changes : "
365: + ex.getMessage());
366: }
367: }
368:
369: /**
370: * This method sets the transaction timeout for this resource manager.
371: *
372: * @return TRUE if the transaction timeout can be set successfully.
373: * @param transactionTimeout The new transaction timeout value.
374: * @exception XAException
375: */
376: public boolean setTransactionTimeout(int i) throws XAException {
377: return true;
378: }
379:
380: /**
381: * This method is called to start a transaction on a resource manager.
382: *
383: * @param xid The id of the new transaction.
384: * @param flags The flags associated with the transaction.
385: * @exception XAException
386: */
387: public void start(Xid xid, int i) throws XAException {
388: if (!transactionChanges.containsKey(xid)) {
389: transactionChanges.put(xid, new ChangeEntry(xid));
390: }
391: currentTransaction.set(xid);
392: }
393:
394: /**
395: * This method will check to see if the cache has been terminated or not.
396: *
397: * @exception BeanException
398: */
399: private void checkStatus() throws BeanException {
400: if (status.isTerminated()) {
401: throw new BeanException(
402: "The proxy cache has been shut down.");
403: }
404: }
405: }
|