/*
 * File   : $Source: /usr/local/cvs/opencms/src/org/opencms/lock/CmsLockManager.java,v $
 * Date   : $Date: 2008-02-27 12:05:46 $
 * Version: $Revision: 1.48 $
 *
 * This library is part of OpenCms -
 * the Open Source Content Management System
 *
 * Copyright (c) 2002 - 2008 Alkacon Software GmbH (http://www.alkacon.com)
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * For further information about Alkacon Software GmbH, please see the
 * company website: http://www.alkacon.com
 *
 * For further information about OpenCms, please see the
 * project website: http://www.opencms.org
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package org.opencms.lock;

import org.opencms.db.CmsDbContext;
import org.opencms.db.CmsDriverManager;
import org.opencms.file.CmsProject;
import org.opencms.file.CmsResource;
import org.opencms.file.CmsResourceFilter;
import org.opencms.file.CmsUser;
import org.opencms.file.CmsVfsResourceNotFoundException;
import org.opencms.i18n.CmsMessageContainer;
import org.opencms.main.CmsException;
import org.opencms.main.OpenCms;
import org.opencms.util.CmsUUID;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

/**
 * The CmsLockManager is used by the Cms application to detect 
 * the lock state of a resource.<p>
 * 
 * The lock state depends on the path of the resource, and probably 
 * locked parent folders. The result of a query to the lock manager
 * are instances of CmsLock objects.<p>
 * 
 * @author Michael Emmerich 
 * @author Thomas Weckert  
 * @author Andreas Zahner  
 * @author Michael Moossen  
 * 
 * @version $Revision: 1.48 $ 
 * 
 * @since 6.0.0 
 * 
 * @see org.opencms.file.CmsObject#getLock(CmsResource)
 * @see org.opencms.lock.CmsLock
 */
public final class CmsLockManager {

    /** The driver manager instance. */
    private CmsDriverManager m_driverManager;

    /** The flag to indicate if the locks should be written to the db. */
    private boolean m_isDirty = false;

    /** The flag to indicate if the lock manager has been started in run level 4. */
    private boolean m_runningInServlet = false;

    /**
     * Default constructor, creates a new lock manager.<p>
     * 
     * @param driverManager the driver manager instance 
     */
    public CmsLockManager(CmsDriverManager driverManager) {

        m_driverManager = driverManager;
    }

    /**
     * Adds a resource to the lock manager.<p>
     * 
     * @param dbc the current database context
     * @param resource the resource
     * @param user the user who locked the resource
     * @param project the project where the resource is locked
     * @param type the lock type
     * 
     * @throws CmsLockException if the resource is locked
     * @throws CmsException if something goes wrong
     */
    public void addResource(CmsDbContext dbc, CmsResource resource, CmsUser user, CmsProject project, CmsLockType type)
    throws CmsLockException, CmsException {

        // check the type
        if (!type.isSystem() && !type.isExclusive()) {
            // invalid type
            throw new CmsLockException(Messages.get().container(Messages.ERR_INVALID_LOCK_TYPE_1, type.toString()));
        }

        // get the current lock
        CmsLock currentLock = getLock(dbc, resource);

        // check lockability
        checkLockable(dbc, resource, user, project, type, currentLock);

        boolean needNewLock = true;
        // prevent shared locks get compromised
        if ((type.isExclusive()) && !(type.isTemporary() && currentLock.isInherited())) {
            if (!currentLock.getEditionLock().isUnlocked()) {
                needNewLock = false;
            }
        }

        CmsLock newLock = CmsLock.getNullLock();
        if (needNewLock) {
            // lock the resource
            newLock = new CmsLock(resource.getRootPath(), user.getId(), project, type);
            lockResource(newLock);
        }

        // handle collisions with exclusive locked sub-resources in case of a folder
        if (resource.isFolder() && newLock.getSystemLock().isUnlocked()) {
            String resourceName = resource.getRootPath();
            Iterator itLocks = OpenCms.getMemoryMonitor().getAllCachedLocks().iterator();
            while (itLocks.hasNext()) {
                CmsLock lock = (CmsLock)itLocks.next();
                String lockedPath = lock.getResourceName();
                if (lockedPath.startsWith(resourceName) && !lockedPath.equals(resourceName)) {
                    unlockResource(lockedPath, false);
                }
            }
        }
    }

