001: package org.apache.ojb.broker.locking;
002:
003: /* Copyright 2002-2005 The Apache Software Foundation
004: *
005: * Licensed under the Apache License, Version 2.0 (the "License");
006: * you may not use this file except in compliance with the License.
007: * You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017:
018: import java.io.Serializable;
019: import java.util.Collection;
020: import java.util.HashMap;
021: import java.util.Hashtable;
022: import java.util.Iterator;
023: import java.util.Map;
024:
025: import org.apache.ojb.broker.util.logging.Logger;
026: import org.apache.ojb.broker.util.logging.LoggerFactory;
027: import org.apache.commons.lang.SystemUtils;
028:
029: /**
030: * This implementation of the {@link LockManager} interface supports a simple, fast, non-blocking
031: * pessimistic locking for single JVM applications.
032: *
033: * @version $Id: LockManagerInMemoryImpl.java,v 1.1.2.3 2005/12/21 22:25:32 tomdz Exp $
034: */
035: public class LockManagerInMemoryImpl implements LockManager {
036: private Logger log = LoggerFactory
037: .getLogger(LockManagerInMemoryImpl.class);
038: private static long CLEANUP_FREQUENCY = 1000; // 1000 milliseconds.
039: private static int MAX_LOCKS_TO_CLEAN = 300;
040: /**
041: * MBAIRD: a LinkedHashMap returns objects in the order you put them in,
042: * while still maintaining an O(1) lookup like a normal hashmap. We can then
043: * use this to get the oldest entries very quickly, makes cleanup a breeze.
044: */
045: private HashMap locktable = new HashMap();
046: private LockIsolationManager lockStrategyManager = new LockIsolationManager();
047: private long m_lastCleanupAt = System.currentTimeMillis();
048: private long lockTimeout;
049: private long timeoutCounterRead;
050: private long timeoutCounterWrite;
051:
052: public LockManagerInMemoryImpl() {
053: this .lockTimeout = DEFAULT_LOCK_TIMEOUT;
054: }
055:
056: public long getLockTimeout() {
057: return lockTimeout;
058: }
059:
060: public void setLockTimeout(long timeout) {
061: this .lockTimeout = timeout;
062: }
063:
064: /**
065: * NOOP
066: * @return Always '0'
067: */
068: public long getBlockTimeout() {
069: return 0;
070: }
071:
072: /**
073: * NOOP
074: */
075: public void setBlockTimeout(long timeout) {
076: }
077:
078: public String getLockInfo() {
079: String eol = SystemUtils.LINE_SEPARATOR;
080: StringBuffer msg = new StringBuffer("Class: "
081: + LockManagerInMemoryImpl.class.getName() + eol);
082: msg.append("lock timeout: " + getLockTimeout() + " [ms]" + eol);
083: msg.append("concurrent lock owners: " + locktable.size() + eol);
084: msg.append("timed out write locks: " + timeoutCounterWrite
085: + eol);
086: msg.append("timed out read locks: " + timeoutCounterRead + eol);
087: return msg.toString();
088: }
089:
090: public boolean readLock(Object key, Object resourceId,
091: int isolationLevel) {
092: if (log.isDebugEnabled())
093: log
094: .debug("LM.readLock(tx-" + key + ", " + resourceId
095: + ")");
096: checkTimedOutLocks();
097: LockEntry reader = new LockEntry(resourceId, key, System
098: .currentTimeMillis(), isolationLevel,
099: LockEntry.LOCK_READ);
100: LockIsolation ls = lockStrategyManager
101: .getStrategyFor(isolationLevel);
102: return addReaderIfPossibleInternal(reader, ls
103: .allowMultipleRead(), ls.allowReadWhenWrite());
104: }
105:
106: private boolean addReaderIfPossibleInternal(LockEntry reader,
107: boolean allowMultipleReader,
108: boolean allowReaderWhenWriteLock) {
109: boolean result = false;
110: ObjectLocks objectLocks = null;
111: Object oid = reader.getResourceId();
112: /**
113: * MBAIRD: We need to synchronize the get/put so we don't have two threads
114: * competing to check if something is locked and double-locking it.
115: */
116: synchronized (locktable) {
117: objectLocks = (ObjectLocks) locktable.get(oid);
118: if (objectLocks == null) {
119: // no write or read lock, go on
120: objectLocks = new ObjectLocks();
121: locktable.put(oid, objectLocks);
122: objectLocks.addReader(reader);
123: result = true;
124: } else {
125: // ObjectLocks exist, first check for a write lock
126: LockEntry writer = objectLocks.getWriter();
127: if (writer != null) {
128: // if writer is owned by current entity, read lock is
129: // successful (we have an write lock)
130: if (writer.isOwnedBy(reader.getKey())) {
131: result = true;
132: } else {
133: // if read lock is allowed when different entity hold write lock
134: // go on if multiple reader allowed, else do nothing
135: if (allowReaderWhenWriteLock
136: && allowMultipleReader) {
137: objectLocks.addReader(reader);
138: result = true;
139: } else {
140: result = false;
141: }
142: }
143: } else {
144: // no write lock exist, check for existing read locks
145: if (objectLocks.getReaders().size() > 0) {
146: // if we have already an read lock, do nothing
147: if (objectLocks.getReader(reader.getKey()) != null) {
148: result = true;
149: } else {
150: // we have read locks of other entities, add read lock
151: // if allowed
152: if (allowMultipleReader) {
153: objectLocks.addReader(reader);
154: result = true;
155: }
156: }
157: } else {
158: // no read locks exist, so go on
159: objectLocks.addReader(reader);
160: result = true;
161: }
162: }
163: }
164: }
165: return result;
166: }
167:
168: /**
169: * Remove an read lock.
170: */
171: public boolean removeReader(Object key, Object resourceId) {
172: boolean result = false;
173: ObjectLocks objectLocks = null;
174: synchronized (locktable) {
175: objectLocks = (ObjectLocks) locktable.get(resourceId);
176: if (objectLocks != null) {
177: /**
178: * MBAIRD, last one out, close the door and turn off the lights.
179: * if no locks (readers or writers) exist for this object, let's remove
180: * it from the locktable.
181: */
182: Map readers = objectLocks.getReaders();
183: result = readers.remove(key) != null;
184: if ((objectLocks.getWriter() == null)
185: && (readers.size() == 0)) {
186: locktable.remove(resourceId);
187: }
188: }
189: }
190: return result;
191: }
192:
193: /**
194: * Remove an write lock.
195: */
196: public boolean removeWriter(Object key, Object resourceId) {
197: boolean result = false;
198: ObjectLocks objectLocks = null;
199: synchronized (locktable) {
200: objectLocks = (ObjectLocks) locktable.get(resourceId);
201: if (objectLocks != null) {
202: /**
203: * MBAIRD, last one out, close the door and turn off the lights.
204: * if no locks (readers or writers) exist for this object, let's remove
205: * it from the locktable.
206: */
207: LockEntry entry = objectLocks.getWriter();
208: if (entry != null && entry.isOwnedBy(key)) {
209: objectLocks.setWriter(null);
210: result = true;
211:
212: // no need to check if writer is null, we just set it.
213: if (objectLocks.getReaders().size() == 0) {
214: locktable.remove(resourceId);
215: }
216: }
217: }
218: }
219: return result;
220: }
221:
222: public boolean releaseLock(Object key, Object resourceId) {
223: if (log.isDebugEnabled())
224: log.debug("LM.releaseLock(tx-" + key + ", " + resourceId
225: + ")");
226: boolean result = removeReader(key, resourceId);
227: // if no read lock could be removed, try write lock
228: if (!result) {
229: result = removeWriter(key, resourceId);
230: }
231: return result;
232: }
233:
234: /**
235: * @see LockManager#releaseLocks(Object)
236: */
237: public void releaseLocks(Object key) {
238: if (log.isDebugEnabled())
239: log.debug("LM.releaseLocks(tx-" + key + ")");
240: checkTimedOutLocks();
241: releaseLocksInternal(key);
242: }
243:
244: private void releaseLocksInternal(Object key) {
245: synchronized (locktable) {
246: Collection values = locktable.values();
247: ObjectLocks entry;
248: for (Iterator iterator = values.iterator(); iterator
249: .hasNext();) {
250: entry = (ObjectLocks) iterator.next();
251: entry.removeReader(key);
252: if (entry.getWriter() != null
253: && entry.getWriter().isOwnedBy(key)) {
254: entry.setWriter(null);
255: }
256: }
257: }
258: }
259:
260: public boolean writeLock(Object key, Object resourceId,
261: int isolationLevel) {
262: if (log.isDebugEnabled())
263: log.debug("LM.writeLock(tx-" + key + ", " + resourceId
264: + ")");
265: checkTimedOutLocks();
266: LockEntry writer = new LockEntry(resourceId, key, System
267: .currentTimeMillis(), isolationLevel,
268: LockEntry.LOCK_WRITE);
269: LockIsolation ls = lockStrategyManager
270: .getStrategyFor(isolationLevel);
271: return setWriterIfPossibleInternal(writer, ls
272: .allowWriteWhenRead());
273: }
274:
275: private boolean setWriterIfPossibleInternal(LockEntry writer,
276: boolean allowReaders) {
277: boolean result = false;
278: ObjectLocks objectLocks = null;
279: /**
280: * MBAIRD: We need to synchronize the get/put so we don't have two threads
281: * competing to check if something is locked and double-locking it.
282: */
283: synchronized (locktable) {
284: objectLocks = (ObjectLocks) locktable.get(writer
285: .getResourceId());
286: // if we don't upgrade, go on
287: if (objectLocks == null) {
288: // no locks for current entity exist, so go on
289: objectLocks = new ObjectLocks();
290: objectLocks.setWriter(writer);
291: locktable.put(writer.getResourceId(), objectLocks);
292: result = true;
293: } else {
294: // the ObjectLock exist, check if there is already a write lock
295: LockEntry oldWriter = objectLocks.getWriter();
296: if (oldWriter != null) {
297: // if already a write lock exists, check owner
298: if (oldWriter.isOwnedBy(writer.getKey())) {
299: // if current entity has already a write lock
300: // signal success
301: result = true;
302: }
303: } else {
304: // current ObjectLock has no write lock, so check for readers
305: int readerSize = objectLocks.getReaders().size();
306: if (readerSize > 0) {
307: // does current entity have already an read lock
308: if (objectLocks.getReader(writer.getKey()) != null) {
309: if (readerSize == 1) {
310: // only current entity has a read lock, so go on
311: objectLocks.readers.remove(writer
312: .getKey());
313: objectLocks.setWriter(writer);
314: result = true;
315: } else {
316: // current entity and others have already a read lock
317: // if aquire a write is allowed, go on
318: if (allowReaders) {
319: objectLocks.readers.remove(writer
320: .getKey());
321: objectLocks.setWriter(writer);
322: result = true;
323: }
324: }
325: } else {
326: // current entity has no read lock, but others
327: // if aquire a write is allowed, go on
328: if (allowReaders) {
329: objectLocks.setWriter(writer);
330: result = true;
331: }
332: }
333: } else {
334: // no readers and writers, so go on if we don't upgrade
335: objectLocks.setWriter(writer);
336: result = true;
337: }
338: }
339: }
340: }
341: return result;
342: }
343:
344: public boolean upgradeLock(Object key, Object resourceId,
345: int isolationLevel) {
346: if (log.isDebugEnabled())
347: log.debug("LM.upgradeLock(tx-" + key + ", " + resourceId
348: + ")");
349: return writeLock(key, resourceId, isolationLevel);
350: }
351:
352: /**
353: * @see LockManager#hasWrite(Object, Object)
354: */
355: public boolean hasWrite(Object key, Object resourceId) {
356: if (log.isDebugEnabled())
357: log
358: .debug("LM.hasWrite(tx-" + key + ", " + resourceId
359: + ")");
360: checkTimedOutLocks();
361: return hasWriteLockInternal(resourceId, key);
362: }
363:
364: private boolean hasWriteLockInternal(Object resourceId, Object key) {
365: boolean result = false;
366: ObjectLocks objectLocks = null;
367: synchronized (locktable) {
368: objectLocks = (ObjectLocks) locktable.get(resourceId);
369: if (objectLocks != null) {
370: LockEntry writer = objectLocks.getWriter();
371: if (writer != null) {
372: result = writer.isOwnedBy(key);
373: }
374: }
375: }
376: return result;
377: }
378:
379: public boolean hasUpgrade(Object key, Object resourceId) {
380: if (log.isDebugEnabled())
381: log.debug("LM.hasUpgrade(tx-" + key + ", " + resourceId
382: + ")");
383: return hasWrite(key, resourceId);
384: }
385:
386: /**
387: * @see LockManager#hasRead(Object, Object)
388: */
389: public boolean hasRead(Object key, Object resourceId) {
390: if (log.isDebugEnabled())
391: log.debug("LM.hasRead(tx-" + key + ", " + resourceId + ')');
392: checkTimedOutLocks();
393: return hasReadLockInternal(resourceId, key);
394: }
395:
396: private boolean hasReadLockInternal(Object resourceId, Object key) {
397: boolean result = false;
398: ObjectLocks objectLocks = null;
399: synchronized (locktable) {
400: objectLocks = (ObjectLocks) locktable.get(resourceId);
401: if (objectLocks != null) {
402: LockEntry reader = objectLocks.getReader(key);
403: if (reader != null
404: || (objectLocks.getWriter() != null && objectLocks
405: .getWriter().isOwnedBy(key))) {
406: result = true;
407: }
408: }
409: }
410: return result;
411: }
412:
413: /**
414: *
415: */
416: public int lockedObjects() {
417: return locktable.size();
418: }
419:
420: private void checkTimedOutLocks() {
421: if (System.currentTimeMillis() - m_lastCleanupAt > CLEANUP_FREQUENCY) {
422: removeTimedOutLocks(getLockTimeout());
423: m_lastCleanupAt = System.currentTimeMillis();
424: }
425: }
426:
427: /**
428: * removes all timed out lock entries from the persistent storage.
429: * The timeout value can be set in the OJB properties file.
430: */
431: private void removeTimedOutLocks(long timeout) {
432: int count = 0;
433: long maxAge = System.currentTimeMillis() - timeout;
434: boolean breakFromLoop = false;
435: ObjectLocks temp = null;
436: synchronized (locktable) {
437: Iterator it = locktable.values().iterator();
438: /**
439: * run this loop while:
440: * - we have more in the iterator
441: * - the breakFromLoop flag hasn't been set
442: * - we haven't removed more than the limit for this cleaning iteration.
443: */
444: while (it.hasNext() && !breakFromLoop
445: && (count <= MAX_LOCKS_TO_CLEAN)) {
446: temp = (ObjectLocks) it.next();
447: if (temp.getWriter() != null) {
448: if (temp.getWriter().getTimestamp() < maxAge) {
449: // writer has timed out, set it to null
450: temp.setWriter(null);
451: ++timeoutCounterWrite;
452: }
453: }
454: if (temp.getYoungestReader() < maxAge) {
455: // all readers are older than timeout.
456: temp.getReaders().clear();
457: ++timeoutCounterRead;
458: if (temp.getWriter() == null) {
459: // all readers and writer are older than timeout,
460: // remove the objectLock from the iterator (which
461: // is backed by the map, so it will be removed.
462: it.remove();
463: }
464: } else {
465: // we need to walk each reader.
466: Iterator readerIt = temp.getReaders().values()
467: .iterator();
468: LockEntry readerLock = null;
469: while (readerIt.hasNext()) {
470: readerLock = (LockEntry) readerIt.next();
471: if (readerLock.getTimestamp() < maxAge) {
472: // this read lock is old, remove it.
473: readerIt.remove();
474: }
475: }
476: }
477: count++;
478: }
479: }
480: }
481:
482: //===============================================================
483: // inner class
484: //===============================================================
485: static final class ObjectLocks {
486: private LockEntry writer;
487: private Hashtable readers;
488: private long m_youngestReader = 0;
489:
490: ObjectLocks() {
491: this (null);
492: }
493:
494: ObjectLocks(LockEntry writer) {
495: this .writer = writer;
496: readers = new Hashtable();
497: }
498:
499: LockEntry getWriter() {
500: return writer;
501: }
502:
503: void setWriter(LockEntry writer) {
504: this .writer = writer;
505: }
506:
507: Hashtable getReaders() {
508: return readers;
509: }
510:
511: void addReader(LockEntry reader) {
512: /**
513: * MBAIRD:
514: * we want to track the youngest reader so we can remove all readers at timeout
515: * if the youngestreader is older than the timeoutperiod.
516: */
517: if ((reader.getTimestamp() < m_youngestReader)
518: || (m_youngestReader == 0)) {
519: m_youngestReader = reader.getTimestamp();
520: }
521: this .readers.put(reader.getKey(), reader);
522: }
523:
524: long getYoungestReader() {
525: return m_youngestReader;
526: }
527:
528: LockEntry getReader(Object key) {
529: return (LockEntry) this .readers.get(key);
530: }
531:
532: LockEntry removeReader(Object key) {
533: return (LockEntry) this .readers.remove(key);
534: }
535: }
536:
537: //===============================================================
538: // inner class
539: //===============================================================
540: /**
541: * A lock entry encapsulates locking information.
542: */
543: final class LockEntry implements Serializable {
544: /**
545: * marks a Read Lock.
546: */
547: static final int LOCK_READ = 0;
548:
549: /**
550: * marks a Write Lock.
551: */
552: static final int LOCK_WRITE = 1;
553:
554: /**
555: * the object to be locked.
556: */
557: private Object resourceId;
558:
559: /**
560: * key for locked object
561: */
562: private Object key;
563:
564: /**
565: * the timestamp marking the time of acquisition of this lock
566: */
567: private long timestamp;
568:
569: /**
570: * the isolationlevel for this lock.
571: */
572: private int isolationLevel;
573:
574: /**
575: * marks if this is a read or a write lock.
576: * LOCK_READ = 0;
577: * LOCK_WRITE = 1;
578: */
579: private int lockType;
580:
581: /**
582: * Multiargument constructor for fast loading of LockEntries by OJB.
583: */
584: public LockEntry(Object resourceId, Object key, long timestamp,
585: int isolationLevel, int lockType) {
586: this .resourceId = resourceId;
587: this .key = key;
588: this .timestamp = timestamp;
589: this .isolationLevel = isolationLevel;
590: this .lockType = lockType;
591:
592: }
593:
594: /**
595: * Returns the resource id of the locked object (or the locked object itself).
596: */
597: public Object getResourceId() {
598: return resourceId;
599: }
600:
601: /**
602: * Returns lock key.
603: */
604: public Object getKey() {
605: return key;
606: }
607:
608: /**
609: * returns the timestamp of the acqusition of the lock.
610: */
611: public long getTimestamp() {
612: return timestamp;
613: }
614:
615: /**
616: * returns the isolation level of this lock
617: */
618: public int getIsolationLevel() {
619: return isolationLevel;
620: }
621:
622: /**
623: * returns the locktype of this lock.
624: *
625: * @return LOCK_READ if lock is a readlock,
626: * LOCK_WRITE if lock is a Write lock.
627: */
628: public int getLockType() {
629: return lockType;
630: }
631:
632: /**
633: * sets the locktype of this lockentry.
634: *
635: * @param locktype LOCK_READ for read, LOCK_WRITE for write lock.
636: */
637: public void setLockType(int locktype) {
638: this .lockType = locktype;
639: }
640:
641: /**
642: * Returns true if this lock is owned by the specified key.
643: */
644: public boolean isOwnedBy(Object key) {
645: return this .getKey().equals(key);
646: }
647:
648: /**
649: * Sets the isolationLevel.
650: *
651: * @param isolationLevel The isolationLevel to set
652: */
653: public void setIsolationLevel(int isolationLevel) {
654: this .isolationLevel = isolationLevel;
655: }
656:
657: /**
658: * Sets the resourceId.
659: *
660: * @param resourceId The resourceId to set
661: */
662: public void setresourceId(String resourceId) {
663: this .resourceId = resourceId;
664: }
665:
666: /**
667: * Sets the timestamp.
668: *
669: * @param timestamp The timestamp to set
670: */
671: public void setTimestamp(long timestamp) {
672: this .timestamp = timestamp;
673: }
674:
675: /**
676: * Sets the key.
677: *
678: * @param key The key to set
679: */
680: public void setKey(Object key) {
681: this.key = key;
682: }
683: }
684: }
|