/* * Copyright 2005-2006 webdav-servlet group. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.modeshape.webdav.locking; import java.util.Enumeration; import java.util.Hashtable; import org.modeshape.common.logging.Logger; import org.modeshape.webdav.ITransaction; import org.modeshape.webdav.exceptions.LockFailedException; /** * simple locking management for concurrent data access, NOT the webdav locking. ( could that be used instead? ) IT IS ACTUALLY * USED FOR DOLOCK * * @author re */ public class ResourceLocks implements IResourceLocks { private static Logger LOG = Logger.getLogger(ResourceLocks.class); /** * after creating this much LockedObjects, a cleanup deletes unused LockedObjects */ private final int cleanupLimit = 100000; protected int cleanupCounter = 0; /** * keys: path value: LockedObject from that path */ protected Hashtable<String, LockedObject> locks = new Hashtable<String, LockedObject>(); /** * keys: id value: LockedObject from that id */ protected Hashtable<String, LockedObject> locksByID = new Hashtable<String, LockedObject>(); /** * keys: path value: Temporary LockedObject from that path */ protected Hashtable<String, LockedObject> tempLocks = new Hashtable<String, LockedObject>(); /** * keys: id value: Temporary LockedObject from that id */ protected Hashtable<String, LockedObject> tempLocksByID = new Hashtable<String, LockedObject>(); // REMEMBER TO REMOVE UNUSED LOCKS FROM THE HASHTABLE AS WELL protected LockedObject root = null; protected LockedObject tempRoot = null; private boolean temporary = true; public ResourceLocks() { root = new LockedObject(this, "/", true); tempRoot = new LockedObject(this, "/", false); } @Override public synchronized boolean lock( ITransaction transaction, String path, String owner, boolean exclusive, int depth, int timeout, boolean temporary ) throws LockFailedException { LockedObject lo = null; if (temporary) { lo = generateTempLockedObjects(path); lo.type = "read"; } else { lo = generateLockedObjects(path); lo.type = "write"; } if (lo.checkLocks(exclusive, depth)) { lo.exclusive = exclusive; lo.lockDepth = depth; lo.expiresAt = System.currentTimeMillis() + (timeout * 1000); if (lo.parent != null) { lo.parent.expiresAt = lo.expiresAt; if (lo.parent.equals(root)) { LockedObject rootLo = getLockedObjectByPath(transaction, root.getPath()); rootLo.expiresAt = lo.expiresAt; } else if (lo.parent.equals(tempRoot)) { LockedObject tempRootLo = getTempLockedObjectByPath(transaction, tempRoot.getPath()); tempRootLo.expiresAt = lo.expiresAt; } } if (lo.addLockedObjectOwner(owner)) { return true; } LOG.trace("Couldn't set owner \"" + owner + "\" to resource at '" + path + "'"); return false; } // can not lock LOG.trace("Lock resource at " + path + " failed because" + "\na parent or child resource is currently locked"); return false; } @Override public synchronized boolean unlock( ITransaction transaction, String id, String owner ) { if (locksByID.containsKey(id)) { String path = locksByID.get(id).getPath(); if (locks.containsKey(path)) { LockedObject lo = locks.get(path); lo.removeLockedObjectOwner(owner); if (lo.children == null && lo.owner == null) { lo.removeLockedObject(); } } else { // there is no lock at that path. someone tried to unlock it // anyway. could point to a problem LOG.trace("org.modeshape.web.webdav.locking.ResourceLocks.unlock(): no lock for path " + path); return false; } if (cleanupCounter > cleanupLimit) { cleanupCounter = 0; cleanLockedObjects(root, !temporary); } } checkTimeouts(transaction, !temporary); return true; } @Override public synchronized void unlockTemporaryLockedObjects( ITransaction transaction, String path, String owner ) { if (tempLocks.containsKey(path)) { LockedObject lo = tempLocks.get(path); lo.removeLockedObjectOwner(owner); } else { // there is no lock at that path. someone tried to unlock it // anyway. could point to a problem LOG.trace("org.modeshape.web.webdav.locking.ResourceLocks.unlock(): no lock for path " + path); } if (cleanupCounter > cleanupLimit) { cleanupCounter = 0; cleanLockedObjects(tempRoot, temporary); } checkTimeouts(transaction, temporary); } @Override public void checkTimeouts( ITransaction transaction, boolean temporary ) { if (!temporary) { Enumeration<LockedObject> lockedObjects = locks.elements(); while (lockedObjects.hasMoreElements()) { LockedObject currentLockedObject = lockedObjects.nextElement(); if (currentLockedObject.expiresAt < System.currentTimeMillis()) { currentLockedObject.removeLockedObject(); } } } else { Enumeration<LockedObject> lockedObjects = tempLocks.elements(); while (lockedObjects.hasMoreElements()) { LockedObject currentLockedObject = lockedObjects.nextElement(); if (currentLockedObject.expiresAt < System.currentTimeMillis()) { currentLockedObject.removeTempLockedObject(); } } } } @Override public boolean exclusiveLock( ITransaction transaction, String path, String owner, int depth, int timeout ) throws LockFailedException { return lock(transaction, path, owner, true, depth, timeout, false); } @Override public boolean sharedLock( ITransaction transaction, String path, String owner, int depth, int timeout ) throws LockFailedException { return lock(transaction, path, owner, false, depth, timeout, false); } @Override public LockedObject getLockedObjectByID( ITransaction transaction, String id ) { if (locksByID.containsKey(id)) { return locksByID.get(id); } return null; } @Override public LockedObject getLockedObjectByPath( ITransaction transaction, String path ) { if (locks.containsKey(path)) { return this.locks.get(path); } return null; } @Override public LockedObject getTempLockedObjectByID( ITransaction transaction, String id ) { if (tempLocksByID.containsKey(id)) { return tempLocksByID.get(id); } return null; } @Override public LockedObject getTempLockedObjectByPath( ITransaction transaction, String path ) { if (tempLocks.containsKey(path)) { return this.tempLocks.get(path); } return null; } /** * generates real LockedObjects for the resource at path and its parent folders. does not create new LockedObjects if they * already exist * * @param path path to the (new) LockedObject * @return the LockedObject for path. */ private LockedObject generateLockedObjects( String path ) { if (!locks.containsKey(path)) { LockedObject returnObject = new LockedObject(this, path, !temporary); String parentPath = getParentPath(path); if (parentPath != null) { LockedObject parentLockedObject = generateLockedObjects(parentPath); parentLockedObject.addChild(returnObject); returnObject.parent = parentLockedObject; } return returnObject; } // there is already a LockedObject on the specified path return this.locks.get(path); } /** * generates temporary LockedObjects for the resource at path and its parent folders. does not create new LockedObjects if * they already exist * * @param path path to the (new) LockedObject * @return the LockedObject for path. */ private LockedObject generateTempLockedObjects( String path ) { if (!tempLocks.containsKey(path)) { LockedObject returnObject = new LockedObject(this, path, temporary); String parentPath = getParentPath(path); if (parentPath != null) { LockedObject parentLockedObject = generateTempLockedObjects(parentPath); parentLockedObject.addChild(returnObject); returnObject.parent = parentLockedObject; } return returnObject; } // there is already a LockedObject on the specified path return this.tempLocks.get(path); } /** * deletes unused LockedObjects and resets the counter. works recursively starting at the given LockedObject * * @param lo LockedObject * @param temporary Clean temporary or real locks * @return if cleaned */ private boolean cleanLockedObjects( LockedObject lo, boolean temporary ) { if (lo.children == null) { if (lo.owner == null) { if (temporary) { lo.removeTempLockedObject(); } else { lo.removeLockedObject(); } return true; } return false; } boolean canDelete = true; int limit = lo.children.length; for (int i = 0; i < limit; i++) { if (!cleanLockedObjects(lo.children[i], temporary)) { canDelete = false; } else { // because the deleting shifts the array i--; limit--; } } if (canDelete) { if (lo.owner == null) { if (temporary) { lo.removeTempLockedObject(); } else { lo.removeLockedObject(); } return true; } return false; } return false; } /** * creates the parent path from the given path by removing the last '/' and everything after that * * @param path the path * @return parent path */ private String getParentPath( String path ) { int slash = path.lastIndexOf('/'); if (slash == -1) { return null; } if (slash == 0) { // return "root" if parent path is empty string return "/"; } return path.substring(0, slash); } }