    /**
     * Counts the exclusive locked resources in a project.<p>
     * 
     * @param project the project
     * 
     * @return the number of exclusive locked resources in the specified project
     */
    public int countExclusiveLocksInProject(CmsProject project) {

        int count = 0;
        Iterator itLocks = OpenCms.getMemoryMonitor().getAllCachedLocks().iterator();
        while (itLocks.hasNext()) {
            CmsLock lock = (CmsLock)itLocks.next();
            if (lock.getEditionLock().isInProject(project)) {
                count++;
            }
        }
        return count;
    }

    /**
     * Returns the lock state of the given resource.<p>
     * 
     * In case no lock is set, the <code>null lock</code> which can be obtained 
     * by {@link CmsLock#getNullLock()} is returned.<p>
     * 
     * @param dbc the current database context
     * @param resource the resource
     * 
     * @return the lock state of the given resource 

     * @throws CmsException if something goes wrong
     */
    public CmsLock getLock(CmsDbContext dbc, CmsResource resource) throws CmsException {

        return getLock(dbc, resource, true);
    }

    /**
     * Returns the lock state of the given resource.<p>
     * 
     * In case no lock is set, the <code>null lock</code> which can be obtained 
     * by {@link CmsLock#getNullLock()} is returned.<p>
     * 
     * @param dbc the current database context
     * @param resource the resource
     * @param includeSiblings if siblings (shared locks) should be included in the search
     * 
     * @return the lock state of the given resource 

     * @throws CmsException if something goes wrong
     */
    public CmsLock getLock(CmsDbContext dbc, CmsResource resource, boolean includeSiblings) throws CmsException {

        // resources are never locked in the online project
        // and non-existent resources are never locked
        if ((resource == null) || (dbc.currentProject().isOnlineProject())) {
            return CmsLock.getNullLock();
        }

        // check exclusive direct locks first
        CmsLock lock = getDirectLock(resource.getRootPath());
        if ((lock == null) && includeSiblings) {
            // check if siblings are exclusively locked
            List siblings = internalReadSiblings(dbc, resource);
            lock = getSiblingsLock(siblings, resource.getRootPath());
        }
        if (lock == null) {
            // if there is no parent lock, this will be the null lock as well
            lock = getParentLock(resource.getRootPath());
        }
        if (!lock.getSystemLock().isUnlocked()) {
            lock = lock.getSystemLock();
        } else {
            lock = lock.getEditionLock();
        }
        return lock;
    }

    /**
     * Returns all exclusive locked resources matching the given resource name and filter.<p>
     * 
     * @param dbc the database context
     * @param resourceName the resource name
     * @param filter the lock filter
     * 
     * @return a list of root paths
     * 
     * @throws CmsException if something goes wrong 
     */
    public List getLocks(CmsDbContext dbc, String resourceName, CmsLockFilter filter) throws CmsException {

        List locks = new ArrayList();
        Iterator itLocks = OpenCms.getMemoryMonitor().getAllCachedLocks().iterator();
        while (itLocks.hasNext()) {
            CmsLock lock = (CmsLock)itLocks.next();
            if (filter.isSharedExclusive()) {
                CmsResource resource;
                try {
                    resource = m_driverManager.readResource(dbc, lock.getResourceName(), CmsResourceFilter.ALL);
                } catch (CmsVfsResourceNotFoundException e) {
                    OpenCms.getMemoryMonitor().uncacheLock(lock.getResourceName());
                    continue;
                }
                if (resource.getSiblingCount() > 1) {
                    Iterator itSiblings = internalReadSiblings(dbc, resource).iterator();
                    while (itSiblings.hasNext()) {
                        CmsResource sibling = (CmsResource)itSiblings.next();
                        CmsLock siblingLock = internalSiblingLock(lock, sibling.getRootPath());
                        if (filter.match(resourceName, siblingLock)) {
                            locks.add(siblingLock);
                        }
                    }
                }
            }
            if (filter.match(resourceName, lock)) {
                locks.add(lock);
            }
        }
        return locks;
    }

