/*
* 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 com.ejie.x38.webdav.locking;
import java.util.Enumeration;
import java.util.Hashtable;
import com.ejie.x38.webdav.ITransaction;
import com.ejie.x38.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 MemoryResourceLocks implements IResourceLocks {
private static org.slf4j.Logger LOG = org.slf4j.LoggerFactory
.getLogger(MemoryResourceLocks.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
* Concurrent access can occur
*/
protected Hashtable<String, LockedObject> _locks = new Hashtable<String, LockedObject>();
/**
* keys: id value: LockedObject from that id
* Concurrent access can occur
*/
protected Hashtable<String, LockedObject> _locksByID = new Hashtable<String, LockedObject>();
/**
* keys: path value: Temporary LockedObject from that path
* Concurrent access can occur
*/
protected Hashtable<String, LockedObject> _tempLocks = new Hashtable<String, LockedObject>();
/**
* keys: id value: Temporary LockedObject from that id
* Concurrent access can occur
*/
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 MemoryResourceLocks() {
_root = new MemoryLockedObject(this, "/", true);
_tempRoot = new MemoryLockedObject(this, "/", false);
}
public synchronized boolean lock(ITransaction transaction, String path,
String owner, boolean exclusive, int depth, int timeout,
boolean temporary) throws LockFailedException {
MemoryLockedObject 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 MemoryLockedObject generateLockedObjects(ITransaction transaction,
String path) {
if (!_locks.containsKey(path)) {
MemoryLockedObject returnObject = new MemoryLockedObject(this, path,
!_temporary);
String parentPath = getParentPath(path);
if (parentPath != null) {
MemoryLockedObject parentLockedObject = generateLockedObjects(
transaction, parentPath);
parentLockedObject.addChild(returnObject);
returnObject._parent = parentLockedObject;
}
return returnObject;
} else {
// there is already a LockedObject on the specified path
return (MemoryLockedObject) 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 MemoryLockedObject generateTempLockedObjects(ITransaction transaction,
String path) {
if (!_tempLocks.containsKey(path)) {
MemoryLockedObject returnObject = new MemoryLockedObject(this, path, _temporary);
String parentPath = getParentPath(path);
if (parentPath != null) {
MemoryLockedObject parentLockedObject = generateTempLockedObjects(
transaction, parentPath);
parentLockedObject.addChild(returnObject);
returnObject._parent = parentLockedObject;
}
return returnObject;
} else {
// there is already a LockedObject on the specified path
return (MemoryLockedObject) 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);
}
}
}
public Hashtable<String, LockedObject> get_locks() {
return _locks;
}
public void set_locks(Hashtable<String, LockedObject> _locks) {
this._locks = _locks;
}
public Hashtable<String, LockedObject> get_locksByID() {
return _locksByID;
}
public void set_locksByID(Hashtable<String, LockedObject> _locksByID) {
this._locksByID = _locksByID;
}
public Hashtable<String, LockedObject> get_tempLocks() {
return _tempLocks;
}
public void set_tempLocks(Hashtable<String, LockedObject> _tempLocks) {
this._tempLocks = _tempLocks;
}
public Hashtable<String, LockedObject> get_tempLocksByID() {
return _tempLocksByID;
}
public void set_tempLocksByID(Hashtable<String, LockedObject> _tempLocksByID) {
this._tempLocksByID = _tempLocksByID;
}
public LockedObject get_root() {
return _root;
}
public void set_root(LockedObject _root) {
this._root = _root;
}
public void addCleanupCounter(){
this._cleanupCounter++;
}
}