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 2007-08-24 22:17:41 ewestfal Exp $
034     */
035    public class LockManagerInMemoryImpl implements LockManager
036    {
037        private Logger log = LoggerFactory.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        {
054            this.lockTimeout = DEFAULT_LOCK_TIMEOUT;
055        }
056    
057        public long getLockTimeout()
058        {
059            return lockTimeout;
060        }
061    
062        public void setLockTimeout(long timeout)
063        {
064            this.lockTimeout = timeout;
065        }
066    
067        /**
068         * NOOP
069         * @return Always '0'
070         */
071        public long getBlockTimeout()
072        {
073            return 0;
074        }
075    
076        /**
077         * NOOP
078         */ 
079        public void setBlockTimeout(long timeout)
080        {
081        }
082    
083        public String getLockInfo()
084        {
085            String eol = SystemUtils.LINE_SEPARATOR;
086            StringBuffer msg = new StringBuffer("Class: " + LockManagerInMemoryImpl.class.getName() + eol);
087            msg.append("lock timeout: " + getLockTimeout() + " [ms]" + eol);
088            msg.append("concurrent lock owners: " + locktable.size() + eol);
089            msg.append("timed out write locks: " + timeoutCounterWrite + eol);
090            msg.append("timed out read locks: " + timeoutCounterRead + eol);
091            return msg.toString();
092        }
093    
094        public boolean readLock(Object key, Object resourceId, int isolationLevel)
095        {
096            if(log.isDebugEnabled()) log.debug("LM.readLock(tx-" + key + ", " + resourceId + ")");
097            checkTimedOutLocks();
098            LockEntry reader = new LockEntry(resourceId,
099                    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    }