    /**
     * Returns <code>true</code> if the given resource contains a resource that has a system lock.<p>
     * 
     * This check is required for certain operations on folders.<p> 
     * 
     * @param dbc the database context
     * @param resource the resource to check the system locks for
     * 
     * @return <code>true</code> if the given resource contains a resource that has a system lock
     * 
     * @throws CmsException if something goes wrong 
     */
    public boolean hasSystemLocks(CmsDbContext dbc, CmsResource resource) throws CmsException {

        if (resource == null) {
            return false;
        }
        Iterator itLocks = OpenCms.getMemoryMonitor().getAllCachedLocks().iterator();
        while (itLocks.hasNext()) {
            CmsLock lock = (CmsLock)itLocks.next();
            if (lock.getSystemLock().isUnlocked()) {
                // only system locks matter here
                continue;
            }
            if (lock.getResourceName().startsWith(resource.getRootPath())) {
                if (lock.getResourceName().startsWith(resource.getRootPath())) {
                    return true;
                }
                try {
                    resource = m_driverManager.readResource(dbc, lock.getResourceName(), CmsResourceFilter.ALL);
                } catch (CmsVfsResourceNotFoundException e) {
                    OpenCms.getMemoryMonitor().uncacheLock(lock.getResourceName());
                    continue;
                }
                CmsResource lockedResource;
                try {
                    lockedResource = m_driverManager.readResource(dbc, lock.getResourceName(), CmsResourceFilter.ALL);
                } catch (CmsVfsResourceNotFoundException e) {
                    OpenCms.getMemoryMonitor().uncacheLock(lock.getResourceName());
                    continue;
                }
                if (lockedResource.getSiblingCount() > 1) {
                    Iterator itSiblings = internalReadSiblings(dbc, lockedResource).iterator();
                    while (itSiblings.hasNext()) {
                        CmsResource sibling = (CmsResource)itSiblings.next();
                        CmsLock siblingLock = internalSiblingLock(lock, sibling.getRootPath());
                        if (siblingLock.getResourceName().startsWith(resource.getRootPath())) {
                            return true;
                        }
                    }
                }
            }
        }
        return false;
    }

    /**
     * Moves a lock during the move resource operation.<p>
     * 
     * @param source the source root path 
     * @param destination the destination root path
     */
    public void moveResource(String source, String destination) {

        CmsLock lock = OpenCms.getMemoryMonitor().getCachedLock(source);
        if (lock != null) {
            OpenCms.getMemoryMonitor().uncacheLock(lock.getResourceName());
            CmsLock newLock = new CmsLock(destination, lock.getUserId(), lock.getProject(), lock.getType());
            lock = lock.getRelatedLock();
            if ((lock != null) && !lock.isNullLock()) {
                CmsLock relatedLock = new CmsLock(destination, lock.getUserId(), lock.getProject(), lock.getType());
                newLock.setRelatedLock(relatedLock);
            }
            OpenCms.getMemoryMonitor().cacheLock(newLock);
        }
    }

    /**
     * Reads the latest saved locks from the database and installs them to 
     * this lock manager.<p>
     * 
     *  @param dbc the current database context
     *  
     *  @throws CmsException if something goes wrong
     */
    public void readLocks(CmsDbContext dbc) throws CmsException {

        if (OpenCms.getRunLevel() > OpenCms.RUNLEVEL_3_SHELL_ACCESS) {
            // read the locks only if the wizard is not enabled
            Map lockCache = new HashMap();
            List locks = m_driverManager.getProjectDriver().readLocks(dbc);
            Iterator itLocks = locks.iterator();
            while (itLocks.hasNext()) {
                CmsLock lock = (CmsLock)itLocks.next();
                internalLockResource(lock, lockCache);
            }
            OpenCms.getMemoryMonitor().flushLocks(lockCache);
            m_runningInServlet = true;
        }
    }

    /**
     * Removes a resource after it has been deleted by the driver manager.<p>
     * 
     * @param dbc the current database context
     * @param resourceName the root path of the deleted resource
     * @throws CmsException if something goes wrong
     */
    public void removeDeletedResource(CmsDbContext dbc, String resourceName) throws CmsException {

        try {
            m_driverManager.getVfsDriver().readResource(dbc, dbc.currentProject().getUuid(), resourceName, false);
            throw new CmsLockException(Messages.get().container(
                Messages.ERR_REMOVING_UNDELETED_RESOURCE_1,
                dbc.getRequestContext().removeSiteRoot(resourceName)));
        } catch (CmsVfsResourceNotFoundException e) {
            // ok, ignore
        }
        unlockResource(resourceName, true);
        unlockResource(resourceName, false);
    }

