com.adito.vfs.VFSLockManager.java Source code

Java tutorial

Introduction

Here is the source code for com.adito.vfs.VFSLockManager.java

Source

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