001: /**
002: * Copyright (C) 2007 NetMind Consulting Bt.
003: *
004: * This library is free software; you can redistribute it and/or
005: * modify it under the terms of the GNU Lesser General Public
006: * License as published by the Free Software Foundation; either
007: * version 3 of the License, or (at your option) any later version.
008: *
009: * This library is distributed in the hope that it will be useful,
010: * but WITHOUT ANY WARRANTY; without even the implied warranty of
011: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012: * Lesser General Public License for more details.
013: *
014: * You should have received a copy of the GNU Lesser General Public
015: * License along with this library; if not, write to the Free Software
016: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
017: */package hu.netmind.persistence;
018:
019: import org.apache.log4j.Logger;
020: import java.util.*;
021: import hu.netmind.persistence.node.RemoteLockTracker;
022: import hu.netmind.persistence.node.LockMetaData;
023:
024: /**
025: * This class tracks locks on objects. Any persistable object can be locked, just like
026: * with the <code>synchronized</code> keyword in Java, and it does roughly the same thing
027: * too. If an object is locked, no database operations can occur outside of the lock owner
028: * transaction. All <code>Store</code> operations automatically try to lock the objects
029: * they work with, so there can't be any concurrent modifications.<br>
030: * You can also lock classes or interfaces. These equal to locking database tables, only they are
031: * hierarchical. That means, if you lock a class, all subclasses will also be locked
032: * automatically. For example, if you lock <code>Object.class</code> successfully, then
033: * only the owner of that lock will be able to modify anything. Of course, a class can't be
034: * locked, if there is another thread which holds lock on any super-, or sub-classes, or
035: * an instances of this class, or any subclass.
036: * @author Brautigam Robert
037: * @version Revision: $Revision$
038: */
039: public class LockTracker {
040: private static Logger logger = Logger.getLogger(LockTracker.class);
041:
042: private StoreContext context;
043: private SessionInfoProvider provider;
044:
045: public LockTracker(StoreContext context) {
046: this .context = context;
047: this .provider = new TransactionInfoProvider(context
048: .getTransactionTracker());
049: }
050:
051: public SessionInfoProvider getProvider() {
052: return provider;
053: }
054:
055: public void setProvider(SessionInfoProvider provider) {
056: this .provider = provider;
057: }
058:
059: /**
060: * Lock a single object, and ensure that the object given is the
061: * most recent version of the object. The session information is gathered
062: * from the session info provider.
063: * @throws ConcurrentModificationException If the object is already
064: * locked by another thread.
065: */
066: public void lockEnsureCurrent(Object obj) {
067: lock(new Object[] { obj }, null, -1, true);
068: }
069:
070: /**
071: * Lock a single object. The session information is gathered
072: * from the session info provider.
073: * @throws ConcurrentModificationException If the object is already
074: * locked by another thread.
075: */
076: public void lock(Object obj) {
077: lock(new Object[] { obj }, null, -1, false);
078: }
079:
080: /**
081: * Lock a single object, and guarantee it's current.
082: * The session information is gathered from the session info provider.
083: * @param obj The object to lock.
084: * @param wait Wait the given amount of milliseconds for the lock to
085: * free up. Method only throws ConcurrentModificationException if
086: * the lock is not available in the given time.
087: * @throws ConcurrentModificationException If the object is already
088: * locked by another thread.
089: */
090: public void lockEnsureCurrent(Object obj, int wait) {
091: lock(new Object[] { obj }, null, wait, true);
092: }
093:
094: /**
095: * Lock a single object with wait period given. The session information is gathered
096: * from the session info provider.
097: * @param obj The object to lock.
098: * @param wait Wait the given amount of milliseconds for the lock to
099: * free up. Method only throws ConcurrentModificationException if
100: * the lock is not available in the given time.
101: * @throws ConcurrentModificationException If the object is already
102: * locked by another thread.
103: */
104: public void lock(Object obj, int wait) {
105: lock(new Object[] { obj }, null, wait, false);
106: }
107:
108: /**
109: * Lock a single object with the session information given,
110: * and check is the given object is the current version.
111: * @throws ConcurrentModificationException If the object is already
112: * locked by another thread.
113: */
114: public void lockEnsureCurrent(Object obj, SessionInfo info) {
115: lock(new Object[] { obj }, info, -1, true);
116: }
117:
118: /**
119: * Lock a single object with the session information given.
120: * @throws ConcurrentModificationException If the object is already
121: * locked by another thread.
122: */
123: public void lock(Object obj, SessionInfo info) {
124: lock(new Object[] { obj }, info, -1, false);
125: }
126:
127: /**
128: * Lock multiple objects, and check whether given objects
129: * are current. The session information is gathered
130: * from the session info provider. Use this method, if you want
131: * to lock multiple objects at the same time.
132: * @throws ConcurrentModificationException If the object is already
133: * locked by another thread.
134: */
135: public void lockEnsureCurrent(Object[] objs) {
136: lock(objs, null, -1, true);
137: }
138:
139: /**
140: * Lock multiple objects. The session information is gathered
141: * from the session info provider. Use this method, if you want
142: * to lock multiple objects at the same time.
143: * @throws ConcurrentModificationException If the object is already
144: * locked by another thread.
145: */
146: public void lock(Object[] objs) {
147: lock(objs, null, -1, false);
148: }
149:
150: /**
151: * Lock multiple objects, and check whether given objects
152: * are current. The session information is gathered
153: * from the session info provider. Use this method, if you want
154: * to lock multiple objects at the same time.
155: * @param wait Wait the given amount of milliseconds for the lock to
156: * free up. Method only throws ConcurrentModificationException if
157: * the lock is not available in the given time.
158: * @throws ConcurrentModificationException If the object is already
159: * locked by another thread.
160: */
161: public void lockEnsureCurrent(Object[] objs, int wait) {
162: lock(objs, null, wait, true);
163: }
164:
165: /**
166: * Lock multiple objects. The session information is gathered
167: * from the session info provider. Use this method, if you want
168: * to lock multiple objects at the same time.
169: * @param wait Wait the given amount of milliseconds for the lock to
170: * free up. Method only throws ConcurrentModificationException if
171: * the lock is not available in the given time.
172: * @throws ConcurrentModificationException If the object is already
173: * locked by another thread.
174: */
175: public void lock(Object[] objs, int wait) {
176: lock(objs, null, wait, false);
177: }
178:
179: /**
180: * Lock multiple objects with all possible parameters specified.
181: * Use this method, if you want to lock multiple objects at the same time.
182: * If classes are asked to be ensured to be current, the following date is
183: * taken into account:
184: * <ul>
185: * <li>If the lock is called from inside a transaction, the
186: * transaction's first operation's date is taken.</li>
187: * <li>If there are regular objects with this lock call, then
188: * the oldest object's read date is taken.</li>
189: * </ul>
190: * Whichever was earlier, the classes are checked against that date.
191: * @param objs The object to lock simultaniously.
192: * @param info The session info to memorize for this lock. This object will
193: * be included in the ConcurrentModificationException.
194: * @param wait Wait the given amount of milliseconds for the lock to
195: * free up. Method only throws ConcurrentModificationException if
196: * the lock is not available in the given time.
197: * @param ensureCurrent Do the objects need to be guaranteed to be current.
198: * If this flag is set, the lock operation will fail, if any supplied
199: * object has a newer version.
200: * @throws ConcurrentModificationException If the object is already
201: * locked by another process.
202: */
203: public synchronized void lock(Object[] objs, SessionInfo info,
204: int wait, boolean ensureCurrent) {
205: if (logger.isDebugEnabled())
206: logger.debug("locking " + objs.length + " objects, info: "
207: + info + ", wait: " + wait);
208: // Allocate session info
209: if (info == null) {
210: info = new SessionInfo(); // Empty
211: try {
212: if (provider != null)
213: info = provider.getSessionInfo();
214: } catch (Throwable e) {
215: logger
216: .warn(
217: "could not allocate session info from provider",
218: e);
219: }
220: }
221: // If this lock operation is inside a transacion,
222: // then the start of the transaction will be the
223: // date the classes (tables) are ensure to be current.
224: // So if inside a transaction a class is asked to be
225: // ensured actual, then it's ensured that it's not
226: // been tampered with since the beginning of the transacion.
227: Transaction tx = context.getTransactionTracker()
228: .getTransaction(TransactionTracker.TX_OPTIONAL);
229: Long firstSerial = null;
230: long txSerial = 0;
231: if (tx != null) {
232: firstSerial = tx.getSerial();
233: logger
234: .debug("there was a transaction, classes will be compared to at least: "
235: + firstSerial);
236: if (tx.getSerial() != null)
237: txSerial = tx.getSerial().longValue();
238: }
239: // Create metadata for all objects
240: Vector remoteLockMetas = new Vector();
241: for (int i = 0; i < objs.length; i++) {
242: LockMetaData meta = new LockMetaData();
243: if (objs[i] instanceof Class) {
244: // Classes
245: meta.setObjectClass((Class) objs[i]);
246: } else {
247: // Objects
248: meta.setObjectClass(objs[i].getClass());
249: PersistenceMetaData persistenceMeta = context
250: .getObjectTracker().getMetaData(objs[i]);
251: meta.setObjectId(persistenceMeta.getPersistenceId());
252: meta.setQuerySerial(persistenceMeta.getQuerySerial());
253: meta.setStartSerial(persistenceMeta
254: .getPersistenceStart());
255: meta.setEndSerial(persistenceMeta.getPersistenceEnd());
256: // Check if this query serial is older than the first date current
257: if ((meta.getQuerySerial() != null)
258: && ((firstSerial == null) || (firstSerial
259: .longValue() < meta.getQuerySerial()
260: .longValue())))
261: firstSerial = meta.getQuerySerial();
262: }
263: remoteLockMetas.add(meta);
264: }
265: // Re-set the oldest serial to the class-only metas
266: for (int i = 0; i < remoteLockMetas.size(); i++) {
267: LockMetaData meta = (LockMetaData) remoteLockMetas.get(i);
268: if (meta.getObjectId() == null)
269: meta.setQuerySerial(firstSerial);
270: }
271: // Now make an ordered list of the remote lock objects. It should be
272: // ordered, because that reduces the likelyhood of a deadlock.
273: Collections.sort(remoteLockMetas);
274: // Lock with central lock tracker
275: SessionInfo oldInfo = context.getNodeManager().lock(
276: context.getNodeManager().getNodeIndex(),
277: Thread.currentThread().getId(), txSerial,
278: remoteLockMetas, info, wait, ensureCurrent);
279: if (oldInfo != null)
280: throw new ConcurrentModificationException(oldInfo, objs,
281: "Tried to lock, but was already locked.");
282: // All is good in lockworld
283: logger.debug("successful lock operation.");
284: }
285:
286: /**
287: * Unlock a single object. If the object is not locked, nothing is done.
288: */
289: public void unlock(Object obj) {
290: unlock(new Object[] { obj });
291: }
292:
293: /**
294: * Unlock multiple objects.
295: */
296: public synchronized void unlock(Object[] objs) {
297: if (logger.isDebugEnabled())
298: logger.debug("unlocking " + objs.length + " objects.");
299: // The transaction if there is any
300: Transaction tx = context.getTransactionTracker()
301: .getTransaction(TransactionTracker.TX_OPTIONAL);
302: long txSerial = 0;
303: if ((tx != null) && (tx.getSerial() != null))
304: txSerial = tx.getSerial().longValue();
305: // Go through all objects and create lock metadata
306: Vector remoteLockMetas = new Vector();
307: for (int i = 0; i < objs.length; i++) {
308: LockMetaData meta = new LockMetaData();
309: if (objs[i] instanceof Class) {
310: // Classes
311: meta.setObjectClass((Class) objs[i]);
312: } else {
313: // Objects
314: meta.setObjectClass(objs[i].getClass());
315: PersistenceMetaData persistenceMeta = context
316: .getObjectTracker().getMetaData(objs[i]);
317: meta.setObjectId(persistenceMeta.getPersistenceId());
318: }
319: remoteLockMetas.add(meta);
320: }
321: // Remote unlock
322: try {
323: context.getNodeManager().unlock(
324: context.getNodeManager().getNodeIndex(),
325: Thread.currentThread().getId(), txSerial,
326: remoteLockMetas);
327: } catch (Exception e) {
328: // Do not throw excetion here, because unlock only fails,
329: // if connection is severed, in which case the server will
330: // invalidate all locks either way.
331: logger.warn("could not unlock remotely", e);
332: }
333: }
334:
335: }
|