    /**
     * Removes all locks of a user.<p>
     * 
     * Edition and system locks are removed.<p>
     * 
     * @param userId the id of the user whose locks should be removed
     */
    public void removeLocks(CmsUUID userId) {

        Iterator itLocks = OpenCms.getMemoryMonitor().getAllCachedLocks().iterator();
        while (itLocks.hasNext()) {
            CmsLock currentLock = (CmsLock)itLocks.next();
            boolean editLock = currentLock.getEditionLock().getUserId().equals(userId);
            boolean sysLock = currentLock.getSystemLock().getUserId().equals(userId);
            if (editLock) {
                unlockResource(currentLock.getResourceName(), false);
            }
            if (sysLock) {
                unlockResource(currentLock.getResourceName(), true);
            }
        }
    }

    /**
     * Removes a resource from the lock manager.<p>
     * 
     * The forceUnlock option should be used with caution.<br>
     * forceUnlock will remove the lock by ignoring any rules which may cause wrong lock states.<p>
     * 
     * @param dbc the current database context
     * @param resource the resource
     * @param forceUnlock <code>true</code>, if a resource is forced to get unlocked (only edition locks), 
     *                    no matter by which user and in which project the resource is currently locked
     * @param removeSystemLock <code>true</code>, if you also want to remove system locks
     * 
     * @return the previous {@link CmsLock} object of the resource, 
     *          or <code>{@link CmsLock#getNullLock()}</code> if the resource was unlocked
     *
     * @throws CmsException if something goes wrong
     */
    public CmsLock removeResource(CmsDbContext dbc, CmsResource resource, boolean forceUnlock, boolean removeSystemLock)
    throws CmsException {

        String resourcename = resource.getRootPath();
        CmsLock lock = getLock(dbc, resource).getEditionLock();

        // check some abort conditions first
        if (!lock.isNullLock()) {
            // the resource is locked by another user or in other project
            if (!forceUnlock && (!lock.isOwnedInProjectBy(dbc.currentUser(), dbc.currentProject()))) {
                throw new CmsLockException(Messages.get().container(
                    Messages.ERR_RESOURCE_UNLOCK_1,
                    dbc.removeSiteRoot(resourcename)));
            }

            // sub-resources of a locked folder can't be unlocked
            if (!forceUnlock && lock.isInherited()) {
                throw new CmsLockException(Messages.get().container(
                    Messages.ERR_UNLOCK_LOCK_INHERITED_1,
                    dbc.removeSiteRoot(resourcename)));
            }
        }

        // remove the lock and clean-up stuff
        if (lock.isExclusive()) {
            if (resource.isFolder()) {
                // in case of a folder, remove any exclusive locks on sub-resources that probably have
                // been upgraded from an inherited lock when the user edited a resource                
                Iterator itLocks = OpenCms.getMemoryMonitor().getAllCachedLocks().iterator();
                while (itLocks.hasNext()) {
                    String lockedPath = ((CmsLock)itLocks.next()).getResourceName();
                    if (lockedPath.startsWith(resourcename) && !lockedPath.equals(resourcename)) {
                        // remove the exclusive locked sub-resource
                        unlockResource(lockedPath, false);
                    }
                }
            }
            if (removeSystemLock) {
                unlockResource(resourcename, true);
            }
            unlockResource(resourcename, false);
            return lock;
        }

        if (lock.getType().isSharedExclusive()) {
            List locks = OpenCms.getMemoryMonitor().getAllCachedLockPaths();
            // when a resource with a shared lock gets unlocked, fetch all siblings of the resource 
            // to the same content record to identify the exclusive locked sibling
            List siblings = internalReadSiblings(dbc, resource);
            for (int i = 0; i < siblings.size(); i++) {
                CmsResource sibling = (CmsResource)siblings.get(i);
                if (locks.contains(sibling.getRootPath())) {
                    // remove the exclusive locked sibling
                    if (removeSystemLock) {
                        unlockResource(sibling.getRootPath(), true);
                    }
                    unlockResource(sibling.getRootPath(), false);
                    break; // it can only be one!
                }
            }
            return lock;
        }

        // remove system locks only if explicit required
        if (removeSystemLock && !getLock(dbc, resource).getSystemLock().isUnlocked()) {
            return unlockResource(resourcename, true);
        }
        return lock;
    }

