/*
* 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);
}
}