001: //$HeadURL: svn+ssh://mschneider@svn.wald.intevation.org/deegree/base/trunk/src/org/deegree/io/datastore/FeatureId.java $
002: /*---------------- FILE HEADER ------------------------------------------
003:
004: This file is part of deegree.
005: Copyright (C) 2001-2008 by:
006: EXSE, Department of Geography, University of Bonn
007: http://www.giub.uni-bonn.de/deegree/
008: lat/lon GmbH
009: http://www.lat-lon.de
010:
011: This library is free software; you can redistribute it and/or
012: modify it under the terms of the GNU Lesser General Public
013: License as published by the Free Software Foundation; either
014: version 2.1 of the License, or (at your option) any later version.
015:
016: This library is distributed in the hope that it will be useful,
017: but WITHOUT ANY WARRANTY; without even the implied warranty of
018: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
019: Lesser General Public License for more details.
020:
021: You should have received a copy of the GNU Lesser General Public
022: License along with this library; if not, write to the Free Software
023: Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
024:
025: Contact:
026:
027: Andreas Poth
028: lat/lon GmbH
029: Aennchenstr. 19
030: 53115 Bonn
031: Germany
032: E-Mail: poth@lat-lon.de
033:
034: Prof. Dr. Klaus Greve
035: Department of Geography
036: University of Bonn
037: Meckenheimer Allee 166
038: 53115 Bonn
039: Germany
040: E-Mail: greve@giub.uni-bonn.de
041:
042:
043: ---------------------------------------------------------------------------*/
044: package org.deegree.io.datastore;
045:
046: import java.io.File;
047: import java.io.FileInputStream;
048: import java.io.FileOutputStream;
049: import java.io.FilenameFilter;
050: import java.io.ObjectInputStream;
051: import java.io.ObjectOutputStream;
052: import java.util.ArrayList;
053: import java.util.Date;
054: import java.util.HashMap;
055: import java.util.List;
056: import java.util.Map;
057: import java.util.Set;
058: import java.util.TreeMap;
059: import java.util.TreeSet;
060: import java.util.UUID;
061:
062: import org.deegree.framework.log.ILogger;
063: import org.deegree.framework.log.LoggerFactory;
064: import org.deegree.i18n.Messages;
065: import org.deegree.ogcwebservices.wfs.operation.LockFeature;
066:
067: /**
068: * Keeps track of all persistent features that are in a locked state, i.e. a {@link LockFeature}
069: * request has been issued to lock them.
070: * <p>
071: * Locked features cannot be updated or deleted except by transactions that specify the appropriate
072: * lock identifier.
073: * <p>
074: * The <code>LockManager</code> also ensures that active locks survive a restart of the VM -
075: * therefore it keeps serialized and up-to-date versions of all active {@link Lock} instances in a
076: * temporary directory. The directory is specified by the <code>java.io.tmpdir</code> system
077: * property. On first initialization, i.e. the first call to {@link #getInstance()}, the directory
078: * is scanned for all files matching the pattern <code>deegree-lock*.tmp</code>, and these are
079: * deserialized to rebuild the <code>LockManager</code>'s status.
080: *
081: * @author <a href="mailto:schneider@lat-lon.de">Markus Schneider</a>
082: * @author last edited by: $Author:$
083: *
084: * @version $Revision:$, $Date:$
085: */
086: public class LockManager {
087:
088: private static final ILogger LOG = LoggerFactory
089: .getLogger(LockManager.class);
090:
091: static String FILE_PREFIX = "deegree-lock";
092:
093: static String FILE_SUFFIX = ".tmp";
094:
095: private static LockManager instance;
096:
097: private static File workingDir;
098:
099: // maps expiry time to Lock, first key is the one that will timeout first
100: private TreeMap<Long, Lock> expiryTimeToLock = new TreeMap<Long, Lock>();
101:
102: private Map<String, Lock> lockIdToLock = new HashMap<String, Lock>();
103:
104: private Map<String, Lock> fidToLock = new HashMap<String, Lock>();
105:
106: private Map<Lock, File> lockToFile = new HashMap<Lock, File>();
107:
108: private LockManager(File workingDir) throws DatastoreException {
109: if (workingDir == null) {
110: String msg = "No working directory for the lock manager specified. Using default temp directory: '"
111: + System.getProperty("java.io.tmpdir") + "'";
112: LOG.logInfo(msg);
113: workingDir = new File(System.getProperty("java.io.tmpdir"));
114: }
115:
116: LockManager.workingDir = workingDir;
117: if (!workingDir.isDirectory()) {
118: String msg = "Specified working directory for the lock manager '"
119: + workingDir + "' does not denote a directory.";
120: throw new DatastoreException(msg);
121: }
122: if (!workingDir.canWrite()) {
123: String msg = "Cannot write to the lock manager's working directory ('"
124: + workingDir + "' ).";
125: throw new DatastoreException(msg);
126: }
127:
128: String msg = "Lock manager will use directory '" + workingDir
129: + "' to persist it's locks.";
130: LOG.logInfo(msg);
131:
132: restoreLocks();
133: checkForExpiredLocks();
134: }
135:
136: private void restoreLocks() {
137:
138: // get all persistent locks from temporary directory
139: String[] fileNames = workingDir.list(new FilenameFilter() {
140: public boolean accept(File dir, String name) {
141: return name.startsWith(FILE_PREFIX)
142: && name.endsWith(FILE_SUFFIX);
143: }
144: });
145:
146: if (fileNames != null) {
147: String msg = Messages.getMessage(
148: "DATASTORE_LOCK_RESTORING", fileNames.length,
149: workingDir);
150: LOG.logInfo(msg);
151: for (String fileName : fileNames) {
152: File file = new File(workingDir + File.separator
153: + fileName);
154: try {
155: FileInputStream fis = null;
156: ObjectInputStream ois = null;
157: try {
158: fis = new FileInputStream(file);
159: ois = new ObjectInputStream(fis);
160: Lock lock = (Lock) ois.readObject();
161: registerLock(lock, file);
162: } finally {
163: if (ois != null) {
164: ois.close();
165: }
166: }
167: } catch (Exception e) {
168: msg = Messages.getMessage(
169: "DATASTORE_LOCK_RESTORE_FAILED", fileName);
170: LOG.logError(msg, e);
171: }
172: }
173: }
174: }
175:
176: /**
177: * Initializes the <code>LockManager</code>.
178: *
179: * @param workingDir
180: * directory where the <code>LockManager</code> will persists its locks
181: * @throws DatastoreException
182: */
183: public static synchronized void initialize(File workingDir)
184: throws DatastoreException {
185: if (instance != null) {
186: String msg = "LockManager has already been initialized.";
187: throw new DatastoreException(msg);
188: }
189: instance = new LockManager(workingDir);
190: }
191:
192: /**
193: * Returns the only instance of <code>LockManager</code>.
194: *
195: * @return the only instance of <code>LockManager</code>
196: */
197: public static synchronized LockManager getInstance() {
198: if (instance == null) {
199: String msg = "LockManager has not been initialized yet.";
200: throw new RuntimeException(msg);
201: }
202: return instance;
203: }
204:
205: /**
206: * Returns whether the specified feature is locked.
207: *
208: * @param fid
209: * id of the feature
210: * @return true, if the specified feature is locked, false otherwise
211: */
212: public boolean isLocked(FeatureId fid) {
213: return getLockId(fid) != null;
214: }
215:
216: /**
217: * Returns the id of the lock that locks the specified feature (if it is locked).
218: *
219: * @param fid
220: * id of the feature
221: * @return the lock id, or null if it is not locked
222: */
223: public String getLockId(FeatureId fid) {
224:
225: checkForExpiredLocks();
226:
227: String lockId = null;
228: synchronized (this ) {
229: Lock lock = this .fidToLock.get(fid.getAsString());
230: if (lock != null) {
231: lockId = lock.getId();
232: }
233: }
234: return lockId;
235: }
236:
237: /**
238: * Acquires a lock for the given {@link LockFeature} request. The affected feature instances and
239: * their descendant features + super features have to be specified as well.
240: * <p>
241: * If the lockAction in the request is set to ALL and not all requested features could be
242: * locked, a {@link DatastoreException} will be thrown.
243: * <p>
244: * If no features have been locked at all, a lock will be issued, but the lock is not registered
245: * (as requested by the WFS spec.).
246: *
247: * @param request
248: * <code>LockFeature</code> request
249: * @param fidsToLock
250: * all feature instances that are affected by the request
251: * @return the acquired lock, never null
252: * @throws DatastoreException
253: */
254: public Lock acquireLock(LockFeature request,
255: List<FeatureId> fidsToLock) throws DatastoreException {
256:
257: checkForExpiredLocks();
258:
259: Lock lock = null;
260:
261: synchronized (this ) {
262: String lockId = UUID.randomUUID().toString();
263:
264: Set<String> lockableFids = new TreeSet<String>();
265: List<String> notLockableFids = new ArrayList<String>(
266: fidsToLock.size());
267:
268: for (FeatureId fid : fidsToLock) {
269: String fidAsString = fid.getAsString();
270: if (this .fidToLock.get(fidAsString) != null) {
271: notLockableFids.add(fidAsString);
272: } else {
273: lockableFids.add(fidAsString);
274: }
275: }
276:
277: if (request.lockAllFeatures() && !notLockableFids.isEmpty()) {
278: StringBuffer sb = new StringBuffer();
279: for (int i = 0; i < notLockableFids.size(); i++) {
280: sb.append(notLockableFids.get(i));
281: if (i != notLockableFids.size() - 1) {
282: sb.append(", ");
283: }
284: }
285: String msg = Messages.getMessage(
286: "DATASTORE_LOCK_SOME_HELD", sb);
287: throw new DatastoreException(msg);
288: }
289:
290: if (!lockableFids.isEmpty()) {
291: long duration = request.getExpiry();
292: long expiryTime = System.currentTimeMillis() + duration;
293: lock = new Lock(lockId, lockableFids, expiryTime);
294: File file = persistLock(lock);
295: registerLock(lock, file);
296: } else {
297: lock = new Lock(lockId, lockableFids, System
298: .currentTimeMillis());
299: String msg = Messages.getMessage(
300: "DATASTORE_EMPTY_LOCK", lockId);
301: LOG.logInfo(msg);
302: }
303: }
304: return lock;
305: }
306:
307: /**
308: * Releases the specified lock completely (all associated features are unlocked) and removes it
309: * (also from the temporary directory).
310: *
311: * @param lockId
312: * lock identifier
313: * @throws DatastoreException
314: */
315: public void releaseLock(String lockId) throws DatastoreException {
316: synchronized (this ) {
317: Lock lock = this .lockIdToLock.get(lockId);
318: if (lock == null) {
319: String msg = Messages.getMessage(
320: "DATASTORE_UNKNOWN_LOCK", lockId);
321: throw new DatastoreException(msg);
322: }
323: releaseLock(lock);
324: }
325: }
326:
327: /**
328: * Releases the given lock completely (all associated features are unlocked) and removes it
329: * (also from the temporary directory).
330: *
331: * @param lock
332: * lock to be released
333: */
334: public void releaseLock(Lock lock) {
335: synchronized (this ) {
336: this .lockIdToLock.remove(lock.getId());
337: this .expiryTimeToLock.remove(lock.getExpiryTime());
338: Set<String> lockedFids = lock.getLockedFids();
339: for (String fid : lockedFids) {
340: this .fidToLock.remove(fid);
341: }
342: File file = this .lockToFile.get(lock);
343: file.delete();
344: this .lockToFile.remove(lock);
345: }
346: }
347:
348: /**
349: * Releases the specified lock partly (all specified features are unlocked).
350: * <p>
351: * If there are no more features associated with the lock, the lock is removed.
352: *
353: * @param lockId
354: * lock identifier
355: * @param unlockFids
356: * features to be unlocked
357: * @throws DatastoreException
358: */
359: public void releaseLockPartly(String lockId,
360: Set<FeatureId> unlockFids) throws DatastoreException {
361:
362: synchronized (this ) {
363: Lock lock = this .lockIdToLock.get(lockId);
364: if (lock == null) {
365: String msg = Messages.getMessage(
366: "DATASTORE_UNKNOWN_LOCK", lockId);
367: throw new DatastoreException(msg);
368: }
369:
370: Set<String> lockedFeatures = lock.getLockedFids();
371:
372: for (FeatureId fid : unlockFids) {
373: String fidAsString = fid.getAsString();
374: this .fidToLock.remove(fidAsString);
375: lockedFeatures.remove(fidAsString);
376: }
377:
378: if (lockedFeatures.isEmpty()) {
379: String msg = Messages.getMessage(
380: "DATASTORE_LOCK_CLEARED", lock.getId());
381: LOG.logInfo(msg);
382: this .lockIdToLock.remove(lockId);
383: this .expiryTimeToLock.remove(lock.getExpiryTime());
384: }
385: persistLock(lock);
386: }
387: }
388:
389: /**
390: * Checks for expired locks and releases them.
391: */
392: private void checkForExpiredLocks() {
393: synchronized (this ) {
394: while (!this .expiryTimeToLock.isEmpty()) {
395: long expiry = this .expiryTimeToLock.firstKey();
396: if (expiry > System.currentTimeMillis()) {
397: break;
398: }
399: Lock lock = this .expiryTimeToLock.get(expiry);
400: String msg = Messages.getMessage(
401: "DATASTORE_LOCK_EXPIRED", lock.getId(),
402: new Date(lock.getExpiryTime()));
403: LOG.logInfo(msg);
404: releaseLock(lock);
405: }
406: }
407: }
408:
409: /**
410: * Registers the given lock in the lookup maps of the <code>LockManager</code>.
411: * <p>
412: * This includes:
413: * <ul>
414: * <li><code>fidToLock-Map</code></li>
415: * <li><code>lockIdToLock-Map</code></li>
416: * <li><code>expiryTimeToLock-Map</code></li>
417: * <li><code>lockToFile-Map</code></li>
418: * </ul>
419: *
420: * @param lock
421: * the lock to be registered
422: * @param file
423: * file that stores the persistent representation of the lock
424: */
425: private void registerLock(Lock lock, File file) {
426: for (String fid : lock.getLockedFids()) {
427: this .fidToLock.put(fid, lock);
428: }
429: this .expiryTimeToLock.put(lock.getExpiryTime(), lock);
430: this .lockIdToLock.put(lock.getId(), lock);
431: this .lockToFile.put(lock, file);
432: String msg = Messages.getMessage("DATASTORE_LOCK_TIMEOUT_INFO",
433: lock.getId(), new Date(lock.getExpiryTime()));
434: LOG.logInfo(msg);
435: }
436:
437: /**
438: * Persists the given {@link Lock} to a temporary directory.
439: * <p>
440: * <ul>
441: * <li>If the lock is empty (it holds no features), a potentially existing file is deleted.</li>
442: * <li>If the lock is not empty and it has not been stored yet, it is written to a temporary
443: * file.</li>
444: * <li>If the lock is not empty and it has already been stored, the existing file is
445: * overwritten with the current lock status.</li>
446: * </ul>
447: *
448: * @param lock
449: * the <code>Lock</code> to be persisted
450: * @return <code>File</code> that stores the <code>Lock</code>, may be null
451: * @throws DatastoreException
452: */
453: private File persistLock(Lock lock) throws DatastoreException {
454:
455: File file = this .lockToFile.get(lock);
456: if (!lock.getLockedFids().isEmpty()) {
457: // only store it if any features are hold by the lock
458:
459: // delete file if it already exists
460: if (file != null) {
461: file.delete();
462: }
463:
464: // write lock to file
465: FileOutputStream fos = null;
466: ObjectOutputStream oos = null;
467: try {
468: try {
469: if (file == null) {
470: file = File.createTempFile(FILE_PREFIX,
471: FILE_SUFFIX, workingDir);
472: }
473: String msg = Messages.getMessage(
474: "DATASTORE_LOCK_STORE", lock.getId(), file
475: .getAbsolutePath());
476: LOG.logDebug(msg);
477: fos = new FileOutputStream(file);
478: oos = new ObjectOutputStream(fos);
479: oos.writeObject(lock);
480: } finally {
481: if (oos != null) {
482: oos.flush();
483: oos.close();
484: } else if (fos != null) {
485: fos.close();
486: }
487: }
488: } catch (Exception e) {
489: String msg = Messages.getMessage(
490: "DATASTORE_LOCK_STORING_FAILED", lock.getId(),
491: file.getAbsolutePath(), e.getMessage());
492: LOG.logError(msg, e);
493: throw new DatastoreException(msg);
494: }
495: } else if (file != null) {
496: // else (and file exists) delete file
497: LOG.logDebug("Deleting lock '" + lock.getId()
498: + "' in file: " + file.getAbsolutePath());
499: file.delete();
500: }
501: return file;
502: }
503:
504: @Override
505: public String toString() {
506: StringBuffer sb = new StringBuffer("LockManager status:\n");
507: sb.append("- number of locked features: "
508: + this .fidToLock.size() + "\n");
509: sb.append("- active locks: " + this .lockIdToLock.size() + "\n");
510: for (Lock lock : this .lockIdToLock.values()) {
511: sb.append("- ");
512: sb.append(lock);
513: sb.append("\n");
514: }
515: return sb.toString();
516: }
517: }
|