    /**
     * Removes all resources locked in a project.<p>
     * 
     * @param projectId the ID of the project where the resources have been locked
     * @param removeSystemLocks if <code>true</code>, also system locks are removed
     */
    public void removeResourcesInProject(CmsUUID projectId, boolean removeSystemLocks) {

        Iterator itLocks = OpenCms.getMemoryMonitor().getAllCachedLocks().iterator();
        while (itLocks.hasNext()) {
            CmsLock currentLock = (CmsLock)itLocks.next();
            if (removeSystemLocks && currentLock.getSystemLock().getProjectId().equals(projectId)) {
                unlockResource(currentLock.getResourceName(), true);
            }
            if (currentLock.getEditionLock().getProjectId().equals(projectId)) {
                unlockResource(currentLock.getResourceName(), false);
            }
        }
    }

    /**
     * Removes all exclusive temporary locks of a user.<p>
     * 
     * Only edition lock can be temporary, so no system locks are removed.<p>
     * 
     * @param userId the id of the user whose locks has to be removed
     */
    public void removeTempLocks(CmsUUID userId) {

        Iterator itLocks = OpenCms.getMemoryMonitor().getAllCachedLocks().iterator();
        while (itLocks.hasNext()) {
            CmsLock currentLock = (CmsLock)itLocks.next();
            if (currentLock.isTemporary() && currentLock.getUserId().equals(userId)) {
                unlockResource(currentLock.getResourceName(), false);
            }
        }
    }

    /** 
     * @see java.lang.Object#toString()
     */
    public String toString() {

        StringBuffer buf = new StringBuffer();

        // bring the list of locked resources into a human readable order first
        List lockedResources = OpenCms.getMemoryMonitor().getAllCachedLocks();
        Collections.sort(lockedResources);

        // iterate all locks
        Iterator itLocks = lockedResources.iterator();
        while (itLocks.hasNext()) {
            CmsLock lock = (CmsLock)itLocks.next();
            buf.append(lock).append("\n");
        }
        return buf.toString();
    }

    /**
     * Writes the locks that are currently stored in-memory to the database to allow restoring them in 
     * later startups.<p> 
     * 
     * This overwrites the locks previously stored in the underlying database table.<p>
     * 
     *  @param dbc the current database context
     *  
     *  @throws CmsException if something goes wrong
     */
    public void writeLocks(CmsDbContext dbc) throws CmsException {

        if (m_isDirty // only if something changed
            && m_runningInServlet // only if started in run level 4 
            && OpenCms.getMemoryMonitor().requiresPersistency()) { // only if persistency is required

            List locks = OpenCms.getMemoryMonitor().getAllCachedLocks();
            m_driverManager.getProjectDriver().writeLocks(dbc, locks);
            m_isDirty = false;
        }
    }

    /**
     * Checks if the given resource is lockable by the given user/project/lock type.<p> 
     * 
     * @param dbc just to get the site path of the resource
     * @param resource the resource to check lockability for
     * @param user the user to check
     * @param project the project to check
     * @param type the lock type to check
     * @param currentLock the resource current lock
     * 
     * @throws CmsLockException if resource is not lockable
     */
    private void checkLockable(
        CmsDbContext dbc,
        CmsResource resource,
        CmsUser user,
        CmsProject project,
        CmsLockType type,
        CmsLock currentLock) throws CmsLockException {

        if (!currentLock.isLockableBy(user)) {
            // check type, owner and project for system locks
            // this is required if publishing several siblings
            if (currentLock.getSystemLock().isUnlocked()
                || (currentLock.getType() != type)
                || !currentLock.isOwnedInProjectBy(user, project)) {
                // display the right message
                CmsMessageContainer message = null;
                if (currentLock.getSystemLock().isPublish()) {
                    message = Messages.get().container(
                        Messages.ERR_RESOURCE_LOCKED_FORPUBLISH_1,
                        dbc.getRequestContext().getSitePath(resource));
                } else if (currentLock.getEditionLock().isInherited()) {
                    message = Messages.get().container(
                        Messages.ERR_RESOURCE_LOCKED_INHERITED_1,
                        dbc.getRequestContext().getSitePath(resource));
                } else {
                    message = Messages.get().container(
                        Messages.ERR_RESOURCE_LOCKED_BYOTHERUSER_1,
                        dbc.getRequestContext().getSitePath(resource));
                }
                throw new CmsLockException(message);
            }
        }
    }

