You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
384 lines
12 KiB
384 lines
12 KiB
/* |
|
* 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 net.sf.webdav.locking; |
|
|
|
import java.util.Enumeration; |
|
import java.util.Hashtable; |
|
|
|
import net.sf.webdav.ITransaction; |
|
import net.sf.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 org.slf4j.Logger LOG = org.slf4j.LoggerFactory |
|
.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); |
|
} |
|
|
|
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(transaction, path); |
|
lo._type = "read"; |
|
} else { |
|
lo = generateLockedObjects(transaction, 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; |
|
} else { |
|
LOG.trace("Couldn't set owner \"" + owner |
|
+ "\" to resource at '" + path + "'"); |
|
return false; |
|
} |
|
} else { |
|
// can not lock |
|
LOG.trace("Lock resource at " + path + " failed because" |
|
+ "\na parent or child resource is currently locked"); |
|
return false; |
|
} |
|
} |
|
|
|
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("net.sf.webdav.locking.ResourceLocks.unlock(): no lock for path " |
|
+ path); |
|
return false; |
|
} |
|
|
|
if (_cleanupCounter > _cleanupLimit) { |
|
_cleanupCounter = 0; |
|
cleanLockedObjects(transaction, _root, !_temporary); |
|
} |
|
} |
|
checkTimeouts(transaction, !_temporary); |
|
|
|
return true; |
|
|
|
} |
|
|
|
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("net.sf.webdav.locking.ResourceLocks.unlock(): no lock for path " |
|
+ path); |
|
} |
|
|
|
if (_cleanupCounter > _cleanupLimit) { |
|
_cleanupCounter = 0; |
|
cleanLockedObjects(transaction, _tempRoot, _temporary); |
|
} |
|
|
|
checkTimeouts(transaction, _temporary); |
|
|
|
} |
|
|
|
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(); |
|
} |
|
} |
|
} |
|
|
|
} |
|
|
|
public boolean exclusiveLock(ITransaction transaction, String path, |
|
String owner, int depth, int timeout) throws LockFailedException { |
|
return lock(transaction, path, owner, true, depth, timeout, false); |
|
} |
|
|
|
public boolean sharedLock(ITransaction transaction, String path, |
|
String owner, int depth, int timeout) throws LockFailedException { |
|
return lock(transaction, path, owner, false, depth, timeout, false); |
|
} |
|
|
|
public LockedObject getLockedObjectByID(ITransaction transaction, String id) { |
|
if (_locksByID.containsKey(id)) { |
|
return _locksByID.get(id); |
|
} else { |
|
return null; |
|
} |
|
} |
|
|
|
public LockedObject getLockedObjectByPath(ITransaction transaction, |
|
String path) { |
|
if (_locks.containsKey(path)) { |
|
return (LockedObject) this._locks.get(path); |
|
} else { |
|
return null; |
|
} |
|
} |
|
|
|
public LockedObject getTempLockedObjectByID(ITransaction transaction, |
|
String id) { |
|
if (_tempLocksByID.containsKey(id)) { |
|
return _tempLocksByID.get(id); |
|
} else { |
|
return null; |
|
} |
|
} |
|
|
|
public LockedObject getTempLockedObjectByPath(ITransaction transaction, |
|
String path) { |
|
if (_tempLocks.containsKey(path)) { |
|
return (LockedObject) this._tempLocks.get(path); |
|
} else { |
|
return null; |
|
} |
|
} |
|
|
|
/** |
|
* generates real LockedObjects for the resource at path and its parent |
|
* folders. does not create new LockedObjects if they already exist |
|
* |
|
* @param transaction |
|
* @param path |
|
* path to the (new) LockedObject |
|
* @return the LockedObject for path. |
|
*/ |
|
private LockedObject generateLockedObjects(ITransaction transaction, |
|
String path) { |
|
if (!_locks.containsKey(path)) { |
|
LockedObject returnObject = new LockedObject(this, path, |
|
!_temporary); |
|
String parentPath = getParentPath(path); |
|
if (parentPath != null) { |
|
LockedObject parentLockedObject = generateLockedObjects( |
|
transaction, parentPath); |
|
parentLockedObject.addChild(returnObject); |
|
returnObject._parent = parentLockedObject; |
|
} |
|
return returnObject; |
|
} else { |
|
// there is already a LockedObject on the specified path |
|
return (LockedObject) 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 transaction |
|
* @param path |
|
* path to the (new) LockedObject |
|
* @return the LockedObject for path. |
|
*/ |
|
private LockedObject generateTempLockedObjects(ITransaction transaction, |
|
String path) { |
|
if (!_tempLocks.containsKey(path)) { |
|
LockedObject returnObject = new LockedObject(this, path, _temporary); |
|
String parentPath = getParentPath(path); |
|
if (parentPath != null) { |
|
LockedObject parentLockedObject = generateTempLockedObjects( |
|
transaction, parentPath); |
|
parentLockedObject.addChild(returnObject); |
|
returnObject._parent = parentLockedObject; |
|
} |
|
return returnObject; |
|
} else { |
|
// there is already a LockedObject on the specified path |
|
return (LockedObject) this._tempLocks.get(path); |
|
} |
|
|
|
} |
|
|
|
/** |
|
* deletes unused LockedObjects and resets the counter. works recursively |
|
* starting at the given LockedObject |
|
* |
|
* @param transaction |
|
* @param lo |
|
* LockedObject |
|
* @param temporary |
|
* Clean temporary or real locks |
|
* |
|
* @return if cleaned |
|
*/ |
|
private boolean cleanLockedObjects(ITransaction transaction, |
|
LockedObject lo, boolean temporary) { |
|
|
|
if (lo._children == null) { |
|
if (lo._owner == null) { |
|
if (temporary) { |
|
lo.removeTempLockedObject(); |
|
} else { |
|
lo.removeLockedObject(); |
|
} |
|
|
|
return true; |
|
} else { |
|
return false; |
|
} |
|
} else { |
|
boolean canDelete = true; |
|
int limit = lo._children.length; |
|
for (int i = 0; i < limit; i++) { |
|
if (!cleanLockedObjects(transaction, 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; |
|
} else { |
|
return false; |
|
} |
|
} else { |
|
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; |
|
} else { |
|
if (slash == 0) { |
|
// return "root" if parent path is empty string |
|
return "/"; |
|
} else { |
|
return path.substring(0, slash); |
|
} |
|
} |
|
} |
|
|
|
}
|
|
|