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 }