    /**
     * Returns the direct lock of a resource.<p>
     * 
     * @param resourcename the name of the resource
     * 
     * @return the direct lock of the resource or <code>null</code> 
     */
    private CmsLock getDirectLock(String resourcename) {

        return OpenCms.getMemoryMonitor().getCachedLock(resourcename);
    }

    /**
     * Returns the lock of a possible locked parent folder of a resource, system locks are ignored.<p>
     * 
     * @param resourceName the name of the resource
     * 
     * @return the lock of a parent folder, or {@link CmsLock#getNullLock()} if no parent folders are locked by a non system lock
     */
    private CmsLock getParentFolderLock(String resourceName) {

        Iterator itLocks = OpenCms.getMemoryMonitor().getAllCachedLocks().iterator();
        while (itLocks.hasNext()) {
            CmsLock lock = (CmsLock)itLocks.next();
            if (lock.getResourceName().endsWith("/")
                && resourceName.startsWith(lock.getResourceName())
                && !resourceName.equals(lock.getResourceName())) {
                // system locks does not get inherited
                lock = lock.getEditionLock();
                // check the lock
                if (!lock.isUnlocked()) {
                    return lock;
                }
            }
        }
        return CmsLock.getNullLock();
    }

    /**
     * Returns the inherited lock of a resource.<p>
     * 
     * @param resourcename the name of the resource
     * @return the inherited lock or the null lock
     */
    private CmsLock getParentLock(String resourcename) {

        CmsLock parentFolderLock = getParentFolderLock(resourcename);
        if (!parentFolderLock.isNullLock()) {
            return new CmsLock(
                resourcename,
                parentFolderLock.getUserId(),
                parentFolderLock.getProject(),
                CmsLockType.INHERITED);
        }
        return CmsLock.getNullLock();
    }

    /**
     * Returns the indirect lock of a resource depending on siblings lock state.<p>
     * 
     * @param siblings the list of siblings
     * @param resourcename the name of the resource
     * 
     * @return the indirect lock of the resource or the null lock
     */
    private CmsLock getSiblingsLock(List siblings, String resourcename) {

        for (int i = 0; i < siblings.size(); i++) {
            CmsResource sibling = (CmsResource)siblings.get(i);
            CmsLock exclusiveLock = getDirectLock(sibling.getRootPath());
            if (exclusiveLock != null) {
                // a sibling is already locked 
                return internalSiblingLock(exclusiveLock, resourcename);
            }
        }
        // no locked siblings found
        return null;

    }

    /**
     * Finally set the given lock.<p>
     * 
     * @param lock the lock to set
     * @param locks during reading the locks from db we need to operate on an extra map
     * 
     * @throws CmsLockException if the lock is not compatible with the current lock 
     */
    private void internalLockResource(CmsLock lock, Map locks) throws CmsLockException {

        CmsLock currentLock = null;
        if (locks == null) {
            currentLock = OpenCms.getMemoryMonitor().getCachedLock(lock.getResourceName());
        } else {
            currentLock = (CmsLock)locks.get(lock.getResourceName());
        }
        if (currentLock != null) {
            if (currentLock.getSystemLock().equals(lock) || currentLock.getEditionLock().equals(lock)) {
                return;
            }
            if (!currentLock.getSystemLock().isUnlocked() && lock.getSystemLock().isUnlocked()) {
                lock.setRelatedLock(currentLock);
                if (locks == null) {
                    OpenCms.getMemoryMonitor().cacheLock(lock);
                } else {
                    locks.put(lock.getResourceName(), lock);
                }
            } else if (currentLock.getSystemLock().isUnlocked() && !lock.getSystemLock().isUnlocked()) {
                currentLock.setRelatedLock(lock);
            } else {
                throw new CmsLockException(Messages.get().container(
                    Messages.ERR_LOCK_ILLEGAL_STATE_2,
                    currentLock,
                    lock));
            }
        } else {
            if (locks == null) {
                OpenCms.getMemoryMonitor().cacheLock(lock);
            } else {
                locks.put(lock.getResourceName(), lock);
            }
        }
    }

