001: /* Copyright 2002 The JA-SIG Collaborative. All rights reserved.
002: * See license distributed with this file and
003: * available online at http://www.uportal.org/license.html
004: */
005:
006: package org.jasig.portal.concurrency.locking;
007:
008: import java.util.Date;
009:
010: import org.jasig.portal.EntityIdentifier;
011: import org.jasig.portal.concurrency.IEntityLock;
012: import org.jasig.portal.concurrency.IEntityLockService;
013: import org.jasig.portal.concurrency.LockingException;
014: import org.jasig.portal.properties.PropertiesManager;
015: import org.apache.commons.logging.Log;
016: import org.apache.commons.logging.LogFactory;
017:
018: /**
019: * @author Dan Ellentuck
020: * @version $Revision: 35550 $
021: */
022: public class ReferenceEntityLockService implements IEntityLockService {
023: private static final Log log = LogFactory
024: .getLog(ReferenceEntityLockService.class);
025:
026: // Singleton instance:
027: private static IEntityLockService singleton = null;
028:
029: // Store for IEntityLocks:
030: private IEntityLockStore lockStore = null;
031:
032: // Locking properties, initialized with default values, are settable
033: // via portal.properties:
034:
035: // Are we running in a multi-server environment? If so, the lock store
036: // will be in persistent storage.
037: private boolean multiServer = false;
038:
039: // Lifetime of a lock in seconds, defaults to 5 minutes.
040: private int defaultLockPeriod = 300;
041:
042: /* Fudge factor in milliseconds, extends the apparent expiration times
043: * of potentially conflicting locks beyond their actual expirations.
044: * We only use it when checking for locking conflicts and then only if
045: * inMemory == false. Defaults to 5000.
046: */
047: private int lockToleranceMillis = 5000;
048:
049: /**
050: * ReferenceEntityLockingService constructor comment.
051: */
052: public ReferenceEntityLockService() throws LockingException {
053: super ();
054: initialize();
055: }
056:
057: /**
058: * Attempts to change the lock's <code>lockType</code> to <code>newType</code>.
059: * @param lock IEntityLock
060: * @param newType int
061: * @exception org.jasig.portal.concurrency.LockingException
062: */
063: public void convert(IEntityLock lock, int newType)
064: throws LockingException {
065: convert(lock, newType, defaultLockPeriod);
066: }
067:
068: /**
069: * Attempts to change the lock's <code>lockType</code> to <code>newType</code>.
070: * @param lock IEntityLock
071: * @param newType int
072: * @param newDuration int
073: * @exception org.jasig.portal.concurrency.LockingException
074: */
075: public void convert(IEntityLock lock, int newType, int newDuration)
076: throws LockingException {
077: if (lock.getLockType() == newType) {
078: throw new LockingException("Could not convert " + lock
079: + " : old and new lock TYPEs are the same.");
080: }
081:
082: if (!isValidLockType(newType)) {
083: throw new LockingException("Could not convert " + lock
084: + " : lock TYPE " + newType + " is invalid.");
085: }
086:
087: if (!isValid(lock)) {
088: throw new LockingException("Could not convert " + lock
089: + " : lock is invalid.");
090: }
091:
092: if (newType == WRITE_LOCK
093: && retrieveLocks(lock.getEntityType(), lock
094: .getEntityKey(), null).length > 1) {
095: throw new LockingException("Could not convert " + lock
096: + " : another lock already exists.");
097: }
098:
099: if (newType == READ_LOCK) { /* Can always convert to READ */
100: }
101:
102: Date newExpiration = getNewExpiration(newDuration);
103: getLockStore()
104: .update(lock, newExpiration, new Integer(newType));
105: ((EntityLockImpl) lock).setLockType(newType);
106: ((EntityLockImpl) lock).setExpirationTime(newExpiration);
107: }
108:
109: /**
110: * Answer if this <code>IEntityLock</code> exists in the store.
111: * @param lock
112: * @return boolean
113: */
114: public boolean existsInStore(IEntityLock lock)
115: throws LockingException {
116: Class entityType = lock.getEntityType();
117: String key = lock.getEntityKey();
118: Integer lockType = new Integer(lock.getLockType());
119: Date expiration = lock.getExpirationTime();
120: String owner = lock.getLockOwner();
121: IEntityLock[] lockArray = getLockStore().find(entityType, key,
122: lockType, expiration, owner);
123:
124: return (lockArray.length > 0);
125: }
126:
127: /**
128: * @return int
129: */
130: private int getDefaultLockPeriod() {
131: return defaultLockPeriod;
132: }
133:
134: /**
135: * @return org.jasig.portal.concurrency.locking.IEntityLockStore
136: */
137: private IEntityLockStore getLockStore() {
138: return lockStore;
139: }
140:
141: /**
142: * @return int
143: */
144: private int getLockToleranceMillis() {
145: return lockToleranceMillis;
146: }
147:
148: /**
149: * @return java.util.Date
150: */
151: private Date getNewExpiration(int durationSecs) {
152: return new Date(System.currentTimeMillis()
153: + (durationSecs * 1000));
154: }
155:
156: /**
157: * @exception LockingException
158: */
159: private void initialize() throws LockingException {
160: String eMsg = null;
161:
162: try {
163: multiServer = PropertiesManager.getPropertyAsBoolean(
164: "org.jasig.portal.concurrency.multiServer", false);
165:
166: lockStore = (multiServer) ? RDBMEntityLockStore.singleton()
167: : MemoryEntityLockStore.singleton();
168: } catch (Exception e) {
169: eMsg = "ReferenceEntityLockingService.initialize(): Failed to instantiate entity lock store. "
170: + e;
171: log.error(eMsg);
172: throw new LockingException(eMsg);
173: }
174:
175: try {
176: int lockDuration = PropertiesManager
177: .getPropertyAsInt("org.jasig.portal.concurrency.IEntityLockService.defaultLockDuration");
178: setDefaultLockPeriod(lockDuration);
179: } catch (Exception ex) { /* defaults to 5 minutes. */
180: }
181:
182: if (multiServer) {
183: try {
184: int lockTolerance = PropertiesManager
185: .getPropertyAsInt("org.jasig.portal.concurrency.clockTolerance");
186: setLockToleranceMillis(lockTolerance);
187: } catch (Exception ex) { /* defaults to 0. */
188: }
189: }
190: }
191:
192: /**
193: * Answers if the entity represented by the entityType and entityKey already
194: * has a lock of some type.
195: *
196: * @param entityType
197: * @param entityKey
198: * @exception org.jasig.portal.concurrency.LockingException
199: */
200: private boolean isLocked(Class entityType, String entityKey)
201: throws LockingException {
202: return isLocked(entityType, entityKey, null);
203: }
204:
205: /**
206: * Answers if the entity represented by entityType and entityKey has one
207: * or more locks. Param <code>lockType</code> can be null.
208: *
209: * @param entityType
210: * @param entityKey
211: * @param lockType (optional)
212: * @exception org.jasig.portal.concurrency.LockingException
213: */
214: private boolean isLocked(Class entityType, String entityKey,
215: Integer lockType) throws LockingException {
216: IEntityLock[] locks = retrieveLocks(entityType, entityKey,
217: lockType);
218: return locks.length > 0;
219: }
220:
221: /**
222: * @return boolean
223: */
224: private boolean isMultiServer() {
225: return multiServer;
226: }
227:
228: /**
229: * @param lock IEntityLock
230: * @return boolean
231: */
232: private boolean isUnexpired(IEntityLock lock) {
233: return lock.getExpirationTime().getTime() > System
234: .currentTimeMillis();
235: }
236:
237: /**
238: * Answers if this <code>IEntityLock</code> represents a lock that is still
239: * good. To be valid, a lock must exist in the underlying store and be
240: * unexpired.
241: *
242: * @param lock IEntityLock
243: * @exception org.jasig.portal.concurrency.LockingException
244: */
245: public boolean isValid(IEntityLock lock) throws LockingException {
246: return isUnexpired(lock) && existsInStore(lock);
247: }
248:
249: /**
250: *
251: */
252: private boolean isValidLockType(int lockType) {
253: return ((lockType == READ_LOCK) || (lockType == WRITE_LOCK));
254: }
255:
256: /**
257: * Returns a lock for the entity, lock type and owner if no conflicting locks exist.
258: * @param entityType
259: * @param entityKey
260: * @param lockType
261: * @param owner
262: * @return org.jasig.portal.groups.IEntityLock
263: * @exception LockingException
264: */
265: public IEntityLock newLock(Class entityType, String entityKey,
266: int lockType, String owner) throws LockingException {
267: return newLock(entityType, entityKey, lockType, owner,
268: defaultLockPeriod);
269: }
270:
271: /**
272: * Returns a lock for the entity, lock type and owner if no conflicting locks exist.
273: * @param entityType
274: * @param entityKey
275: * @param lockType
276: * @param owner
277: * @param durationSecs
278: * @return org.jasig.portal.groups.IEntityLock
279: * @exception LockingException
280: *
281: * Retrieves potentially conflicting locks and checks them before adding
282: * the new lock to the store. The add of a write lock will fail if any
283: * other lock exists for the entity. The add of a read lock will fail if
284: * a write lock exists for the entity. After we add a write lock we
285: * check the store a second time and roll back if any other lock has snuck
286: * in. I think this is slightly safer than depending on the db isolation
287: * level for transactional integrity.
288: */
289: public IEntityLock newLock(Class entityType, String entityKey,
290: int lockType, String owner, int durationSecs)
291: throws LockingException {
292: int expirationSecs = durationSecs;
293: Date expires = getNewExpiration(expirationSecs);
294: IEntityLock newLock = new EntityLockImpl(entityType, entityKey,
295: lockType, expires, owner, this );
296:
297: // retrieve potentially conflicting locks:
298: IEntityLock[] locks = retrieveLocks(entityType, entityKey, null);
299:
300: if (lockType == WRITE_LOCK) {
301: if (locks.length > 0) {
302: throw new LockingException(
303: "Could not create lock: entity already locked.");
304: }
305:
306: getLockStore().add(newLock);
307:
308: locks = retrieveLocks(entityType, entityKey, null);
309: if (locks.length > 1) // another lock snuck in
310: {
311: release(newLock);
312: throw new LockingException(
313: "Could not create lock: entity already locked.");
314: }
315: }
316:
317: else // ( lockType == READ_LOCK )
318: {
319: for (int i = 0; i < locks.length; i++) {
320: if (locks[i].getLockType() == WRITE_LOCK) {
321: throw new LockingException(
322: "Could not create lock: entity already write locked.");
323: } else {
324: if (locks[i].equals(newLock)) {
325: // another read lock from the same owner; bump the expiration time.
326: expirationSecs++;
327: expires = getNewExpiration(expirationSecs);
328: newLock = new EntityLockImpl(entityType,
329: entityKey, lockType, expires, owner,
330: this );
331: }
332: }
333: }
334: getLockStore().add(newLock);
335: }
336: return newLock;
337: }
338:
339: /**
340: * Returns a lock for the entity, lock type and owner if no conflicting locks exist.
341: * @return org.jasig.portal.groups.IEntityLock
342: * @param entityID org.jasig.portal.EntityIdentifier
343: * @param lockType int
344: * @param owner String
345: * @exception LockingException
346: */
347: public IEntityLock newLock(EntityIdentifier entityID, int lockType,
348: String owner) throws LockingException {
349: return newLock(entityID.getType(), entityID.getKey(), lockType,
350: owner, defaultLockPeriod);
351: }
352:
353: /**
354: * Returns a lock for the entity, lock type and owner if no conflicting locks exist.
355: * @return org.jasig.portal.groups.IEntityLock
356: * @param entityID org.jasig.portal.EntityIdentifier
357: * @param lockType int
358: * @param owner String
359: * @param durationSecs int
360: * @exception LockingException
361: */
362: public IEntityLock newLock(EntityIdentifier entityID, int lockType,
363: String owner, int durationSecs) throws LockingException {
364: return newLock(entityID.getType(), entityID.getKey(), lockType,
365: owner, durationSecs);
366: }
367:
368: /**
369: * Releases the <code>IEntityLock</code>.
370: * @param lock IEntityLock
371: * @exception LockingException
372: */
373: public void release(IEntityLock lock) throws LockingException {
374: getLockStore().delete(lock);
375: ((EntityLockImpl) lock).setExpirationTime(new Date(0));
376: }
377:
378: /**
379: * Extends the expiration time of the lock by some service-defined increment.
380: * @param lock IEntityLock
381: * @exception LockingException
382: */
383: public void renew(IEntityLock lock) throws LockingException {
384: renew(lock, defaultLockPeriod);
385: }
386:
387: /**
388: * Extends the expiration time of the lock by some service-defined increment.
389: * @param lock IEntityLock
390: * @exception LockingException
391: */
392: public void renew(IEntityLock lock, int duration)
393: throws LockingException {
394: if (isValid(lock)) {
395: Date newExpiration = getNewExpiration(duration);
396: getLockStore().update(lock, newExpiration);
397: ((EntityLockImpl) lock).setExpirationTime(newExpiration);
398: } else {
399: throw new LockingException("Could not renew " + lock
400: + " : lock is invalid.");
401: }
402: }
403:
404: /**
405: * Returns an IEntityLock[] containing unexpired locks for the entityType, entityKey
406: * and lockType. Param <code>lockType</code> can be null.
407: *
408: * @param entityType
409: * @param entityKey
410: * @param lockType (optional)
411: * @exception LockingException
412: */
413: private IEntityLock[] retrieveLocks(Class entityType,
414: String entityKey, Integer lockType) throws LockingException {
415: Date expiration = (multiServer) ? new Date(System
416: .currentTimeMillis()
417: - getLockToleranceMillis()) : new Date();
418:
419: return getLockStore().findUnexpired(expiration, entityType,
420: entityKey, lockType, null);
421: }
422:
423: /**
424: * @param newDefaultLockPeriod int
425: */
426: private void setDefaultLockPeriod(int newDefaultLockPeriod) {
427: defaultLockPeriod = newDefaultLockPeriod;
428: }
429:
430: /**
431: * @param newLockToleranceMillis int
432: */
433: private void setLockToleranceMillis(int newLockToleranceMillis) {
434: lockToleranceMillis = newLockToleranceMillis;
435: }
436:
437: /**
438: * @param newMultiServer boolean
439: */
440: private void setMultiServer(boolean newMultiServer) {
441: multiServer = newMultiServer;
442: }
443:
444: /**
445: * @return org.jasig.portal.concurrency.locking.ReferenceEntityLockService
446: */
447: public static synchronized IEntityLockService singleton()
448: throws LockingException {
449: if (singleton == null) {
450: singleton = new ReferenceEntityLockService();
451: }
452: return singleton;
453: }
454: }
|