/*
* Adito
*
* Copyright (C) 2003-2006 3SP LTD. All Rights Reserved
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
package com.adito.vfs;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.TreeSet;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.vfs.FileName;
import org.apache.commons.vfs.FileObject;
import com.adito.security.LogonController;
import com.adito.security.LogonControllerFactory;
import com.adito.security.SessionInfo;
import com.adito.vfs.webdav.DAVUtilities;
import com.adito.vfs.webdav.LockedException;
/**
*
*/
public class VFSLockManager {
/**
* A simple class to maintain global locks on files. Locks should only be
* requested when a resource is being edited.
*/
private final static Log log = LogFactory.getLog(VFSLockManager.class);
private static long HANDLE = 0;
private static final Object LOCK = new Object();
private static VFSLockManager instance;
private final Map<String, NonExclusiveLock> nonExclusiveLocks_ = new HashMap<String, NonExclusiveLock>();
private final Map<String, ExclusiveLock> exclusiveLocks_ = new HashMap<String, ExclusiveLock>();
private final Map<String, Collection<Lock>> locksByHandle_ = new HashMap<String, Collection<Lock>>();
private final boolean debug_ = false;
VFSLockManager() {
addLogging();
}
private void addLogging() {
if (!log.isDebugEnabled() && !debug_)
return;
Thread thread = new Thread() {
public void run() {
while (true) {
sleepPlease();
if (exclusiveLocks_.size() > 0) {
log.debug("***Exclusive Locks***");
for (ExclusiveLock lock : exclusiveLocks_.values())
log.debug(" " + lock.getSession() + " - " + lock.getResource().getFullPath());
} else {
log.debug("***No outstanding exclusive locks***");
}
if (nonExclusiveLocks_.size() > 0) {
log.debug("***Non-exclusive Locks***");
for (NonExclusiveLock lock : nonExclusiveLocks_.values()) {
log.debug(" " + lock.getResource().getFullPath() + " open by ");
for (SessionInfo sessionInfo : lock.getSessions())
log.debug(" - " + sessionInfo.toString());
}
} else {
log.debug("***No outstanding non-exclusive locks***");
}
}
}
};
thread.start();
}
private static void sleepPlease() {
try {
Thread.sleep(30000);
} catch (InterruptedException e) {
}
}
/**
* Generates a unique handle for a file.
*
* @return the handle
*/
public static String getNewHandle() {
synchronized (LOCK) {
return System.currentTimeMillis() + "" + (HANDLE++);
}
}
/**
* Does what it says on the tin.
*
* @return the instance
*/
public static VFSLockManager getInstance() {
return instance == null ? instance = new VFSLockManager() : instance;
}
/**
*
* @param resource
* @param session
* @param exclusive
* @param lockParent
* @param handle
* @throws LockedException
* @throws IOException
*/
public synchronized void lock(VFSResource resource, SessionInfo session, boolean exclusive, boolean lockParent, String handle)
throws LockedException, IOException {
String key = resource.getWebFolderPath();
if (log.isDebugEnabled())
log.debug("Attempting to " + (exclusive ? "exclusively" : "non-exclusively") + " lock resource " + key);
// First of all try to soft lock the parent
if (lockParent && resource.getParent() != null) {
lock(resource.getParent(), session, false, false, handle);
}
if (!locksByHandle_.containsKey(handle))
locksByHandle_.put(handle, new ArrayList<Lock>());
Collection<Lock> locks = locksByHandle_.get(handle);
if (exclusive && exclusiveLocks_.containsKey(key)) {
ExclusiveLock lock = exclusiveLocks_.get(key);
if (!lock.isLockOwner(session))
throw new LockedException("File is currently locked exclusively by session " + lock.getSession());
} else if (nonExclusiveLocks_.containsKey(key)) {
NonExclusiveLock lock = nonExclusiveLocks_.get(key);
if (exclusive && !lock.isLockOwner(session))
throw new LockedException("Cannot lock file exclusivley; there are currently " + lock.getSessions().size()
+ " sessions using it non-exclusivley");
lock.incrementLock(session);
} else if (exclusive) {
ExclusiveLock lock = new ExclusiveLock(resource, session, handle);
exclusiveLocks_.put(key, lock);
locks.add(lock);
} else {
NonExclusiveLock lock = new NonExclusiveLock(resource, session, handle);
nonExclusiveLocks_.put(key, lock);
locks.add(lock);
}
if (log.isDebugEnabled())
log.debug((exclusive ? "Exclusively" : "Non-exclusively") + " locked " + key);
}
/**
* @param handle
*/
public synchronized void unlock(String handle) {
Collection<Lock> locks = locksByHandle_.get(handle);
try {
for (Iterator itr = locks.iterator(); itr.hasNext();) {
Lock lock = (Lock) itr.next();
unlock(lock);
itr.remove();
}
} finally {
if(locks.isEmpty())
locksByHandle_.remove(handle);
}
}
/**
* @param session
* @param handle
*/
public synchronized void unlock(SessionInfo session, String handle) {
Collection<Lock> locks = locksByHandle_.get(handle);
try {
for (Iterator itr = locks.iterator(); itr.hasNext();) {
Lock lock = (Lock) itr.next();
if (lock.removeLock(handle, session)) {
unlock(lock);
itr.remove();
}
}
} finally {
if(locks.isEmpty())
locksByHandle_.remove(handle);
}
}
private void unlock(Lock lock) {
String webFolderPath = lock.getResource().getWebFolderPath();
Map lockMap = lock instanceof ExclusiveLock ? exclusiveLocks_ : nonExclusiveLocks_;
lockMap.remove(webFolderPath);
}
/**
* @return <code>java.util.Collection<VFSFileLock></code> containing all the currently held locks
*/
public synchronized Collection<VFSFileLock> getCurrentLocks () {
Collection<VFSFileLock> lockedFiles = new TreeSet<VFSFileLock> ();
for (Map.Entry<String, Collection<Lock>> entry : locksByHandle_.entrySet()) {
String handle = entry.getKey();
for (Lock lock : entry.getValue()) {
try {
FileObject file = lock.getResource().getFile();
if(file!=null)
{
FileName name = file.getName();
String baseName = name.getBaseName();
String friendlyURI = DAVUtilities.stripUserInfo(name.getURI().toString());
boolean sessionsActive = areSessionsActive(lock);
lockedFiles.add(new VFSFileLock(baseName, friendlyURI, sessionsActive, handle));
}
} catch (IOException e) {
// ignore
}
}
}
return lockedFiles;
}
@SuppressWarnings("unchecked")
private static boolean areSessionsActive (Lock lock)
{
Collection<SessionInfo> lockOwners = lock.getLockOwners();
if (lockOwners.isEmpty())
return false;
LogonController logonController = LogonControllerFactory.getInstance();
Collection<SessionInfo> sessions = logonController.getActiveSessions().values();
int lockOwnerCount = lockOwners.size();
lockOwners.removeAll(sessions);
return lockOwners.isEmpty() || (lockOwners.size() != lockOwnerCount);
}
private static final class ExclusiveLock implements Lock {
private final VFSResource resource_;
private final SessionInfo session_;
private final String handle_;
private ExclusiveLock(VFSResource resource, SessionInfo session, String handle) {
resource_ = resource;
session_ = session;
handle_ = handle;
}
public VFSResource getResource() {
return resource_;
}
private SessionInfo getSession() {
return session_;
}
private String getHandle() {
return handle_;
}
public boolean removeLock(String handle, SessionInfo sessionInfo) {
if (!getHandle().equals(handle)) {
log.error("User attempting to unlock resource is not the lock owner");
return false;
}
if (log.isDebugEnabled())
log.debug("Exclusive lock for " + getResource().getWebFolderPath() + " has been removed");
return true;
}
public boolean isLockOwner(SessionInfo sessionInfo) {
return session_.getUser().equals(sessionInfo.getUser());
}
public Collection<SessionInfo> getLockOwners() {
Collection<SessionInfo> lockOwners = new HashSet<SessionInfo>(1);
lockOwners.add(session_);
return lockOwners;
}
@Override
public String toString() {
StringBuffer buffer = new StringBuffer();
buffer.append("ExclusiveLock@");
buffer.append("[").append("Resource='").append(resource_.toString()).append("', ");
buffer.append("Session='").append(getSession().toString()).append("', ");
buffer.append("Handle='").append(getHandle()).append("']");
return buffer.toString();
}
}
private static final class NonExclusiveLock implements Lock {
private final VFSResource resource_;
private final HashSet<SessionInfo> sessions_;
private final String handle_;
NonExclusiveLock(VFSResource resource, SessionInfo session, String handle) {
resource_ = resource;
sessions_ = new HashSet<SessionInfo>();
incrementLock(session);
handle_ = handle;
}
@SuppressWarnings("unchecked")
private Collection<SessionInfo> getSessions() {
return (Collection<SessionInfo>) sessions_.clone();
}
public VFSResource getResource() {
return resource_;
}
private String getHandle() {
return handle_;
}
private int getCount() {
return sessions_.size();
}
private void incrementLock(SessionInfo session) {
sessions_.add(session);
}
private boolean decrementLock(SessionInfo session) {
if (sessions_.isEmpty())
return true;
sessions_.remove(session);
return sessions_.isEmpty();
}
public boolean removeLock(String handle, SessionInfo sessionInfo) {
if (!getHandle().equals(handle)) {
log.error("User attempting to unlock resource is not the lock owner");
return false;
}
if (log.isDebugEnabled())
log.debug("There are " + getCount() + " non-exclusive locks remaining.. decrementing by 1");
boolean decrementLock = decrementLock(sessionInfo);
if (decrementLock && log.isDebugEnabled())
log.debug("All non-exclusive locks for " + getResource().getWebFolderPath() + " have been removed");
return decrementLock;
}
public boolean isLockOwner(SessionInfo sessionInfo) {
for (SessionInfo session : sessions_) {
if (!session.getUser().equals(sessionInfo.getUser()))
return false;
}
return true;
}
public Collection<SessionInfo> getLockOwners() {
return getSessions();
}
@Override
public String toString() {
StringBuffer buffer = new StringBuffer();
buffer.append("NonExclusiveLock@");
buffer.append("[").append("Resource='").append(resource_.toString()).append("', ");
buffer.append("Sessions='").append(getCount()).append("', ");
buffer.append("Handle='").append(getHandle()).append("']");
return buffer.toString();
}
}
private interface Lock {
/**
* The resource associated with the lock.
* @return the resource
*/
VFSResource getResource();
/**
* Removes the lock from the file.
* @param handle of the file
* @param sessionInfo associated with the lock
* @return true if the lock can be removed
*/
boolean removeLock(String handle, SessionInfo sessionInfo);
/**
* Verifies if this lock is owned by the supplied Session.
* @param sessionInfo
* @return true if this session owns the lock.
*/
boolean isLockOwner(SessionInfo sessionInfo);
/**
* @return the owners of the lock
*/
Collection<SessionInfo> getLockOwners ();
}
}