    /**
     * Reads all siblings from a given resource.<p>
     * 
     * The result is a list of <code>{@link CmsResource}</code> objects. 
     * It does NOT contain the resource itself, only the siblings of the resource.<p>
     * 
     * @param dbc the current database context
     * @param resource the resource to find all siblings from
     * 
     * @return a list of <code>{@link CmsResource}</code> Objects that 
     *          are siblings to the specified resource, 
     *          excluding the specified resource itself
     * 
     * @throws CmsException if something goes wrong
     */
    private List internalReadSiblings(CmsDbContext dbc, CmsResource resource) throws CmsException {

        // reading siblings using the DriverManager methods while the lock state is checked would
        // result in an infinite loop, therefore we must access the VFS driver directly
        List siblings = m_driverManager.getVfsDriver().readSiblings(dbc, dbc.currentProject().getUuid(), resource, true);
        siblings.remove(resource);
        return siblings;
    }

    /**
     * Returns a shared lock for the given excclusive lock and sibling.<p>
     * 
     * @param exclusiveLock the exclusive lock to use (has to be set on a sibling of siblingName)
     * @param siblingName the siblings name
     * 
     * @return the shared lock
     */
    private CmsLock internalSiblingLock(CmsLock exclusiveLock, String siblingName) {

        CmsLock lock = null;
        if (!exclusiveLock.getSystemLock().isUnlocked()) {
            lock = new CmsLock(
                siblingName,
                exclusiveLock.getUserId(),
                exclusiveLock.getProject(),
                exclusiveLock.getSystemLock().getType());
        }
        if ((lock == null) || !exclusiveLock.getEditionLock().isNullLock()) {
            CmsLockType type = CmsLockType.SHARED_EXCLUSIVE;
            if (!getParentLock(siblingName).isNullLock()) {
                type = CmsLockType.SHARED_INHERITED;
            }
            if (lock == null) {
                lock = new CmsLock(siblingName, exclusiveLock.getUserId(), exclusiveLock.getProject(), type);
            } else {
                CmsLock editionLock = new CmsLock(
                    siblingName,
                    exclusiveLock.getUserId(),
                    exclusiveLock.getProject(),
                    type);
                lock.setRelatedLock(editionLock);
            }
        }
        return lock;
    }

    /**
     * Sets the given lock to the resource.<p>
     * 
     * @param lock the lock to set
     * 
     * @throws CmsLockException if the lock is not compatible with the current lock 
     */
    private void lockResource(CmsLock lock) throws CmsLockException {

        m_isDirty = true;
        internalLockResource(lock, null);
    }

    /**
     * Unlocks the the resource with the given name.<p>
     * 
     * @param resourceName the name of the resource to unlock
     * @param systemLocks <code>true</code> if only system locks should be removed, 
     *              and <code>false</code> if only exclusive locks should be removed
     * 
     * @return the removed lock object
     */
    private CmsLock unlockResource(String resourceName, boolean systemLocks) {

        m_isDirty = true;

        // get the current lock
        CmsLock lock = OpenCms.getMemoryMonitor().getCachedLock(resourceName);
        if (lock == null) {
            return CmsLock.getNullLock();
        }

        // check the lock type (system or user) to remove
        if (systemLocks) {
            if (!lock.getSystemLock().isUnlocked()) {
                // if a system lock has to be removed
                // user locks are removed too
                OpenCms.getMemoryMonitor().uncacheLock(resourceName);
                return lock;
            } else {
                // if it is a edition lock, do nothing
                return CmsLock.getNullLock();
            }
        } else {
            if (lock.getSystemLock().isUnlocked()) {
                // if it is just an edition lock just remove it
                OpenCms.getMemoryMonitor().uncacheLock(resourceName);
                return lock;
            } else {
                // if it is a system lock check the edition lock
                if (!lock.getEditionLock().isUnlocked()) {
                    // remove the edition lock
                    CmsLock tmp = lock.getEditionLock();
                    CmsLock sysLock = lock.getSystemLock();
                    sysLock.setRelatedLock(null);
                    if (!sysLock.equals(lock)) {
                        // replace the lock entry if needed
                        OpenCms.getMemoryMonitor().cacheLock(sysLock);
                    }
                    return tmp;
                } else {
                    // if there is no edition lock, only a system lock, do nothing
                    return CmsLock.getNullLock();
                }
            }
        }
    }
}