1 package org.apache.ojb.broker.locking;
2
3 /* Copyright 2002-2005 The Apache Software Foundation
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18 import java.io.Serializable;
19 import java.util.Collection;
20 import java.util.HashMap;
21 import java.util.Hashtable;
22 import java.util.Iterator;
23 import java.util.Map;
24
25 import org.apache.ojb.broker.util.logging.Logger;
26 import org.apache.ojb.broker.util.logging.LoggerFactory;
27 import org.apache.commons.lang.SystemUtils;
28
29 /**
30 * This implementation of the {@link LockManager} interface supports a simple, fast, non-blocking
31 * pessimistic locking for single JVM applications.
32 *
33 * @version $Id: LockManagerInMemoryImpl.java,v 1.1 2007-08-24 22:17:41 ewestfal Exp $
34 */
35 public class LockManagerInMemoryImpl implements LockManager
36 {
37 private Logger log = LoggerFactory.getLogger(LockManagerInMemoryImpl.class);
38 private static long CLEANUP_FREQUENCY = 1000; // 1000 milliseconds.
39 private static int MAX_LOCKS_TO_CLEAN = 300;
40 /**
41 * MBAIRD: a LinkedHashMap returns objects in the order you put them in,
42 * while still maintaining an O(1) lookup like a normal hashmap. We can then
43 * use this to get the oldest entries very quickly, makes cleanup a breeze.
44 */
45 private HashMap locktable = new HashMap();
46 private LockIsolationManager lockStrategyManager = new LockIsolationManager();
47 private long m_lastCleanupAt = System.currentTimeMillis();
48 private long lockTimeout;
49 private long timeoutCounterRead;
50 private long timeoutCounterWrite;
51
52 public LockManagerInMemoryImpl()
53 {
54 this.lockTimeout = DEFAULT_LOCK_TIMEOUT;
55 }
56
57 public long getLockTimeout()
58 {
59 return lockTimeout;
60 }
61
62 public void setLockTimeout(long timeout)
63 {
64 this.lockTimeout = timeout;
65 }
66
67 /**
68 * NOOP
69 * @return Always '0'
70 */
71 public long getBlockTimeout()
72 {
73 return 0;
74 }
75
76 /**
77 * NOOP
78 */
79 public void setBlockTimeout(long timeout)
80 {
81 }
82
83 public String getLockInfo()
84 {
85 String eol = SystemUtils.LINE_SEPARATOR;
86 StringBuffer msg = new StringBuffer("Class: " + LockManagerInMemoryImpl.class.getName() + eol);
87 msg.append("lock timeout: " + getLockTimeout() + " [ms]" + eol);
88 msg.append("concurrent lock owners: " + locktable.size() + eol);
89 msg.append("timed out write locks: " + timeoutCounterWrite + eol);
90 msg.append("timed out read locks: " + timeoutCounterRead + eol);
91 return msg.toString();
92 }
93
94 public boolean readLock(Object key, Object resourceId, int isolationLevel)
95 {
96 if(log.isDebugEnabled()) log.debug("LM.readLock(tx-" + key + ", " + resourceId + ")");
97 checkTimedOutLocks();
98 LockEntry reader = new LockEntry(resourceId,
99 key,
100 System.currentTimeMillis(),
101 isolationLevel,
102 LockEntry.LOCK_READ);
103 LockIsolation ls = lockStrategyManager.getStrategyFor(isolationLevel);
104 return addReaderIfPossibleInternal(reader, ls.allowMultipleRead(), ls.allowReadWhenWrite());
105 }
106
107 private boolean addReaderIfPossibleInternal(LockEntry reader, boolean allowMultipleReader,
108 boolean allowReaderWhenWriteLock)
109 {
110 boolean result = false;
111 ObjectLocks objectLocks = null;
112 Object oid = reader.getResourceId();
113 /**
114 * MBAIRD: We need to synchronize the get/put so we don't have two threads
115 * competing to check if something is locked and double-locking it.
116 */
117 synchronized(locktable)
118 {
119 objectLocks = (ObjectLocks) locktable.get(oid);
120 if(objectLocks == null)
121 {
122 // no write or read lock, go on
123 objectLocks = new ObjectLocks();
124 locktable.put(oid, objectLocks);
125 objectLocks.addReader(reader);
126 result = true;
127 }
128 else
129 {
130 // ObjectLocks exist, first check for a write lock
131 LockEntry writer = objectLocks.getWriter();
132 if(writer != null)
133 {
134 // if writer is owned by current entity, read lock is
135 // successful (we have an write lock)
136 if(writer.isOwnedBy(reader.getKey()))
137 {
138 result = true;
139 }
140 else
141 {
142 // if read lock is allowed when different entity hold write lock
143 // go on if multiple reader allowed, else do nothing
144 if(allowReaderWhenWriteLock && allowMultipleReader)
145 {
146 objectLocks.addReader(reader);
147 result = true;
148 }
149 else
150 {
151 result = false;
152 }
153 }
154 }
155 else
156 {
157 // no write lock exist, check for existing read locks
158 if(objectLocks.getReaders().size() > 0)
159 {
160 // if we have already an read lock, do nothing
161 if(objectLocks.getReader(reader.getKey()) != null)
162 {
163 result = true;
164 }
165 else
166 {
167 // we have read locks of other entities, add read lock
168 // if allowed
169 if(allowMultipleReader)
170 {
171 objectLocks.addReader(reader);
172 result = true;
173 }
174 }
175 }
176 else
177 {
178 // no read locks exist, so go on
179 objectLocks.addReader(reader);
180 result = true;
181 }
182 }
183 }
184 }
185 return result;
186 }
187
188 /**
189 * Remove an read lock.
190 */
191 public boolean removeReader(Object key, Object resourceId)
192 {
193 boolean result = false;
194 ObjectLocks objectLocks = null;
195 synchronized(locktable)
196 {
197 objectLocks = (ObjectLocks) locktable.get(resourceId);
198 if(objectLocks != null)
199 {
200 /**
201 * MBAIRD, last one out, close the door and turn off the lights.
202 * if no locks (readers or writers) exist for this object, let's remove
203 * it from the locktable.
204 */
205 Map readers = objectLocks.getReaders();
206 result = readers.remove(key) != null;
207 if((objectLocks.getWriter() == null) && (readers.size() == 0))
208 {
209 locktable.remove(resourceId);
210 }
211 }
212 }
213 return result;
214 }
215
216 /**
217 * Remove an write lock.
218 */
219 public boolean removeWriter(Object key, Object resourceId)
220 {
221 boolean result = false;
222 ObjectLocks objectLocks = null;
223 synchronized(locktable)
224 {
225 objectLocks = (ObjectLocks) locktable.get(resourceId);
226 if(objectLocks != null)
227 {
228 /**
229 * MBAIRD, last one out, close the door and turn off the lights.
230 * if no locks (readers or writers) exist for this object, let's remove
231 * it from the locktable.
232 */
233 LockEntry entry = objectLocks.getWriter();
234 if(entry != null && entry.isOwnedBy(key))
235 {
236 objectLocks.setWriter(null);
237 result = true;
238
239 // no need to check if writer is null, we just set it.
240 if(objectLocks.getReaders().size() == 0)
241 {
242 locktable.remove(resourceId);
243 }
244 }
245 }
246 }
247 return result;
248 }
249
250 public boolean releaseLock(Object key, Object resourceId)
251 {
252 if(log.isDebugEnabled()) log.debug("LM.releaseLock(tx-" + key + ", " + resourceId + ")");
253 boolean result = removeReader(key, resourceId);
254 // if no read lock could be removed, try write lock
255 if(!result)
256 {
257 result = removeWriter(key, resourceId);
258 }
259 return result;
260 }
261
262 /**
263 * @see LockManager#releaseLocks(Object)
264 */
265 public void releaseLocks(Object key)
266 {
267 if(log.isDebugEnabled()) log.debug("LM.releaseLocks(tx-" + key + ")");
268 checkTimedOutLocks();
269 releaseLocksInternal(key);
270 }
271
272 private void releaseLocksInternal(Object key)
273 {
274 synchronized(locktable)
275 {
276 Collection values = locktable.values();
277 ObjectLocks entry;
278 for(Iterator iterator = values.iterator(); iterator.hasNext();)
279 {
280 entry = (ObjectLocks) iterator.next();
281 entry.removeReader(key);
282 if(entry.getWriter() != null && entry.getWriter().isOwnedBy(key))
283 {
284 entry.setWriter(null);
285 }
286 }
287 }
288 }
289
290 public boolean writeLock(Object key, Object resourceId, int isolationLevel)
291 {
292 if(log.isDebugEnabled()) log.debug("LM.writeLock(tx-" + key + ", " + resourceId + ")");
293 checkTimedOutLocks();
294 LockEntry writer = new LockEntry(resourceId,
295 key,
296 System.currentTimeMillis(),
297 isolationLevel,
298 LockEntry.LOCK_WRITE);
299 LockIsolation ls = lockStrategyManager.getStrategyFor(isolationLevel);
300 return setWriterIfPossibleInternal(writer, ls.allowWriteWhenRead());
301 }
302
303 private boolean setWriterIfPossibleInternal(LockEntry writer, boolean allowReaders)
304 {
305 boolean result = false;
306 ObjectLocks objectLocks = null;
307 /**
308 * MBAIRD: We need to synchronize the get/put so we don't have two threads
309 * competing to check if something is locked and double-locking it.
310 */
311 synchronized(locktable)
312 {
313 objectLocks = (ObjectLocks) locktable.get(writer.getResourceId());
314 // if we don't upgrade, go on
315 if(objectLocks == null)
316 {
317 // no locks for current entity exist, so go on
318 objectLocks = new ObjectLocks();
319 objectLocks.setWriter(writer);
320 locktable.put(writer.getResourceId(), objectLocks);
321 result = true;
322 }
323 else
324 {
325 // the ObjectLock exist, check if there is already a write lock
326 LockEntry oldWriter = objectLocks.getWriter();
327 if(oldWriter != null)
328 {
329 // if already a write lock exists, check owner
330 if(oldWriter.isOwnedBy(writer.getKey()))
331 {
332 // if current entity has already a write lock
333 // signal success
334 result = true;
335 }
336 }
337 else
338 {
339 // current ObjectLock has no write lock, so check for readers
340 int readerSize = objectLocks.getReaders().size();
341 if(readerSize > 0)
342 {
343 // does current entity have already an read lock
344 if(objectLocks.getReader(writer.getKey()) != null)
345 {
346 if(readerSize == 1)
347 {
348 // only current entity has a read lock, so go on
349 objectLocks.readers.remove(writer.getKey());
350 objectLocks.setWriter(writer);
351 result = true;
352 }
353 else
354 {
355 // current entity and others have already a read lock
356 // if aquire a write is allowed, go on
357 if(allowReaders)
358 {
359 objectLocks.readers.remove(writer.getKey());
360 objectLocks.setWriter(writer);
361 result = true;
362 }
363 }
364 }
365 else
366 {
367 // current entity has no read lock, but others
368 // if aquire a write is allowed, go on
369 if(allowReaders)
370 {
371 objectLocks.setWriter(writer);
372 result = true;
373 }
374 }
375 }
376 else
377 {
378 // no readers and writers, so go on if we don't upgrade
379 objectLocks.setWriter(writer);
380 result = true;
381 }
382 }
383 }
384 }
385 return result;
386 }
387
388 public boolean upgradeLock(Object key, Object resourceId, int isolationLevel)
389 {
390 if(log.isDebugEnabled()) log.debug("LM.upgradeLock(tx-" + key + ", " + resourceId + ")");
391 return writeLock(key, resourceId, isolationLevel);
392 }
393
394 /**
395 * @see LockManager#hasWrite(Object, Object)
396 */
397 public boolean hasWrite(Object key, Object resourceId)
398 {
399 if(log.isDebugEnabled()) log.debug("LM.hasWrite(tx-" + key + ", " + resourceId + ")");
400 checkTimedOutLocks();
401 return hasWriteLockInternal(resourceId, key);
402 }
403
404 private boolean hasWriteLockInternal(Object resourceId, Object key)
405 {
406 boolean result = false;
407 ObjectLocks objectLocks = null;
408 synchronized(locktable)
409 {
410 objectLocks = (ObjectLocks) locktable.get(resourceId);
411 if(objectLocks != null)
412 {
413 LockEntry writer = objectLocks.getWriter();
414 if(writer != null)
415 {
416 result = writer.isOwnedBy(key);
417 }
418 }
419 }
420 return result;
421 }
422
423 public boolean hasUpgrade(Object key, Object resourceId)
424 {
425 if(log.isDebugEnabled()) log.debug("LM.hasUpgrade(tx-" + key + ", " + resourceId + ")");
426 return hasWrite(key, resourceId);
427 }
428
429 /**
430 * @see LockManager#hasRead(Object, Object)
431 */
432 public boolean hasRead(Object key, Object resourceId)
433 {
434 if(log.isDebugEnabled()) log.debug("LM.hasRead(tx-" + key + ", " + resourceId + ')');
435 checkTimedOutLocks();
436 return hasReadLockInternal(resourceId, key);
437 }
438
439 private boolean hasReadLockInternal(Object resourceId, Object key)
440 {
441 boolean result = false;
442 ObjectLocks objectLocks = null;
443 synchronized(locktable)
444 {
445 objectLocks = (ObjectLocks) locktable.get(resourceId);
446 if(objectLocks != null)
447 {
448 LockEntry reader = objectLocks.getReader(key);
449 if(reader != null || (objectLocks.getWriter() != null && objectLocks.getWriter().isOwnedBy(key)))
450 {
451 result = true;
452 }
453 }
454 }
455 return result;
456 }
457
458 /**
459 *
460 */
461 public int lockedObjects()
462 {
463 return locktable.size();
464 }
465
466 private void checkTimedOutLocks()
467 {
468 if(System.currentTimeMillis() - m_lastCleanupAt > CLEANUP_FREQUENCY)
469 {
470 removeTimedOutLocks(getLockTimeout());
471 m_lastCleanupAt = System.currentTimeMillis();
472 }
473 }
474
475 /**
476 * removes all timed out lock entries from the persistent storage.
477 * The timeout value can be set in the OJB properties file.
478 */
479 private void removeTimedOutLocks(long timeout)
480 {
481 int count = 0;
482 long maxAge = System.currentTimeMillis() - timeout;
483 boolean breakFromLoop = false;
484 ObjectLocks temp = null;
485 synchronized(locktable)
486 {
487 Iterator it = locktable.values().iterator();
488 /**
489 * run this loop while:
490 * - we have more in the iterator
491 * - the breakFromLoop flag hasn't been set
492 * - we haven't removed more than the limit for this cleaning iteration.
493 */
494 while(it.hasNext() && !breakFromLoop && (count <= MAX_LOCKS_TO_CLEAN))
495 {
496 temp = (ObjectLocks) it.next();
497 if(temp.getWriter() != null)
498 {
499 if(temp.getWriter().getTimestamp() < maxAge)
500 {
501 // writer has timed out, set it to null
502 temp.setWriter(null);
503 ++timeoutCounterWrite;
504 }
505 }
506 if(temp.getYoungestReader() < maxAge)
507 {
508 // all readers are older than timeout.
509 temp.getReaders().clear();
510 ++timeoutCounterRead;
511 if(temp.getWriter() == null)
512 {
513 // all readers and writer are older than timeout,
514 // remove the objectLock from the iterator (which
515 // is backed by the map, so it will be removed.
516 it.remove();
517 }
518 }
519 else
520 {
521 // we need to walk each reader.
522 Iterator readerIt = temp.getReaders().values().iterator();
523 LockEntry readerLock = null;
524 while(readerIt.hasNext())
525 {
526 readerLock = (LockEntry) readerIt.next();
527 if(readerLock.getTimestamp() < maxAge)
528 {
529 // this read lock is old, remove it.
530 readerIt.remove();
531 }
532 }
533 }
534 count++;
535 }
536 }
537 }
538
539
540 //===============================================================
541 // inner class
542 //===============================================================
543 static final class ObjectLocks
544 {
545 private LockEntry writer;
546 private Hashtable readers;
547 private long m_youngestReader = 0;
548
549 ObjectLocks()
550 {
551 this(null);
552 }
553
554 ObjectLocks(LockEntry writer)
555 {
556 this.writer = writer;
557 readers = new Hashtable();
558 }
559
560 LockEntry getWriter()
561 {
562 return writer;
563 }
564
565 void setWriter(LockEntry writer)
566 {
567 this.writer = writer;
568 }
569
570 Hashtable getReaders()
571 {
572 return readers;
573 }
574
575 void addReader(LockEntry reader)
576 {
577 /**
578 * MBAIRD:
579 * we want to track the youngest reader so we can remove all readers at timeout
580 * if the youngestreader is older than the timeoutperiod.
581 */
582 if((reader.getTimestamp() < m_youngestReader) || (m_youngestReader == 0))
583 {
584 m_youngestReader = reader.getTimestamp();
585 }
586 this.readers.put(reader.getKey(), reader);
587 }
588
589 long getYoungestReader()
590 {
591 return m_youngestReader;
592 }
593
594 LockEntry getReader(Object key)
595 {
596 return (LockEntry) this.readers.get(key);
597 }
598
599 LockEntry removeReader(Object key)
600 {
601 return (LockEntry) this.readers.remove(key);
602 }
603 }
604
605
606 //===============================================================
607 // inner class
608 //===============================================================
609 /**
610 * A lock entry encapsulates locking information.
611 */
612 final class LockEntry implements Serializable
613 {
614 /**
615 * marks a Read Lock.
616 */
617 static final int LOCK_READ = 0;
618
619 /**
620 * marks a Write Lock.
621 */
622 static final int LOCK_WRITE = 1;
623
624 /**
625 * the object to be locked.
626 */
627 private Object resourceId;
628
629 /**
630 * key for locked object
631 */
632 private Object key;
633
634 /**
635 * the timestamp marking the time of acquisition of this lock
636 */
637 private long timestamp;
638
639 /**
640 * the isolationlevel for this lock.
641 */
642 private int isolationLevel;
643
644 /**
645 * marks if this is a read or a write lock.
646 * LOCK_READ = 0;
647 * LOCK_WRITE = 1;
648 */
649 private int lockType;
650
651 /**
652 * Multiargument constructor for fast loading of LockEntries by OJB.
653 */
654 public LockEntry(Object resourceId,
655 Object key,
656 long timestamp,
657 int isolationLevel,
658 int lockType)
659 {
660 this.resourceId = resourceId;
661 this.key = key;
662 this.timestamp = timestamp;
663 this.isolationLevel = isolationLevel;
664 this.lockType = lockType;
665
666 }
667
668 /**
669 * Returns the resource id of the locked object (or the locked object itself).
670 */
671 public Object getResourceId()
672 {
673 return resourceId;
674 }
675
676 /**
677 * Returns lock key.
678 */
679 public Object getKey()
680 {
681 return key;
682 }
683
684 /**
685 * returns the timestamp of the acqusition of the lock.
686 */
687 public long getTimestamp()
688 {
689 return timestamp;
690 }
691
692 /**
693 * returns the isolation level of this lock
694 */
695 public int getIsolationLevel()
696 {
697 return isolationLevel;
698 }
699
700 /**
701 * returns the locktype of this lock.
702 *
703 * @return LOCK_READ if lock is a readlock,
704 * LOCK_WRITE if lock is a Write lock.
705 */
706 public int getLockType()
707 {
708 return lockType;
709 }
710
711 /**
712 * sets the locktype of this lockentry.
713 *
714 * @param locktype LOCK_READ for read, LOCK_WRITE for write lock.
715 */
716 public void setLockType(int locktype)
717 {
718 this.lockType = locktype;
719 }
720
721 /**
722 * Returns true if this lock is owned by the specified key.
723 */
724 public boolean isOwnedBy(Object key)
725 {
726 return this.getKey().equals(key);
727 }
728
729
730 /**
731 * Sets the isolationLevel.
732 *
733 * @param isolationLevel The isolationLevel to set
734 */
735 public void setIsolationLevel(int isolationLevel)
736 {
737 this.isolationLevel = isolationLevel;
738 }
739
740 /**
741 * Sets the resourceId.
742 *
743 * @param resourceId The resourceId to set
744 */
745 public void setresourceId(String resourceId)
746 {
747 this.resourceId = resourceId;
748 }
749
750 /**
751 * Sets the timestamp.
752 *
753 * @param timestamp The timestamp to set
754 */
755 public void setTimestamp(long timestamp)
756 {
757 this.timestamp = timestamp;
758 }
759
760 /**
761 * Sets the key.
762 *
763 * @param key The key to set
764 */
765 public void setKey(Object key)
766 {
767 this.key = key;
768 }
769 }
770 }