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