001    package org.apache.ojb.odmg.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 org.apache.ojb.broker.Identity;
019    import org.apache.ojb.broker.PersistenceBroker;
020    import org.apache.ojb.broker.util.configuration.Configuration;
021    import org.apache.ojb.broker.util.configuration.ConfigurationException;
022    import org.apache.ojb.odmg.TransactionImpl;
023    import org.apache.ojb.odmg.TxManagerFactory;
024    
025    import java.util.Collection;
026    import java.util.HashMap;
027    import java.util.Iterator;
028    import java.util.Map;
029    import java.util.Vector;
030    
031    /**
032     *
033     * We use a HashMap and synchronize blocks of access for a get "check" then put
034     * operation. We cannot use the hashtable as you could check in one synchronized call
035     * then put in another while a different thread is doing the same thing.
036     *
037     * @deprecated
038     * @author <a href="mailto:mattbaird@yahoo.com">Matthew Baird<a>
039     *              update for use of Hashmap with synchronization.
040     *              implemented timed out lock removal algo.
041     * @author <a href="mailto:thma@apache.org">Thomas Mahler<a>
042     *              original author.
043     * @version $Id: InMemoryLockMapImpl.java,v 1.1 2007-08-24 22:17:28 ewestfal Exp $
044     */
045    public class InMemoryLockMapImpl implements LockMap
046    {
047            /**
048             * MBAIRD: a LinkedHashMap returns objects in the order you put them in,
049             * while still maintaining an O(1) lookup like a normal hashmap. We can then
050             * use this to get the oldest entries very quickly, makes cleanup a breeze.
051             */
052        private HashMap locktable = new HashMap();
053    
054        private long m_lastCleanupAt = System.currentTimeMillis();
055            private static long CLEANUP_FREQUENCY = 500; // 500 milliseconds.
056            private static int MAX_LOCKS_TO_CLEAN = 50;
057    
058        /**
059         * returns the LockEntry for the Writer of object obj.
060         * If now writer exists, null is returned.
061         */
062        public LockEntry getWriter(Object obj)
063        {
064            PersistenceBroker broker = getBroker();
065            Identity oid = new Identity(obj, broker); 
066            return getWriter(oid);
067        }
068    
069        public LockEntry getWriter(Identity oid)
070        {
071            checkTimedOutLocks();
072            /* TODO: smarter solution in future */
073            // fix/workaround
074            // When Identity needs new id's we must overgive
075            // the a target broker when run with multiple databases
076            // using H/L sequence manager
077            ObjectLocks objectLocks = null;
078            synchronized(locktable)
079            {
080                objectLocks = (ObjectLocks) locktable.get(oid.toString());
081            }
082            if (objectLocks == null)
083            {
084                return null;
085            }
086            else
087            {
088                return objectLocks.getWriter();
089            }
090        }
091    
092        /**
093         * obtain a PersistenceBroker instance for persistence operations.
094         */
095        private PersistenceBroker getBroker()
096        {
097            return TxManagerFactory.instance().getCurrentTransaction().getBroker();
098        }
099    
100        /**
101         * returns a collection of Reader LockEntries for object obj.
102         * If no LockEntries could be found an empty Vector is returned.
103         */
104        public Collection getReaders(Object obj)
105        {
106            checkTimedOutLocks();
107            Identity oid = new Identity(obj,getBroker());
108            return getReaders(oid);
109        }
110    
111        public Collection getReaders(Identity oid)
112        {
113            ObjectLocks objectLocks = null;
114            synchronized (locktable)
115            {
116                    objectLocks = (ObjectLocks) locktable.get(oid.toString());
117            }
118            if (objectLocks == null)
119            {
120                return new Vector();
121            }
122            else
123            {
124                return objectLocks.getReaders().values();
125            }
126        }
127    
128        /**
129         * Add a reader lock entry for transaction tx on object obj
130         * to the persistent storage.
131         */
132        public boolean addReader(TransactionImpl tx, Object obj)
133        {
134            checkTimedOutLocks();
135    
136            Identity oid = new Identity(obj,getBroker());
137            LockEntry reader = new LockEntry(oid.toString(),
138                    tx.getGUID(),
139                    System.currentTimeMillis(),
140                    LockStrategyFactory.getIsolationLevel(obj),
141                    LockEntry.LOCK_READ);
142    
143            addReaderInternal(reader);
144            return true;
145        }
146    
147        void addReaderInternal(LockEntry reader)
148        {
149            ObjectLocks objectLocks = null;
150            /**
151             * MBAIRD: We need to synchronize the get/put so we don't have two threads
152             * competing to check if something is locked and double-locking it.
153             */
154            synchronized (locktable)
155            {
156                    String oidString = reader.getOidString();
157                    objectLocks = (ObjectLocks) locktable.get(oidString);
158                if (objectLocks == null)
159                {
160                    objectLocks = new ObjectLocks();
161                    locktable.put(oidString, objectLocks);
162                }
163            }
164            objectLocks.addReader(reader);
165        }
166    
167        /**
168         * remove a reader lock entry for transaction tx on object obj
169         * from the persistent storage.
170         */
171        public void removeReader(TransactionImpl tx, Object obj)
172        {
173            checkTimedOutLocks();
174    
175            Identity oid = new Identity(obj, getBroker());
176            String oidString = oid.toString();
177            String txGuid = tx.getGUID();
178            removeReaderInternal(oidString, txGuid);
179        }
180    
181        private void removeReaderInternal(String oidString, String txGuid)
182        {
183            ObjectLocks objectLocks = null;
184            synchronized (locktable)
185            {
186                    objectLocks = (ObjectLocks) locktable.get(oidString);
187            }
188            if (objectLocks == null)
189            {
190                return;
191            }
192            else
193            {
194                    /**
195                     * MBAIRD, last one out, close the door and turn off the lights.
196                     * if no locks (readers or writers) exist for this object, let's remove
197                     * it from the locktable.
198                     */
199                    synchronized (locktable)
200                    {
201                            Map readers = objectLocks.getReaders();
202                            readers.remove(txGuid);
203                    if ((objectLocks.getWriter() == null) && (readers.size() == 0))
204                    {
205                            locktable.remove(oidString);
206                    }
207                    }
208            }
209        }
210    
211            void removeReaderByLock(LockEntry lock)
212            {
213                    String oidString = lock.getOidString();
214                    String txGuid = lock.getTransactionId();
215                    removeReaderInternal(oidString, txGuid);
216            }
217            
218        /**
219         * remove a writer lock entry for transaction tx on object obj
220         * from the persistent storage.
221         */
222        public void removeWriter(LockEntry writer)
223        {
224            checkTimedOutLocks();
225    
226            String oidString = writer.getOidString();
227            ObjectLocks objectLocks = null;
228            synchronized (locktable)
229            {
230                    objectLocks = (ObjectLocks) locktable.get(oidString);
231            }
232            if (objectLocks == null)
233            {
234                return;
235            }
236            else
237            {
238                            /**
239                     * MBAIRD, last one out, close the door and turn off the lights.
240                     * if no locks (readers or writers) exist for this object, let's remove
241                     * it from the locktable.
242                     */
243                    synchronized (locktable)
244                    {
245                            Map readers = objectLocks.getReaders();
246                            objectLocks.setWriter(null);
247                            // no need to check if writer is null, we just set it.
248                    if (readers.size() == 0)
249                    {
250                            locktable.remove(oidString);
251                    }
252                    }
253            }
254        }
255    
256        /**
257         * upgrade a reader lock entry for transaction tx on object obj
258         * and write it to the persistent storage.
259         */
260        public boolean upgradeLock(LockEntry reader)
261        {
262            checkTimedOutLocks();
263    
264            String oidString = reader.getOidString();
265            ObjectLocks objectLocks = null;
266            synchronized (locktable)
267            {
268                    objectLocks = (ObjectLocks) locktable.get(oidString);
269            }
270    
271            if (objectLocks == null)
272            {
273                return false;
274            }
275            else
276            {
277                // add writer entry
278                LockEntry writer = new LockEntry(reader.getOidString(),
279                        reader.getTransactionId(),
280                        System.currentTimeMillis(),
281                        reader.getIsolationLevel(),
282                        LockEntry.LOCK_WRITE);
283                objectLocks.setWriter(writer);
284                // remove reader entry
285                objectLocks.getReaders().remove(reader.getTransactionId());
286                return true;
287            }
288        }
289    
290        /**
291         * generate a writer lock entry for transaction tx on object obj
292         * and write it to the persistent storage.
293         */
294        public boolean setWriter(TransactionImpl tx, Object obj)
295        {
296            checkTimedOutLocks();
297    
298            Identity oid = new Identity(obj, tx.getBroker());
299            LockEntry writer = new LockEntry(oid.toString(),
300                    tx.getGUID(),
301                    System.currentTimeMillis(),
302                    LockStrategyFactory.getIsolationLevel(obj),
303                    LockEntry.LOCK_WRITE);
304            String oidString = oid.toString();
305            setWriterInternal(writer, oidString);
306            return true;
307        }
308    
309        private void setWriterInternal(LockEntry writer, String oidString)
310        {
311            ObjectLocks objectLocks = null;
312            /**
313             * MBAIRD: We need to synchronize the get/put so we don't have two threads
314             * competing to check if something is locked and double-locking it.
315             */
316            synchronized (locktable)
317            {
318                objectLocks = (ObjectLocks) locktable.get(oidString);
319                if (objectLocks == null)
320                {
321                    objectLocks = new ObjectLocks();
322                    locktable.put(oidString, objectLocks);
323                }
324            }
325            objectLocks.setWriter(writer);
326        }
327    
328            void setWriterByLock(LockEntry writer)
329            {
330                    String oidString = writer.getOidString();
331                    setWriterInternal(writer, oidString);
332            }
333    
334        /**
335         * check if there is a reader lock entry for transaction tx on object obj
336         * in the persistent storage.
337         */
338        public boolean hasReadLock(TransactionImpl tx, Object obj)
339        {
340            checkTimedOutLocks();
341    
342            Identity oid = new Identity(obj,getBroker());
343            String oidString = oid.toString();
344                    String txGuid = tx.getGUID();
345            return hasReadLockInternal(oidString, txGuid);
346        }
347    
348        private boolean hasReadLockInternal(String oidString, String txGuid)
349        {
350            ObjectLocks objectLocks = null;
351            synchronized (locktable)
352            {
353                    objectLocks = (ObjectLocks) locktable.get(oidString);
354            }
355            
356            if (objectLocks == null)
357            {
358                return false;
359            }
360            else
361            {
362                
363                LockEntry reader = objectLocks.getReader(txGuid);
364                if (reader != null)
365                {
366                    return true;
367                }
368                else
369                {
370                    return false;
371                }
372            }
373        }
374    
375            boolean hasReadLock(LockEntry entry)
376            {
377                    String oidString = entry.getOidString();
378                    String txGuid = entry.getTransactionId();
379                    return hasReadLockInternal(oidString,txGuid);
380            }
381    
382        private void checkTimedOutLocks()
383        {
384            if (System.currentTimeMillis() - m_lastCleanupAt > CLEANUP_FREQUENCY)
385            {
386                    removeTimedOutLocks(AbstractLockStrategy.DEFAULT_LOCK_TIMEOUT);
387                m_lastCleanupAt = System.currentTimeMillis();
388            }
389        }
390    
391            /**
392            * removes all timed out lock entries from the persistent storage.
393            * The timeout value can be set in the OJB properties file.
394            */
395        private void removeTimedOutLocks(long timeout)
396        {
397            int count = 0;
398            long maxAge = System.currentTimeMillis() - timeout;
399            boolean breakFromLoop = false;
400            ObjectLocks temp = null;
401            synchronized (locktable)
402            {
403                    Iterator it = locktable.values().iterator();
404                    /**
405                     * run this loop while:
406                     * - we have more in the iterator
407                     * - the breakFromLoop flag hasn't been set
408                     * - we haven't removed more than the limit for this cleaning iteration.
409                     */
410                    while (it.hasNext() && !breakFromLoop && (count <= MAX_LOCKS_TO_CLEAN))
411                    {
412                            temp = (ObjectLocks) it.next();
413                            if (temp.getWriter() != null)
414                            {
415                                    if (temp.getWriter().getTimestamp() < maxAge)
416                                    {
417                                            // writer has timed out, set it to null
418                                            temp.setWriter(null);
419                                    }
420                            }
421                            if (temp.getYoungestReader() < maxAge)
422                            {
423                                    // all readers are older than timeout.
424                                    temp.getReaders().clear();
425                                    if (temp.getWriter() == null)
426                                    {
427                                            // all readers and writer are older than timeout,
428                                            // remove the objectLock from the iterator (which
429                                            // is backed by the map, so it will be removed.
430                                            it.remove();
431                                    }
432                            }
433                            else
434                            {
435                                    // we need to walk each reader.
436                                    Iterator readerIt = temp.getReaders().values().iterator();
437                                    LockEntry readerLock = null;
438                                    while (readerIt.hasNext())
439                                    {
440                                            readerLock = (LockEntry) readerIt.next();
441                                            if (readerLock.getTimestamp() < maxAge)
442                                            {
443                                                    // this read lock is old, remove it.
444                                                    readerIt.remove();
445                                            }
446                                    }
447                            }
448                            count++;
449                    }
450            }
451        }
452        
453        /* (non-Javadoc)
454         * @see org.apache.ojb.broker.util.configuration.Configurable#configure(org.apache.ojb.broker.util.configuration.Configuration)
455         */
456        public void configure(Configuration pConfig) throws ConfigurationException
457        {
458            // noop
459    
460        }
461        
462        int getSize()
463        {
464            return locktable.size();
465        }
466    
467    }