org.opencms.db.CmsDriverManager.java Source code

Java tutorial

Introduction

Here is the source code for org.opencms.db.CmsDriverManager.java

Source

/*
 * This library is part of OpenCms -
 * the Open Source Content Management System
 *
 * Copyright (c) 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.db;

import org.opencms.configuration.CmsConfigurationManager;
import org.opencms.configuration.CmsParameterConfiguration;
import org.opencms.configuration.CmsSystemConfiguration;
import org.opencms.db.generic.CmsUserDriver;
import org.opencms.db.log.CmsLogEntry;
import org.opencms.db.log.CmsLogEntryType;
import org.opencms.db.log.CmsLogFilter;
import org.opencms.db.urlname.CmsUrlNameMappingEntry;
import org.opencms.db.urlname.CmsUrlNameMappingFilter;
import org.opencms.file.CmsDataAccessException;
import org.opencms.file.CmsFile;
import org.opencms.file.CmsFolder;
import org.opencms.file.CmsGroup;
import org.opencms.file.CmsObject;
import org.opencms.file.CmsProject;
import org.opencms.file.CmsProperty;
import org.opencms.file.CmsPropertyDefinition;
import org.opencms.file.CmsRequestContext;
import org.opencms.file.CmsResource;
import org.opencms.file.CmsResourceFilter;
import org.opencms.file.CmsUser;
import org.opencms.file.CmsUserSearchParameters;
import org.opencms.file.CmsVfsException;
import org.opencms.file.CmsVfsResourceAlreadyExistsException;
import org.opencms.file.CmsVfsResourceNotFoundException;
import org.opencms.file.I_CmsResource;
import org.opencms.file.history.CmsHistoryFile;
import org.opencms.file.history.CmsHistoryFolder;
import org.opencms.file.history.CmsHistoryPrincipal;
import org.opencms.file.history.CmsHistoryProject;
import org.opencms.file.history.I_CmsHistoryResource;
import org.opencms.file.types.CmsResourceTypeFolder;
import org.opencms.file.types.CmsResourceTypeJsp;
import org.opencms.file.types.I_CmsResourceType;
import org.opencms.flex.CmsFlexRequestContextInfo;
import org.opencms.i18n.CmsLocaleManager;
import org.opencms.i18n.CmsMessageContainer;
import org.opencms.lock.CmsLock;
import org.opencms.lock.CmsLockException;
import org.opencms.lock.CmsLockFilter;
import org.opencms.lock.CmsLockManager;
import org.opencms.lock.CmsLockType;
import org.opencms.main.CmsEvent;
import org.opencms.main.CmsException;
import org.opencms.main.CmsIllegalArgumentException;
import org.opencms.main.CmsIllegalStateException;
import org.opencms.main.CmsInitException;
import org.opencms.main.CmsLog;
import org.opencms.main.CmsMultiException;
import org.opencms.main.I_CmsEventListener;
import org.opencms.main.OpenCms;
import org.opencms.module.CmsModule;
import org.opencms.monitor.CmsMemoryMonitor;
import org.opencms.publish.CmsPublishEngine;
import org.opencms.publish.CmsPublishJobInfoBean;
import org.opencms.publish.CmsPublishReport;
import org.opencms.relations.CmsCategoryService;
import org.opencms.relations.CmsLink;
import org.opencms.relations.CmsRelation;
import org.opencms.relations.CmsRelationFilter;
import org.opencms.relations.CmsRelationSystemValidator;
import org.opencms.relations.CmsRelationType;
import org.opencms.relations.I_CmsLinkParseable;
import org.opencms.report.CmsLogReport;
import org.opencms.report.I_CmsReport;
import org.opencms.security.CmsAccessControlEntry;
import org.opencms.security.CmsAccessControlList;
import org.opencms.security.CmsAuthentificationException;
import org.opencms.security.CmsOrganizationalUnit;
import org.opencms.security.CmsPasswordEncryptionException;
import org.opencms.security.CmsPermissionSet;
import org.opencms.security.CmsPermissionSetCustom;
import org.opencms.security.CmsPrincipal;
import org.opencms.security.CmsRole;
import org.opencms.security.CmsSecurityException;
import org.opencms.security.I_CmsPermissionHandler;
import org.opencms.security.I_CmsPrincipal;
import org.opencms.util.CmsFileUtil;
import org.opencms.util.CmsStringUtil;
import org.opencms.util.CmsUUID;
import org.opencms.util.PrintfFormat;
import org.opencms.workplace.commons.CmsProgressThread;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;

import org.apache.commons.dbcp.PoolingDriver;
import org.apache.commons.logging.Log;
import org.apache.commons.pool.ObjectPool;

import com.google.common.base.Objects;
import com.google.common.collect.ArrayListMultimap;

/**
 * The OpenCms driver manager.<p>
 * 
 * @since 6.0.0
 */
public final class CmsDriverManager implements I_CmsEventListener {

    /**
     * The comparator used for comparing url name mapping entries by date.<p>
     */
    class UrlNameMappingComparator implements Comparator<CmsUrlNameMappingEntry> {

        /**
         * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
         */
        public int compare(CmsUrlNameMappingEntry o1, CmsUrlNameMappingEntry o2) {

            long date1 = o1.getDateChanged();
            long date2 = o2.getDateChanged();
            if (date1 < date2) {
                return -1;
            }
            if (date1 > date2) {
                return +1;
            }
            return 0;
        }
    }

    /**
     * Enumeration class for the mode parameter in the 
     * {@link CmsDriverManager#readChangedResourcesInsideProject(CmsDbContext, CmsUUID, CmsReadChangedProjectResourceMode)} 
     * method.<p>
     */
    private static class CmsReadChangedProjectResourceMode {

        /**
         * Default constructor.<p>
         */
        protected CmsReadChangedProjectResourceMode() {

            // noop
        }
    }

    /** Attribute login. */
    public static final String ATTRIBUTE_LOGIN = "A_LOGIN";

    /** Cache key for all properties. */
    public static final String CACHE_ALL_PROPERTIES = "_CAP_";

    /** 
     * Values indicating changes of a resource, 
     * ordered according to the scope of the change. 
     */
    /** Value to indicate a change in access control entries of a resource. */
    public static final int CHANGED_ACCESSCONTROL = 1;

    /** Value to indicate a content change. */
    public static final int CHANGED_CONTENT = 16;

    /** Value to indicate a change in the lastmodified settings of a resource. */
    public static final int CHANGED_LASTMODIFIED = 4;

    /** Value to indicate a change in the resource data. */
    public static final int CHANGED_RESOURCE = 8;

    /** Value to indicate a change in the availability timeframe. */
    public static final int CHANGED_TIMEFRAME = 2;

    /** "cache" string in the configuration-file. */
    public static final String CONFIGURATION_CACHE = "cache";

    /** "db" string in the configuration-file. */
    public static final String CONFIGURATION_DB = "db";

    /** "driver.history" string in the configuration-file. */
    public static final String CONFIGURATION_HISTORY = "driver.history";

    /** "driver.project" string in the configuration-file. */
    public static final String CONFIGURATION_PROJECT = "driver.project";

    /** "subscription.vfs" string in the configuration file. */
    public static final String CONFIGURATION_SUBSCRIPTION = "driver.subscription";

    /** "driver.user" string in the configuration-file. */
    public static final String CONFIGURATION_USER = "driver.user";

    /** "driver.vfs" string in the configuration-file. */
    public static final String CONFIGURATION_VFS = "driver.vfs";

    /** DBC attribute key needed to fix publishing behavior involving siblings. */
    public static final String KEY_CHANGED_AND_DELETED = "changedAndDeleted";

    /** The vfs path of the loast and found folder. */
    public static final String LOST_AND_FOUND_FOLDER = "/system/lost-found";

    /** The maximum length of a VFS resource path. */
    public static final int MAX_VFS_RESOURCE_PATH_LENGTH = 512;

    /** Key for indicating no changes. */
    public static final int NOTHING_CHANGED = 0;

    /** Indicates to ignore the resource path when matching resources. */
    public static final String READ_IGNORE_PARENT = null;

    /** Indicates to ignore the time value. */
    public static final long READ_IGNORE_TIME = 0L;

    /** Indicates to ignore the resource type when matching resources. */
    public static final int READ_IGNORE_TYPE = -1;

    /** Indicates to match resources NOT having the given state. */
    public static final int READMODE_EXCLUDE_STATE = 8;

    /** Indicates to match immediate children only. */
    public static final int READMODE_EXCLUDE_TREE = 1;

    /** Indicates to match resources NOT having the given type. */
    public static final int READMODE_EXCLUDE_TYPE = 4;

    /** Mode for reading project resources from the db. */
    public static final int READMODE_IGNORESTATE = 0;

    /** Indicates to match resources in given project only. */
    public static final int READMODE_INCLUDE_PROJECT = 2;

    /** Indicates to match all successors. */
    public static final int READMODE_INCLUDE_TREE = 0;

    /** Mode for reading project resources from the db. */
    public static final int READMODE_MATCHSTATE = 1;

    /** Indicates if only file resources should be read. */
    public static final int READMODE_ONLY_FILES = 128;

    /** Indicates if only folder resources should be read. */
    public static final int READMODE_ONLY_FOLDERS = 64;

    /** Mode for reading project resources from the db. */
    public static final int READMODE_UNMATCHSTATE = 2;

    /** Prefix char for temporary files in the VFS. */
    public static final String TEMP_FILE_PREFIX = "~";

    /** Key to indicate complete update. */
    public static final int UPDATE_ALL = 3;

    /** Key to indicate update of resource record. */
    public static final int UPDATE_RESOURCE = 4;

    /** Key to indicate update of last modified project reference. */
    public static final int UPDATE_RESOURCE_PROJECT = 6;

    /** Key to indicate update of resource state. */
    public static final int UPDATE_RESOURCE_STATE = 1;

    /** Key to indicate update of resource state including the content date. */
    public static final int UPDATE_RESOURCE_STATE_CONTENT = 7;

    /** Key to indicate update of structure record. */
    public static final int UPDATE_STRUCTURE = 5;

    /** Key to indicate update of structure state. */
    public static final int UPDATE_STRUCTURE_STATE = 2;

    /** The log object for this class. */
    private static final Log LOG = CmsLog.getLog(CmsDriverManager.class);

    /** Constant mode parameter to read all files and folders in the {@link #readChangedResourcesInsideProject(CmsDbContext, CmsUUID, CmsReadChangedProjectResourceMode)}} method. */
    private static final CmsReadChangedProjectResourceMode RCPRM_FILES_AND_FOLDERS_MODE = new CmsReadChangedProjectResourceMode();

    /** Constant mode parameter to read all files and folders in the {@link #readChangedResourcesInsideProject(CmsDbContext, CmsUUID, CmsReadChangedProjectResourceMode)}} method. */
    private static final CmsReadChangedProjectResourceMode RCPRM_FILES_ONLY_MODE = new CmsReadChangedProjectResourceMode();

    /** Constant mode parameter to read all files and folders in the {@link #readChangedResourcesInsideProject(CmsDbContext, CmsUUID, CmsReadChangedProjectResourceMode)}} method. */
    private static final CmsReadChangedProjectResourceMode RCPRM_FOLDERS_ONLY_MODE = new CmsReadChangedProjectResourceMode();

    /** The list of initialized JDBC pools. */
    private List<PoolingDriver> m_connectionPools;

    /** The history driver. */
    private I_CmsHistoryDriver m_historyDriver;

    /** The HTML link validator. */
    private CmsRelationSystemValidator m_htmlLinkValidator;

    /** The class used for cache key generation. */
    private I_CmsCacheKey m_keyGenerator;

    /** The lock manager. */
    private CmsLockManager m_lockManager;

    /** The log entry cache. */
    private List<CmsLogEntry> m_log = new ArrayList<CmsLogEntry>();

    /** Local reference to the memory monitor to avoid multiple lookups through the OpenCms singleton. */
    private CmsMemoryMonitor m_monitor;

    /** The project driver. */
    private I_CmsProjectDriver m_projectDriver;

    /** The the configuration read from the <code>opencms.properties</code> file. */
    private CmsParameterConfiguration m_propertyConfiguration;

    /** the publish engine. */
    private CmsPublishEngine m_publishEngine;

    /** The security manager (for access checks). */
    private CmsSecurityManager m_securityManager;

    /** The sql manager. */
    private CmsSqlManager m_sqlManager;

    /** The subscription driver. */
    private I_CmsSubscriptionDriver m_subscriptionDriver;

    /** The user driver. */
    private I_CmsUserDriver m_userDriver;

    /** The VFS driver. */
    private I_CmsVfsDriver m_vfsDriver;

    /**
     * Private constructor, initializes some required member variables.<p> 
     */
    private CmsDriverManager() {

        // intentionally left blank
    }

    /**
     * Reads the required configurations from the opencms.properties file and creates
     * the various drivers to access the cms resources.<p>
     * 
     * The initialization process of the driver manager and its drivers is split into
     * the following phases:
     * <ul>
     * <li>the database pool configuration is read</li>
     * <li>a plain and empty driver manager instance is created</li>
     * <li>an instance of each driver is created</li>
     * <li>the driver manager is passed to each driver during initialization</li>
     * <li>finally, the driver instances are passed to the driver manager during initialization</li>
     * </ul>
     * 
     * @param configurationManager the configuration manager
     * @param securityManager the security manager
     * @param runtimeInfoFactory the initialized OpenCms runtime info factory
     * @param publishEngine the publish engine
     * 
     * @return CmsDriverManager the instantiated driver manager
     * @throws CmsInitException if the driver manager couldn't be instantiated
     */
    public static CmsDriverManager newInstance(CmsConfigurationManager configurationManager,
            CmsSecurityManager securityManager, I_CmsDbContextFactory runtimeInfoFactory,
            CmsPublishEngine publishEngine) throws CmsInitException {

        // read the opencms.properties from the configuration
        CmsParameterConfiguration config = configurationManager.getConfiguration();

        CmsDriverManager driverManager = null;
        try {
            // create a driver manager instance
            driverManager = new CmsDriverManager();
            if (CmsLog.INIT.isInfoEnabled()) {
                CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_DRIVER_MANAGER_START_PHASE1_0));
            }
            if (runtimeInfoFactory == null) {
                throw new CmsInitException(org.opencms.main.Messages.get()
                        .container(org.opencms.main.Messages.ERR_CRITICAL_NO_DB_CONTEXT_0));
            }
        } catch (Exception exc) {
            CmsMessageContainer message = Messages.get().container(Messages.LOG_ERR_DRIVER_MANAGER_START_0);
            if (LOG.isFatalEnabled()) {
                LOG.fatal(message.key(), exc);
            }
            throw new CmsInitException(message, exc);
        }

        // store the configuration
        driverManager.m_propertyConfiguration = config;

        // set the security manager
        driverManager.m_securityManager = securityManager;

        // set connection pools
        driverManager.m_connectionPools = new ArrayList<PoolingDriver>();

        // set the lock manager
        driverManager.m_lockManager = new CmsLockManager(driverManager);

        // create and set the sql manager
        driverManager.m_sqlManager = new CmsSqlManager(driverManager);

        // set the publish engine
        driverManager.m_publishEngine = publishEngine;

        if (CmsLog.INIT.isInfoEnabled()) {
            CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_DRIVER_MANAGER_START_PHASE2_0));
        }

        // read the pool names to initialize
        List<String> driverPoolNames = config.getList(CmsDriverManager.CONFIGURATION_DB + ".pools");
        if (CmsLog.INIT.isInfoEnabled()) {
            String names = "";
            for (String name : driverPoolNames) {
                names += name + " ";
            }
            CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_DRIVER_MANAGER_START_POOLS_1, names));
        }

        // initialize each pool
        for (String name : driverPoolNames) {
            driverManager.newPoolInstance(config, name);
        }

        // initialize the runtime info factory with the generated driver manager
        runtimeInfoFactory.initialize(driverManager);

        if (CmsLog.INIT.isInfoEnabled()) {
            CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_DRIVER_MANAGER_START_PHASE3_0));
        }

        // store the access objects
        CmsDbContext dbc = runtimeInfoFactory.getDbContext();
        driverManager.m_vfsDriver = (I_CmsVfsDriver) driverManager.createDriver(dbc, configurationManager, config,
                CONFIGURATION_VFS, ".vfs.driver");
        dbc.clear();

        dbc = runtimeInfoFactory.getDbContext();
        driverManager.m_userDriver = (I_CmsUserDriver) driverManager.createDriver(dbc, configurationManager, config,
                CONFIGURATION_USER, ".user.driver");
        dbc.clear();

        dbc = runtimeInfoFactory.getDbContext();
        driverManager.m_projectDriver = (I_CmsProjectDriver) driverManager.createDriver(dbc, configurationManager,
                config, CONFIGURATION_PROJECT, ".project.driver");
        dbc.clear();

        dbc = runtimeInfoFactory.getDbContext();
        driverManager.m_historyDriver = (I_CmsHistoryDriver) driverManager.createDriver(dbc, configurationManager,
                config, CONFIGURATION_HISTORY, ".history.driver");
        dbc.clear();

        dbc = runtimeInfoFactory.getDbContext();
        try {
            // we wrap this in a try-catch because otherwise it would fail during the update 
            // process, since the subscription driver configuration does not exist at that point. 
            driverManager.m_subscriptionDriver = (I_CmsSubscriptionDriver) driverManager.createDriver(dbc,
                    configurationManager, config, CONFIGURATION_SUBSCRIPTION, ".subscription.driver");
        } catch (IndexOutOfBoundsException npe) {
            LOG.warn("Could not instantiate subscription driver!");
            LOG.warn(npe.getLocalizedMessage(), npe);
        }
        dbc.clear();

        // register the driver manager for required events
        org.opencms.main.OpenCms.addCmsEventListener(driverManager,
                new int[] { I_CmsEventListener.EVENT_UPDATE_EXPORTS, I_CmsEventListener.EVENT_CLEAR_CACHES,
                        I_CmsEventListener.EVENT_CLEAR_PRINCIPAL_CACHES, I_CmsEventListener.EVENT_USER_MODIFIED,
                        I_CmsEventListener.EVENT_PUBLISH_PROJECT });

        // return the configured driver manager
        return driverManager;
    }

    /**
     * Adds a new relation to the given resource.<p>
     * 
     * @param dbc the database context
     * @param resource the resource to add the relation to
     * @param target the target of the relation
     * @param type the type of the relation
     * @param importCase if importing relations
     * 
     * @throws CmsException if something goes wrong
     */
    public void addRelationToResource(CmsDbContext dbc, CmsResource resource, CmsResource target,
            CmsRelationType type, boolean importCase) throws CmsException {

        if (type.isDefinedInContent()) {
            throw new CmsIllegalArgumentException(Messages.get().container(Messages.ERR_ADD_RELATION_IN_CONTENT_3,
                    dbc.removeSiteRoot(resource.getRootPath()), dbc.removeSiteRoot(target.getRootPath()),
                    type.getLocalizedName(dbc.getRequestContext().getLocale())));
        }
        CmsRelation relation = new CmsRelation(resource, target, type);
        getVfsDriver(dbc).createRelation(dbc, dbc.currentProject().getUuid(), relation);
        if (!importCase) {
            // log it
            log(dbc, new CmsLogEntry(dbc, resource.getStructureId(), CmsLogEntryType.RESOURCE_ADD_RELATION,
                    new String[] { relation.getSourcePath(), relation.getTargetPath() }), false);
            // touch the resource
            setDateLastModified(dbc, resource, System.currentTimeMillis());
        }
    }

    /**
     * Adds a resource to the given organizational unit.<p>
     * 
     * @param dbc the current db context
     * @param orgUnit the organizational unit to add the resource to
     * @param resource the resource that is to be added to the organizational unit
     * 
     * @throws CmsException if something goes wrong
     * 
     * @see org.opencms.security.CmsOrgUnitManager#addResourceToOrgUnit(CmsObject, String, String)
     * @see org.opencms.security.CmsOrgUnitManager#addResourceToOrgUnit(CmsObject, String, String)
     */
    public void addResourceToOrgUnit(CmsDbContext dbc, CmsOrganizationalUnit orgUnit, CmsResource resource)
            throws CmsException {

        m_monitor.flushCache(CmsMemoryMonitor.CacheType.HAS_ROLE, CmsMemoryMonitor.CacheType.ROLE_LIST);
        getUserDriver(dbc).addResourceToOrganizationalUnit(dbc, orgUnit, resource);
    }

    /**
     * Adds a user to a group.<p>
     *
     * @param dbc the current database context
     * @param username the name of the user that is to be added to the group
     * @param groupname the name of the group
     * @param readRoles if reading roles or groups
     *
     * @throws CmsException if operation was not successful
     * @throws CmsDbEntryNotFoundException if the given user or the given group was not found 
     * 
     * @see #removeUserFromGroup(CmsDbContext, String, String, boolean)
     */
    public void addUserToGroup(CmsDbContext dbc, String username, String groupname, boolean readRoles)
            throws CmsException, CmsDbEntryNotFoundException {

        //check if group exists
        CmsGroup group = readGroup(dbc, groupname);
        if (group == null) {
            // the group does not exists
            throw new CmsDbEntryNotFoundException(
                    Messages.get().container(Messages.ERR_UNKNOWN_GROUP_1, groupname));
        }
        if (group.isVirtual() && !readRoles) {
            // if adding a user from a virtual role treat it as removing the user from the role
            addUserToGroup(dbc, username, CmsRole.valueOf(group).getGroupName(), true);
            return;
        }
        if (group.isVirtual()) {
            // this is an hack to prevent unlimited recursive calls
            readRoles = false;
        }
        if ((readRoles && !group.isRole()) || (!readRoles && group.isRole())) {
            // we want a role but we got a group, or the other way
            throw new CmsDbEntryNotFoundException(
                    Messages.get().container(Messages.ERR_UNKNOWN_GROUP_1, groupname));
        }
        if (userInGroup(dbc, username, groupname, readRoles)) {
            // the user is already member of the group
            return;
        }
        //check if the user exists
        CmsUser user = readUser(dbc, username);
        if (user == null) {
            // the user does not exists
            throw new CmsDbEntryNotFoundException(Messages.get().container(Messages.ERR_UNKNOWN_USER_1, username));
        }

        // if adding an user to a role
        if (readRoles) {
            CmsRole role = CmsRole.valueOf(group);
            // a role can only be set if the user has the given role
            m_securityManager.checkRole(dbc, role);
            // now we check if we already have the role 
            if (m_securityManager.hasRole(dbc, user, role)) {
                // do nothing
                return;
            }
            // and now we need to remove all possible child-roles
            List<CmsRole> children = role.getChildren(true);
            Iterator<CmsGroup> itUserGroups = getGroupsOfUser(dbc, username, group.getOuFqn(), true, true, true,
                    dbc.getRequestContext().getRemoteAddress()).iterator();
            while (itUserGroups.hasNext()) {
                CmsGroup roleGroup = itUserGroups.next();
                if (children.contains(CmsRole.valueOf(roleGroup))) {
                    // remove only child roles
                    removeUserFromGroup(dbc, username, roleGroup.getName(), true);
                }
            }
            // update virtual groups
            Iterator<CmsGroup> it = getVirtualGroupsForRole(dbc, role).iterator();
            while (it.hasNext()) {
                CmsGroup virtualGroup = it.next();
                // here we say readroles = true, to prevent an unlimited recursive calls
                addUserToGroup(dbc, username, virtualGroup.getName(), true);
            }
            // if setting a role that is not the workplace user role ensure the user is also wp user
            CmsRole wpUser = CmsRole.WORKPLACE_USER.forOrgUnit(group.getOuFqn());
            if (!role.equals(wpUser) && !role.getChildren(true).contains(wpUser)
                    && !m_securityManager.hasRole(dbc, user, wpUser)) {
                addUserToGroup(dbc, username, wpUser.getGroupName(), true);
            }
        }

        //add this user to the group
        getUserDriver(dbc).createUserInGroup(dbc, user.getId(), group.getId());

        // flush the cache
        if (readRoles) {
            m_monitor.flushCache(CmsMemoryMonitor.CacheType.HAS_ROLE, CmsMemoryMonitor.CacheType.ROLE_LIST);
        }
        m_monitor.flushCache(CmsMemoryMonitor.CacheType.USERGROUPS, CmsMemoryMonitor.CacheType.USER_LIST);

        if (!dbc.getProjectId().isNullUUID()) {
            // user modified event is not needed
            return;
        }
        // fire user modified event
        Map<String, Object> eventData = new HashMap<String, Object>();
        eventData.put(I_CmsEventListener.KEY_USER_ID, user.getId().toString());
        eventData.put(I_CmsEventListener.KEY_USER_NAME, user.getName());
        eventData.put(I_CmsEventListener.KEY_USER_ID, user.getId().toString());
        eventData.put(I_CmsEventListener.KEY_GROUP_NAME, group.getName());
        eventData.put(I_CmsEventListener.KEY_USER_ACTION,
                I_CmsEventListener.VALUE_USER_MODIFIED_ACTION_ADD_USER_TO_GROUP);
        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_USER_MODIFIED, eventData));
    }

    /**
     * Changes the lock of a resource to the current user,
     * that is "steals" the lock from another user.<p>
     * 
     * @param dbc the current database context
     * @param resource the resource to change the lock for
     * @param lockType the new lock type to set
     * 
     * @throws CmsException if something goes wrong
     * @throws CmsSecurityException if something goes wrong
     * 
     * 
     * @see CmsObject#changeLock(String)
     * @see I_CmsResourceType#changeLock(CmsObject, CmsSecurityManager, CmsResource)
     * 
     * @see CmsSecurityManager#hasPermissions(CmsRequestContext, CmsResource, CmsPermissionSet, boolean, CmsResourceFilter)
     */
    public void changeLock(CmsDbContext dbc, CmsResource resource, CmsLockType lockType)
            throws CmsException, CmsSecurityException {

        // get the current lock
        CmsLock currentLock = getLock(dbc, resource);
        // check if the resource is locked at all
        if (currentLock.getEditionLock().isUnlocked() && currentLock.getSystemLock().isUnlocked()) {
            throw new CmsLockException(Messages.get().container(Messages.ERR_CHANGE_LOCK_UNLOCKED_RESOURCE_1,
                    dbc.getRequestContext().getSitePath(resource)));
        } else if ((lockType == CmsLockType.EXCLUSIVE)
                && currentLock.isExclusiveOwnedInProjectBy(dbc.currentUser(), dbc.currentProject())) {
            // the current lock requires no change
            return;
        }

        // duplicate logic from CmsSecurityManager#hasPermissions() because lock state can't be ignored
        // if another user has locked the file, the current user can never get WRITE permissions with the default check
        int denied = 0;

        // check if the current user is vfs manager
        boolean canIgnorePermissions = m_securityManager.hasRoleForResource(dbc, dbc.currentUser(),
                CmsRole.VFS_MANAGER, resource);
        // if the resource type is jsp
        // write is only allowed for developers
        if (!canIgnorePermissions && (CmsResourceTypeJsp.isJsp(resource))) {
            if (!m_securityManager.hasRoleForResource(dbc, dbc.currentUser(), CmsRole.DEVELOPER, resource)) {
                denied |= CmsPermissionSet.PERMISSION_WRITE;
            }
        }
        CmsPermissionSetCustom permissions;
        if (canIgnorePermissions) {
            // if the current user is administrator, anything is allowed
            permissions = new CmsPermissionSetCustom(~0);
        } else {
            // otherwise, get the permissions from the access control list
            permissions = getPermissions(dbc, resource, dbc.currentUser());
        }
        // revoke the denied permissions
        permissions.denyPermissions(denied);
        // now check if write permission is granted
        if ((CmsPermissionSet.ACCESS_WRITE.getPermissions()
                & permissions.getPermissions()) != CmsPermissionSet.ACCESS_WRITE.getPermissions()) {
            // check failed, throw exception
            m_securityManager.checkPermissions(dbc.getRequestContext(), resource, CmsPermissionSet.ACCESS_WRITE,
                    I_CmsPermissionHandler.PERM_DENIED);
        }
        // if we got here write permission is granted on the target

        // remove the old lock
        m_lockManager.removeResource(dbc, resource, true, lockType.isSystem());
        // apply the new lock
        lockResource(dbc, resource, lockType);
    }

    /**
     * Returns a list with all sub resources of a given folder that have set the given property, 
     * matching the current property's value with the given old value and replacing it by a given new value.<p>
     *
     * @param dbc the current database context
     * @param resource the resource on which property definition values are changed
     * @param propertyDefinition the name of the propertydefinition to change the value
     * @param oldValue the old value of the propertydefinition
     * @param newValue the new value of the propertydefinition
     * @param recursive if true, change the property value on the resource and recursively all property values on 
     *                     sub-resources (only for folders)
     * @return a list with the <code>{@link CmsResource}</code>'s where the property value has been changed
     *
     * @throws CmsVfsException for now only when the search for the oldvalue failed. 
     * @throws CmsException if operation was not successful
     */
    public List<CmsResource> changeResourcesInFolderWithProperty(CmsDbContext dbc, CmsResource resource,
            String propertyDefinition, String oldValue, String newValue, boolean recursive)
            throws CmsVfsException, CmsException {

        CmsResourceFilter filter = CmsResourceFilter.IGNORE_EXPIRATION;
        // collect the resources to look up
        List<CmsResource> resources = new ArrayList<CmsResource>();
        if (recursive) {
            // read the files in the folder
            resources = readResourcesWithProperty(dbc, resource, propertyDefinition, null, filter);
            // add the folder itself
            resources.add(resource);
        } else {
            resources.add(resource);
        }

        Pattern oldPattern;
        try {
            // remove the place holder if available
            String tmpOldValue = oldValue;
            if (tmpOldValue.contains(CmsStringUtil.PLACEHOLDER_START)
                    && tmpOldValue.contains(CmsStringUtil.PLACEHOLDER_END)) {
                tmpOldValue = tmpOldValue.replace(CmsStringUtil.PLACEHOLDER_START, "");
                tmpOldValue = tmpOldValue.replace(CmsStringUtil.PLACEHOLDER_END, "");
            }
            // compile regular expression pattern
            oldPattern = Pattern.compile(tmpOldValue);
        } catch (PatternSyntaxException e) {
            throw new CmsVfsException(Messages.get().container(Messages.ERR_CHANGE_RESOURCES_IN_FOLDER_WITH_PROP_4,
                    new Object[] { propertyDefinition, oldValue, newValue, resource.getRootPath() }), e);
        }

        List<CmsResource> changedResources = new ArrayList<CmsResource>(resources.size());
        // create permission set and filter to check each resource
        CmsPermissionSet perm = CmsPermissionSet.ACCESS_WRITE;
        for (int i = 0; i < resources.size(); i++) {
            // loop through found resources and check property values
            CmsResource res = resources.get(i);
            // check resource state and permissions
            try {
                m_securityManager.checkPermissions(dbc, res, perm, true, filter);
            } catch (Exception e) {
                // resource is deleted or not writable for current user
                continue;
            }
            CmsProperty property = readPropertyObject(dbc, res, propertyDefinition, false);
            String propertyValue = property.getValue();
            boolean changed = false;
            if ((propertyValue != null) && oldPattern.matcher(propertyValue).matches()) {
                // apply the place holder content
                String tmpNewValue = CmsStringUtil.transformValues(oldValue, newValue, propertyValue);
                // change structure value
                property.setStructureValue(tmpNewValue);
                changed = true;
            }
            if (changed) {
                // write property object if something has changed
                writePropertyObject(dbc, res, property);
                changedResources.add(res);
            }
        }
        return changedResources;
    }

    /**
     * Changes the resource flags of a resource.<p>
     * 
     * The resource flags are used to indicate various "special" conditions
     * for a resource. Most notably, the "internal only" setting which signals 
     * that a resource can not be directly requested with it's URL.<p>
     * 
     * @param dbc the current database context
     * @param resource the resource to change the flags for
     * @param flags the new resource flags for this resource
     *
     * @throws CmsException if something goes wrong
     * 
     * @see CmsObject#chflags(String, int)
     * @see I_CmsResourceType#chflags(CmsObject, CmsSecurityManager, CmsResource, int)
     */
    public void chflags(CmsDbContext dbc, CmsResource resource, int flags) throws CmsException {

        // must operate on a clone to ensure resource is not modified in case permissions are not granted
        CmsResource clone = (CmsResource) resource.clone();
        clone.setFlags(flags);
        // log it
        log(dbc, new CmsLogEntry(dbc, resource.getStructureId(), CmsLogEntryType.RESOURCE_FLAGS,
                new String[] { resource.getRootPath() }), false);
        // write it
        writeResource(dbc, clone);
    }

    /**
     * Changes the resource type of a resource.<p>
     * 
     * OpenCms handles resources according to the resource type,
     * not the file suffix. This is e.g. why a JSP in OpenCms can have the 
     * suffix ".html" instead of ".jsp" only. Changing the resource type
     * makes sense e.g. if you want to make a plain text file a JSP resource,
     * or a binary file an image, etc.<p> 
     * 
     * @param dbc the current database context
     * @param resource the resource to change the type for
     * @param type the new resource type for this resource
     *
     * @throws CmsException if something goes wrong
     * 
     * @see CmsObject#chtype(String, int)
     * @see I_CmsResourceType#chtype(CmsObject, CmsSecurityManager, CmsResource, int)
     */
    public void chtype(CmsDbContext dbc, CmsResource resource, int type) throws CmsException {

        // must operate on a clone to ensure resource is not modified in case permissions are not granted
        CmsResource clone = (CmsResource) resource.clone();
        I_CmsResourceType newType = OpenCms.getResourceManager().getResourceType(type);
        clone.setType(newType.getTypeId());
        // log it
        log(dbc, new CmsLogEntry(dbc, resource.getStructureId(), CmsLogEntryType.RESOURCE_TYPE,
                new String[] { resource.getRootPath() }), false);
        // write it
        writeResource(dbc, clone);
    }

    /**
     * @see org.opencms.main.I_CmsEventListener#cmsEvent(org.opencms.main.CmsEvent)
     */
    public void cmsEvent(CmsEvent event) {

        if (LOG.isDebugEnabled()) {
            LOG.debug(Messages.get().getBundle().key(Messages.LOG_CMS_EVENT_1, new Integer(event.getType())));
        }

        I_CmsReport report;
        CmsDbContext dbc;

        switch (event.getType()) {

        case I_CmsEventListener.EVENT_UPDATE_EXPORTS:
            dbc = (CmsDbContext) event.getData().get(I_CmsEventListener.KEY_DBCONTEXT);
            updateExportPoints(dbc);
            break;

        case I_CmsEventListener.EVENT_PUBLISH_PROJECT:
            CmsUUID publishHistoryId = new CmsUUID((String) event.getData().get(I_CmsEventListener.KEY_PUBLISHID));
            report = (I_CmsReport) event.getData().get(I_CmsEventListener.KEY_REPORT);
            dbc = (CmsDbContext) event.getData().get(I_CmsEventListener.KEY_DBCONTEXT);
            m_monitor.clearCache();
            writeExportPoints(dbc, report, publishHistoryId);
            break;

        case I_CmsEventListener.EVENT_CLEAR_CACHES:
            m_monitor.clearCache();
            break;
        case I_CmsEventListener.EVENT_CLEAR_PRINCIPAL_CACHES:
        case I_CmsEventListener.EVENT_USER_MODIFIED:
            m_monitor.clearPrincipalsCache();
            break;
        default:
            // noop
        }
    }

    /**
     * Copies the access control entries of a given resource to a destination resource.<p>
     *
     * Already existing access control entries of the destination resource are removed.<p>
     * 
     * @param dbc the current database context
     * @param source the resource to copy the access control entries from
     * @param destination the resource to which the access control entries are copied
     * @param updateLastModifiedInfo if true, user and date "last modified" information on the target resource will be updated
     * 
     * @throws CmsException if something goes wrong
     */
    public void copyAccessControlEntries(CmsDbContext dbc, CmsResource source, CmsResource destination,
            boolean updateLastModifiedInfo) throws CmsException {

        // get the entries to copy
        ListIterator<CmsAccessControlEntry> aceList = getUserDriver(dbc)
                .readAccessControlEntries(dbc, dbc.currentProject(), source.getResourceId(), false).listIterator();

        // remove the current entries from the destination
        getUserDriver(dbc).removeAccessControlEntries(dbc, dbc.currentProject(), destination.getResourceId());

        // now write the new entries
        while (aceList.hasNext()) {
            CmsAccessControlEntry ace = aceList.next();
            getUserDriver(dbc).createAccessControlEntry(dbc, dbc.currentProject(), destination.getResourceId(),
                    ace.getPrincipal(), ace.getPermissions().getAllowedPermissions(),
                    ace.getPermissions().getDeniedPermissions(), ace.getFlags());
        }

        // log it
        log(dbc, new CmsLogEntry(dbc, destination.getStructureId(), CmsLogEntryType.RESOURCE_PERMISSIONS,
                new String[] { destination.getRootPath() }), false);

        // update the "last modified" information
        if (updateLastModifiedInfo) {
            setDateLastModified(dbc, destination, destination.getDateLastModified());
        }

        // clear the cache
        m_monitor.clearAccessControlListCache();

        // fire a resource modification event
        Map<String, Object> data = new HashMap<String, Object>(2);
        data.put(I_CmsEventListener.KEY_RESOURCE, destination);
        data.put(I_CmsEventListener.KEY_CHANGE, new Integer(CHANGED_ACCESSCONTROL));
        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_MODIFIED, data));
    }

    /**
     * Copies a resource.<p>
     * 
     * You must ensure that the destination path is an absolute, valid and
     * existing VFS path. Relative paths from the source are currently not supported.<p>
     * 
     * In case the target resource already exists, it is overwritten with the 
     * source resource.<p>
     * 
     * The <code>siblingMode</code> parameter controls how to handle siblings 
     * during the copy operation.
     * Possible values for this parameter are: 
     * <ul>
     * <li><code>{@link org.opencms.file.CmsResource#COPY_AS_NEW}</code></li>
     * <li><code>{@link org.opencms.file.CmsResource#COPY_AS_SIBLING}</code></li>
     * <li><code>{@link org.opencms.file.CmsResource#COPY_PRESERVE_SIBLING}</code></li>
     * </ul><p>
     * 
     * @param dbc the current database context
     * @param source the resource to copy
     * @param destination the name of the copy destination with complete path
     * @param siblingMode indicates how to handle siblings during copy
     * 
     * @throws CmsException if something goes wrong
     * @throws CmsIllegalArgumentException if the <code>source</code> argument is <code>null</code>
     * 
     * @see CmsObject#copyResource(String, String, CmsResource.CmsResourceCopyMode)
     * @see I_CmsResourceType#copyResource(CmsObject, CmsSecurityManager, CmsResource, String, CmsResource.CmsResourceCopyMode)
     */
    public void copyResource(CmsDbContext dbc, CmsResource source, String destination,
            CmsResource.CmsResourceCopyMode siblingMode) throws CmsException, CmsIllegalArgumentException {

        // check the sibling mode to see if this resource has to be copied as a sibling
        boolean copyAsSibling = false;

        // siblings of folders are not supported
        if (!source.isFolder()) {
            // if the "copy as sibling" mode is used, set the flag to true
            if (siblingMode == CmsResource.COPY_AS_SIBLING) {
                copyAsSibling = true;
            }
            // if the mode is "preserve siblings", we have to check the sibling counter
            if (siblingMode == CmsResource.COPY_PRESERVE_SIBLING) {
                if (source.getSiblingCount() > 1) {
                    copyAsSibling = true;
                }
            }
        }

        // read the source properties
        List<CmsProperty> properties = readPropertyObjects(dbc, source, false);

        if (copyAsSibling) {
            // create a sibling of the source file at the destination  
            createSibling(dbc, source, destination, properties);
            // after the sibling is created the copy operation is finished
            return;
        }

        // prepare the content if required
        byte[] content = null;
        if (source.isFile()) {
            if (source instanceof CmsFile) {
                // resource already is a file
                content = ((CmsFile) source).getContents();
            }
            if ((content == null) || (content.length < 1)) {
                // no known content yet - read from database
                content = getVfsDriver(dbc).readContent(dbc, dbc.currentProject().getUuid(),
                        source.getResourceId());
            }
        }

        // determine destination folder        
        String destinationFoldername = CmsResource.getParentFolder(destination);

        // read the destination folder (will also check read permissions)
        CmsFolder destinationFolder = m_securityManager.readFolder(dbc, destinationFoldername,
                CmsResourceFilter.IGNORE_EXPIRATION);

        // no further permission check required here, will be done in createResource()

        // set user and creation time stamps
        long currentTime = System.currentTimeMillis();
        long dateLastModified;
        CmsUUID userLastModified;
        if (source.isFolder()) {
            // folders always get a new date and user when they are copied
            dateLastModified = currentTime;
            userLastModified = dbc.currentUser().getId();
        } else {
            // files keep the date and user last modified from the source
            dateLastModified = source.getDateLastModified();
            userLastModified = source.getUserLastModified();
        }

        // check the resource flags
        int flags = source.getFlags();
        if (source.isLabeled()) {
            // reset "labeled" link flag for new resource
            flags &= ~CmsResource.FLAG_LABELED;
        }

        // create the new resource        
        CmsResource newResource = new CmsResource(new CmsUUID(), new CmsUUID(), destination, source.getTypeId(),
                source.isFolder(), flags, dbc.currentProject().getUuid(), CmsResource.STATE_NEW, currentTime,
                dbc.currentUser().getId(), dateLastModified, userLastModified, source.getDateReleased(),
                source.getDateExpired(), 1, source.getLength(), source.getDateContent(), source.getVersion()); // version number does not matter since it will be computed later

        // trigger "is touched" state on resource (will ensure modification date is kept unchanged)
        newResource.setDateLastModified(dateLastModified);

        // log it
        log(dbc, new CmsLogEntry(dbc, newResource.getStructureId(), CmsLogEntryType.RESOURCE_COPIED,
                new String[] { newResource.getRootPath() }), false);

        // create the resource
        newResource = createResource(dbc, destination, newResource, content, properties, false);
        // copy relations
        copyRelations(dbc, source, newResource);

        // copy the access control entries to the created resource
        copyAccessControlEntries(dbc, source, newResource, false);

        // clear the cache
        m_monitor.clearAccessControlListCache();

        List<CmsResource> modifiedResources = new ArrayList<CmsResource>();
        modifiedResources.add(source);
        modifiedResources.add(newResource);
        modifiedResources.add(destinationFolder);
        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_COPIED,
                Collections.<String, Object>singletonMap(I_CmsEventListener.KEY_RESOURCES, modifiedResources)));
    }

    /**
     * Copies a resource to the current project of the user.<p>
     * 
     * @param dbc the current database context
     * @param resource the resource to apply this operation to
     * 
     * @throws CmsException if something goes wrong
     * 
     * @see CmsObject#copyResourceToProject(String)
     * @see I_CmsResourceType#copyResourceToProject(CmsObject, CmsSecurityManager, CmsResource)
     */
    public void copyResourceToProject(CmsDbContext dbc, CmsResource resource) throws CmsException {

        // copy the resource to the project only if the resource is not already in the project
        if (!isInsideCurrentProject(dbc, resource.getRootPath())) {
            // check if there are already any subfolders of this resource
            I_CmsProjectDriver projectDriver = getProjectDriver(dbc);
            if (resource.isFolder()) {
                List<String> projectResources = projectDriver.readProjectResources(dbc, dbc.currentProject());
                for (int i = 0; i < projectResources.size(); i++) {
                    String resname = projectResources.get(i);
                    if (resname.startsWith(resource.getRootPath())) {
                        // delete the existing project resource first
                        projectDriver.deleteProjectResource(dbc, dbc.currentProject().getUuid(), resname);
                    }
                }
            }
            try {
                projectDriver.createProjectResource(dbc, dbc.currentProject().getUuid(), resource.getRootPath());
            } catch (CmsException exc) {
                // if the subfolder exists already - all is ok
            } finally {
                m_monitor.flushCache(CmsMemoryMonitor.CacheType.PROJECT_RESOURCES);

                OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_PROJECT_MODIFIED,
                        Collections.<String, Object>singletonMap("project", dbc.currentProject())));
            }
        }
    }

    /**
     * Counts the locked resources in this project.<p>
     *
     * @param project the project to count the locked resources in
     * 
     * @return the amount of locked resources in this project
     */
    public int countLockedResources(CmsProject project) {

        // count locks
        return m_lockManager.countExclusiveLocksInProject(project);
    }

    /**
     * Add a new group to the Cms.<p>
     *
     * Only the admin can do this.
     * Only users, which are in the group "administrators" are granted.<p>
     * 
     * @param dbc the current database context
     * @param id the id of the new group
     * @param name the name of the new group
     * @param description the description for the new group
     * @param flags the flags for the new group
     * @param parent the name of the parent group (or <code>null</code>)
     * 
     * @return new created group
     * 
     * @throws CmsException if the creation of the group failed
     * @throws CmsIllegalArgumentException if the length of the given name was below 1
     */
    public CmsGroup createGroup(CmsDbContext dbc, CmsUUID id, String name, String description, int flags,
            String parent) throws CmsIllegalArgumentException, CmsException {

        // check the group name
        OpenCms.getValidationHandler().checkGroupName(CmsOrganizationalUnit.getSimpleName(name));
        // trim the name
        name = name.trim();

        // check the OU
        readOrganizationalUnit(dbc, CmsOrganizationalUnit.getParentFqn(name));

        // get the id of the parent group if necessary
        if (CmsStringUtil.isNotEmpty(parent)) {
            CmsGroup parentGroup = readGroup(dbc, parent);
            if (!parentGroup.isRole() && !CmsOrganizationalUnit.getParentFqn(parent)
                    .equals(CmsOrganizationalUnit.getParentFqn(name))) {
                throw new CmsDataAccessException(Messages.get().container(
                        Messages.ERR_PARENT_GROUP_MUST_BE_IN_SAME_OU_3, CmsOrganizationalUnit.getSimpleName(name),
                        CmsOrganizationalUnit.getParentFqn(name), parent));
            }
        }

        // create the group
        CmsGroup group = getUserDriver(dbc).createGroup(dbc, id, name, description, flags, parent);

        // if the group is in fact a role, initialize it
        if (group.isVirtual()) {
            // get all users that have the given role
            String groupname = CmsRole.valueOf(group).getGroupName();
            Iterator<CmsUser> it = getUsersOfGroup(dbc, groupname, true, false, true).iterator();
            while (it.hasNext()) {
                CmsUser user = it.next();
                // put them in the new group
                addUserToGroup(dbc, user.getName(), group.getName(), true);
            }
        }

        // put it into the cache
        m_monitor.cacheGroup(group);

        if (!dbc.getProjectId().isNullUUID()) {
            // group modified event is not needed
            return group;
        }
        // fire group modified event
        Map<String, Object> eventData = new HashMap<String, Object>();
        eventData.put(I_CmsEventListener.KEY_GROUP_NAME, group.getName());
        eventData.put(I_CmsEventListener.KEY_GROUP_ID, group.getId().toString());
        eventData.put(I_CmsEventListener.KEY_USER_ACTION, I_CmsEventListener.VALUE_GROUP_MODIFIED_ACTION_CREATE);
        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_GROUP_MODIFIED, eventData));

        // return it
        return group;
    }

    /**
     * Creates a new organizational unit.<p>
     * 
     * @param dbc the current db context
     * @param ouFqn the fully qualified name of the new organizational unit
     * @param description the description of the new organizational unit
     * @param flags the flags for the new organizational unit
     * @param resource the first associated resource
     *
     * @return a <code>{@link CmsOrganizationalUnit}</code> object representing 
     *          the newly created organizational unit
     *
     * @throws CmsException if operation was not successful
     * 
     * @see org.opencms.security.CmsOrgUnitManager#createOrganizationalUnit(CmsObject, String, String, int, String)
     */
    public CmsOrganizationalUnit createOrganizationalUnit(CmsDbContext dbc, String ouFqn, String description,
            int flags, CmsResource resource) throws CmsException {

        // normal case
        CmsOrganizationalUnit parent = readOrganizationalUnit(dbc, CmsOrganizationalUnit.getParentFqn(ouFqn));
        String name = CmsOrganizationalUnit.getSimpleName(ouFqn);
        if (name.endsWith(CmsOrganizationalUnit.SEPARATOR)) {
            name = name.substring(0, name.length() - 1);
        }

        // check the name
        CmsResource.checkResourceName(name);

        // trim the name
        name = name.trim();

        // check the description
        if (CmsStringUtil.isEmptyOrWhitespaceOnly(description)) {
            throw new CmsIllegalArgumentException(
                    Messages.get().container(Messages.ERR_BAD_OU_DESCRIPTION_EMPTY_0));
        }

        // create the organizational unit
        CmsOrganizationalUnit orgUnit = getUserDriver(dbc).createOrganizationalUnit(dbc, name, description, flags,
                parent, resource != null ? resource.getRootPath() : null);
        // put the new created org unit into the cache
        m_monitor.cacheOrgUnit(orgUnit);

        // flush relevant caches
        m_monitor.clearPrincipalsCache();
        m_monitor.flushCache(CmsMemoryMonitor.CacheType.PROPERTY, CmsMemoryMonitor.CacheType.PROPERTY_LIST);

        // create a publish list for the 'virtual' publish event
        CmsResource ouRes = readResource(dbc, CmsUserDriver.ORGUNIT_BASE_FOLDER + orgUnit.getName(),
                CmsResourceFilter.DEFAULT);
        CmsPublishList pl = new CmsPublishList(ouRes, false);
        pl.add(ouRes, false);

        getProjectDriver(dbc).writePublishHistory(dbc, pl.getPublishHistoryId(),
                new CmsPublishedResource(ouRes, -1, CmsResourceState.STATE_NEW));

        // fire the 'virtual' publish event
        Map<String, Object> eventData = new HashMap<String, Object>();
        eventData.put(I_CmsEventListener.KEY_PUBLISHID, pl.getPublishHistoryId().toString());
        eventData.put(I_CmsEventListener.KEY_PROJECTID, dbc.currentProject().getUuid());
        eventData.put(I_CmsEventListener.KEY_DBCONTEXT, dbc);
        CmsEvent afterPublishEvent = new CmsEvent(I_CmsEventListener.EVENT_PUBLISH_PROJECT, eventData);
        OpenCms.fireCmsEvent(afterPublishEvent);

        if (!dbc.getProjectId().isNullUUID()) {
            // OU modified event is not needed
            return orgUnit;
        }

        // fire OU modified event
        Map<String, Object> event2Data = new HashMap<String, Object>();
        event2Data.put(I_CmsEventListener.KEY_OU_NAME, orgUnit.getName());
        event2Data.put(I_CmsEventListener.KEY_OU_ID, orgUnit.getId().toString());
        event2Data.put(I_CmsEventListener.KEY_USER_ACTION, I_CmsEventListener.VALUE_OU_MODIFIED_ACTION_CREATE);
        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_OU_MODIFIED, event2Data));

        // return it
        return orgUnit;
    }

    /**
     * Creates a project.<p>
     *
     * @param dbc the current database context
     * @param name the name of the project to create
     * @param description the description of the project
     * @param groupname the project user group to be set
     * @param managergroupname the project manager group to be set
     * @param projecttype the type of the project
     * 
     * @return the created project
     * 
     * @throws CmsIllegalArgumentException if the chosen <code>name</code> is already used 
     *         by the online project, or if the name is not valid
     * @throws CmsException if something goes wrong
     */
    public CmsProject createProject(CmsDbContext dbc, String name, String description, String groupname,
            String managergroupname, CmsProject.CmsProjectType projecttype)
            throws CmsIllegalArgumentException, CmsException {

        if (CmsProject.ONLINE_PROJECT_NAME.equals(name)) {
            throw new CmsIllegalArgumentException(Messages.get()
                    .container(Messages.ERR_CREATE_PROJECT_ONLINE_PROJECT_NAME_1, CmsProject.ONLINE_PROJECT_NAME));
        }
        // check the name
        CmsProject.checkProjectName(CmsOrganizationalUnit.getSimpleName(name));
        // check the ou
        readOrganizationalUnit(dbc, CmsOrganizationalUnit.getParentFqn(name));
        // read the needed groups from the cms
        CmsGroup group = readGroup(dbc, groupname);
        CmsGroup managergroup = readGroup(dbc, managergroupname);

        return getProjectDriver(dbc).createProject(dbc, new CmsUUID(), dbc.currentUser(), group, managergroup, name,
                description, CmsProject.PROJECT_FLAG_NONE, projecttype);
    }

    /**
     * Creates a property definition.<p>
     *
     * Property definitions are valid for all resource types.<p>
     * 
     * @param dbc the current database context
     * @param name the name of the property definition to create
     * 
     * @return the created property definition
     * 
     * @throws CmsException if something goes wrong
     */
    public CmsPropertyDefinition createPropertyDefinition(CmsDbContext dbc, String name) throws CmsException {

        CmsPropertyDefinition propertyDefinition = null;

        name = name.trim();
        // validate the property name
        CmsPropertyDefinition.checkPropertyName(name);
        // TODO: make the type a parameter
        try {
            try {
                propertyDefinition = getVfsDriver(dbc).readPropertyDefinition(dbc, name,
                        dbc.currentProject().getUuid());
            } catch (CmsException e) {
                propertyDefinition = getVfsDriver(dbc).createPropertyDefinition(dbc, dbc.currentProject().getUuid(),
                        name, CmsPropertyDefinition.TYPE_NORMAL);
            }

            try {
                getVfsDriver(dbc).readPropertyDefinition(dbc, name, CmsProject.ONLINE_PROJECT_ID);
            } catch (CmsException e) {
                getVfsDriver(dbc).createPropertyDefinition(dbc, CmsProject.ONLINE_PROJECT_ID, name,
                        CmsPropertyDefinition.TYPE_NORMAL);
            }

            try {
                getHistoryDriver(dbc).readPropertyDefinition(dbc, name);
            } catch (CmsException e) {
                getHistoryDriver(dbc).createPropertyDefinition(dbc, name, CmsPropertyDefinition.TYPE_NORMAL);
            }
        } finally {

            // fire an event that a property of a resource has been deleted
            OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_PROPERTY_DEFINITION_CREATED,
                    Collections.<String, Object>singletonMap("propertyDefinition", propertyDefinition)));

        }

        return propertyDefinition;
    }

    /**
     * Creates a new publish job.<p>
     * 
     * @param dbc the current database context
     * @param publishJob the publish job to create
     * 
     * @throws CmsException if something goes wrong
     */
    public void createPublishJob(CmsDbContext dbc, CmsPublishJobInfoBean publishJob) throws CmsException {

        getProjectDriver(dbc).createPublishJob(dbc, publishJob);
    }

    /**
     * Creates a new resource with the provided content and properties.<p>
     * 
     * The <code>content</code> parameter may be <code>null</code> if the resource id 
     * already exists. If so, the created resource will be a sibling of the existing 
     * resource, the existing content will remain unchanged.<p>
     * 
     * This is used during file import for import of siblings as the 
     * <code>manifest.xml</code> only contains one binary copy per file.<p>
     *  
     * If the resource id exists but the <code>content</code> is not <code>null</code>,
     * the created resource will be made a sibling of the existing resource,
     * and both will share the new content.<p>
     * 
     * @param dbc the current database context
     * @param resourcePath the name of the resource to create (full path)
     * @param resource the new resource to create
     * @param content the content for the new resource
     * @param properties the properties for the new resource
     * @param importCase if <code>true</code>, signals that this operation is done while 
     *                      importing resource, causing different lock behavior and 
     *                      potential "lost and found" usage
     * 
     * @return the created resource
     * 
     * @throws CmsException if something goes wrong
     */
    public synchronized CmsResource createResource(CmsDbContext dbc, String resourcePath, CmsResource resource,
            byte[] content, List<CmsProperty> properties, boolean importCase) throws CmsException {

        CmsResource newResource = null;
        if (resource.isFolder()) {
            resourcePath = CmsFileUtil.addTrailingSeparator(resourcePath);
        }

        try {
            // need to provide the parent folder id for resource creation
            String parentFolderName = CmsResource.getParentFolder(resourcePath);
            CmsResource parentFolder = readFolder(dbc, parentFolderName, CmsResourceFilter.IGNORE_EXPIRATION);

            CmsLock parentLock = getLock(dbc, parentFolder);
            // it is not allowed to create a resource in a folder locked by other user
            if (!parentLock.isUnlocked() && !parentLock.isOwnedBy(dbc.currentUser())) {
                // one exception is if the admin user tries to create a temporary resource
                if (!CmsResource.getName(resourcePath).startsWith(TEMP_FILE_PREFIX)
                        || !m_securityManager.hasRole(dbc, dbc.currentUser(), CmsRole.ROOT_ADMIN)) {
                    throw new CmsLockException(Messages.get().container(Messages.ERR_CREATE_RESOURCE_PARENT_LOCK_1,
                            dbc.removeSiteRoot(resourcePath)));
                }
            }
            if (CmsResourceTypeJsp.isJsp(resource)) {
                // security check when trying to create a new jsp file
                m_securityManager.checkRoleForResource(dbc, CmsRole.DEVELOPER, parentFolder);
            }

            // check import configuration of "lost and found" folder
            boolean useLostAndFound = importCase && !OpenCms.getImportExportManager().overwriteCollidingResources();

            // check if the resource already exists by name
            CmsResource currentResourceByName = null;
            try {
                currentResourceByName = readResource(dbc, resourcePath, CmsResourceFilter.ALL);
            } catch (CmsVfsResourceNotFoundException e) {
                // if the resource does exist, we have to check the id later to decide what to do
            }

            // check if the resource already exists by id
            try {
                CmsResource currentResourceById = readResource(dbc, resource.getStructureId(),
                        CmsResourceFilter.ALL);
                // it is not allowed to import resources when there is already a resource with the same id but different path 
                if (!currentResourceById.getRootPath().equals(resourcePath)) {
                    throw new CmsVfsResourceAlreadyExistsException(Messages.get().container(
                            Messages.ERR_RESOURCE_WITH_ID_ALREADY_EXISTS_3, dbc.removeSiteRoot(resourcePath),
                            dbc.removeSiteRoot(currentResourceById.getRootPath()),
                            currentResourceById.getStructureId()));
                }
            } catch (CmsVfsResourceNotFoundException e) {
                // if the resource does exist, we have to check the id later to decide what to do
            }

            // check the permissions
            if (currentResourceByName == null) {
                // resource does not exist - check parent folder
                m_securityManager.checkPermissions(dbc, parentFolder, CmsPermissionSet.ACCESS_WRITE, false,
                        CmsResourceFilter.IGNORE_EXPIRATION);
            } else {
                // resource already exists - check existing resource              
                m_securityManager.checkPermissions(dbc, currentResourceByName, CmsPermissionSet.ACCESS_WRITE,
                        !importCase, CmsResourceFilter.ALL);
            }

            // now look for the resource by name
            if (currentResourceByName != null) {
                boolean overwrite = true;
                if (currentResourceByName.getState().isDeleted()) {
                    if (!currentResourceByName.isFolder()) {
                        // if a non-folder resource was deleted it's treated like a new resource
                        overwrite = false;
                    }
                } else {
                    if (!importCase) {
                        // direct "overwrite" of a resource is possible only during import, 
                        // or if the resource has been deleted
                        throw new CmsVfsResourceAlreadyExistsException(org.opencms.db.generic.Messages.get()
                                .container(org.opencms.db.generic.Messages.ERR_RESOURCE_WITH_NAME_ALREADY_EXISTS_1,
                                        dbc.removeSiteRoot(resource.getRootPath())));
                    }
                    // the resource already exists
                    if (!resource.isFolder() && useLostAndFound
                            && (!currentResourceByName.getResourceId().equals(resource.getResourceId()))) {
                        // semantic change: the current resource is moved to L&F and the imported resource will overwrite the old one                
                        // will leave the resource with state deleted, 
                        // but it does not matter, since the state will be set later again
                        moveToLostAndFound(dbc, currentResourceByName, false);
                    }
                }
                if (!overwrite) {
                    // lock the resource, will throw an exception if not lockable
                    lockResource(dbc, currentResourceByName, CmsLockType.EXCLUSIVE);

                    // trigger createResource instead of writeResource
                    currentResourceByName = null;
                }
            }
            // if null, create new resource, if not null write resource
            CmsResource overwrittenResource = currentResourceByName;

            // extract the name (without path)
            String targetName = CmsResource.getName(resourcePath);

            int contentLength;

            // modify target name and content length in case of folder creation
            if (resource.isFolder()) {
                // folders never have any content
                contentLength = -1;
                // must cut of trailing '/' for folder creation (or name check fails)
                if (CmsResource.isFolder(targetName)) {
                    targetName = targetName.substring(0, targetName.length() - 1);
                }
            } else {
                // otherwise ensure content and content length are set correctly
                if (content != null) {
                    // if a content is provided, in each case the length is the length of this content
                    contentLength = content.length;
                } else if (overwrittenResource != null) {
                    // we have no content, but an already existing resource - length remains unchanged
                    contentLength = overwrittenResource.getLength();
                } else {
                    // we have no content - length is used as set in the resource
                    contentLength = resource.getLength();
                }
            }

            // check if the target name is valid (forbidden chars etc.), 
            // if not throw an exception
            // must do this here since targetName is modified in folder case (see above)
            CmsResource.checkResourceName(targetName);

            // set structure and resource ids as given
            CmsUUID structureId = resource.getStructureId();
            CmsUUID resourceId = resource.getResourceId();

            // decide which structure id to use
            if (overwrittenResource != null) {
                // resource exists, re-use existing ids
                structureId = overwrittenResource.getStructureId();
            }
            if (structureId.isNullUUID()) {
                // need a new structure id
                structureId = new CmsUUID();
            }

            // decide which resource id to use
            if (overwrittenResource != null) {
                // if we are overwriting we have to assure the resource id is the same
                resourceId = overwrittenResource.getResourceId();
            }
            if (resourceId.isNullUUID()) {
                // need a new resource id
                resourceId = new CmsUUID();
            }

            try {
                // check online resource
                CmsResource onlineResource = getVfsDriver(dbc).readResource(dbc, CmsProject.ONLINE_PROJECT_ID,
                        resourcePath, true);
                // only allow to overwrite with different id if importing (createResource will set the right id)
                try {
                    CmsResource offlineResource = getVfsDriver(dbc).readResource(dbc,
                            dbc.currentProject().getUuid(), onlineResource.getStructureId(), true);
                    if (!offlineResource.getRootPath().equals(onlineResource.getRootPath())) {
                        throw new CmsVfsOnlineResourceAlreadyExistsException(Messages.get().container(
                                Messages.ERR_ONLINE_RESOURCE_EXISTS_2, dbc.removeSiteRoot(resourcePath),
                                dbc.removeSiteRoot(offlineResource.getRootPath())));
                    }
                } catch (CmsVfsResourceNotFoundException e) {
                    // there is no problem for now
                    // but should never happen
                    if (LOG.isErrorEnabled()) {
                        LOG.error(e.getLocalizedMessage(), e);
                    }
                }
            } catch (CmsVfsResourceNotFoundException e) {
                // ok, there is no online entry to worry about
            }

            // now create a resource object with all informations
            newResource = new CmsResource(structureId, resourceId, resourcePath, resource.getTypeId(),
                    resource.isFolder(), resource.getFlags(), dbc.currentProject().getUuid(), resource.getState(),
                    resource.getDateCreated(), resource.getUserCreated(), resource.getDateLastModified(),
                    resource.getUserLastModified(), resource.getDateReleased(), resource.getDateExpired(), 1,
                    contentLength, resource.getDateContent(), resource.getVersion()); // version number does not matter since it will be computed later

            // ensure date is updated only if required
            if (resource.isTouched()) {
                // this will trigger the internal "is touched" state on the new resource
                newResource.setDateLastModified(resource.getDateLastModified());
            }

            if (resource.isFile()) {
                // check if a sibling to the imported resource lies in a marked site
                if (labelResource(dbc, resource, resourcePath, 2)) {
                    int flags = resource.getFlags();
                    flags |= CmsResource.FLAG_LABELED;
                    resource.setFlags(flags);
                }
                // ensure siblings don't overwrite existing resource records
                if (content == null) {
                    newResource.setState(CmsResource.STATE_KEEP);
                }
            }

            // delete all relations for the resource, before writing the content
            getVfsDriver(dbc).deleteRelations(dbc, dbc.currentProject().getUuid(), newResource,
                    CmsRelationFilter.TARGETS);
            if (overwrittenResource == null) {
                CmsLock lock = getLock(dbc, newResource);
                if (lock.getEditionLock().isExclusive()) {
                    unlockResource(dbc, newResource, true, false);
                }
                // resource does not exist.
                newResource = getVfsDriver(dbc).createResource(dbc, dbc.currentProject().getUuid(), newResource,
                        content);
            } else {
                // resource already exists. 
                // probably the resource is a merged page file that gets overwritten during import, or it gets 
                // overwritten by a copy operation. if so, the structure & resource state are not modified to changed.
                int updateStates = (overwrittenResource.getState().isNew() ? CmsDriverManager.NOTHING_CHANGED
                        : CmsDriverManager.UPDATE_ALL);
                getVfsDriver(dbc).writeResource(dbc, dbc.currentProject().getUuid(), newResource, updateStates);

                if ((content != null) && resource.isFile()) {
                    // also update file content if required                    
                    getVfsDriver(dbc).writeContent(dbc, newResource.getResourceId(), content);
                }
            }

            // write the properties (internal operation, no events or duplicate permission checks)
            writePropertyObjects(dbc, newResource, properties, false);

            // lock the created resource
            try {
                // if it is locked by another user (copied or moved resource) this lock should be preserved and 
                // the exception is OK: locks on created resources are a slave feature to original locks 
                lockResource(dbc, newResource, CmsLockType.EXCLUSIVE);
            } catch (CmsLockException cle) {
                if (LOG.isDebugEnabled()) {
                    LOG.debug(Messages.get().getBundle().key(Messages.ERR_CREATE_RESOURCE_LOCK_1,
                            new Object[] { dbc.removeSiteRoot(newResource.getRootPath()) }));
                }
            }

            if (!importCase) {
                log(dbc, new CmsLogEntry(dbc, newResource.getStructureId(), CmsLogEntryType.RESOURCE_CREATED,
                        new String[] { resource.getRootPath() }), false);
            } else {
                log(dbc, new CmsLogEntry(dbc, newResource.getStructureId(), CmsLogEntryType.RESOURCE_IMPORTED,
                        new String[] { resource.getRootPath() }), false);
            }
        } finally {
            // clear the internal caches
            m_monitor.clearAccessControlListCache();
            m_monitor.flushCache(CmsMemoryMonitor.CacheType.PROPERTY, CmsMemoryMonitor.CacheType.PROPERTY_LIST);

            if (newResource != null) {
                // fire an event that a new resource has been created
                OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_CREATED,
                        Collections.<String, Object>singletonMap(I_CmsEventListener.KEY_RESOURCE, newResource)));
            }
        }
        return newResource;
    }

    /**
     * Creates a new resource of the given resource type
     * with the provided content and properties.<p>
     * 
     * If the provided content is null and the resource is not a folder,
     * the content will be set to an empty byte array.<p>  
     * 
     * @param dbc the current database context
     * @param resourcename the name of the resource to create (full path)
     * @param type the type of the resource to create
     * @param content the content for the new resource
     * @param properties the properties for the new resource
     * 
     * @return the created resource
     * 
     * @throws CmsException if something goes wrong
     * @throws CmsIllegalArgumentException if the <code>resourcename</code> argument is null or of length 0
     * 
     * @see CmsObject#createResource(String, int, byte[], List)
     * @see CmsObject#createResource(String, int)
     * @see I_CmsResourceType#createResource(CmsObject, CmsSecurityManager, String, byte[], List)
     */
    public CmsResource createResource(CmsDbContext dbc, String resourcename, int type, byte[] content,
            List<CmsProperty> properties) throws CmsException, CmsIllegalArgumentException {

        String targetName = resourcename;

        if (content == null) {
            // name based resource creation MUST have a content
            content = new byte[0];
        }
        int size;

        if (CmsFolder.isFolderType(type)) {
            // must cut of trailing '/' for folder creation
            if (CmsResource.isFolder(targetName)) {
                targetName = targetName.substring(0, targetName.length() - 1);
            }
            size = -1;
        } else {
            size = content.length;
        }

        // create a new resource
        CmsResource newResource = new CmsResource(CmsUUID.getNullUUID(), // uuids will be "corrected" later
                CmsUUID.getNullUUID(), targetName, type, CmsFolder.isFolderType(type), 0,
                dbc.currentProject().getUuid(), CmsResource.STATE_NEW, 0, dbc.currentUser().getId(), 0,
                dbc.currentUser().getId(), CmsResource.DATE_RELEASED_DEFAULT, CmsResource.DATE_EXPIRED_DEFAULT, 1,
                size, 0, // version number does not matter since it will be computed later
                0); // content time will be corrected later

        return createResource(dbc, targetName, newResource, content, properties, false);
    }

    /**
     * Creates a new sibling of the source resource.<p>
     * 
     * @param dbc the current database context
     * @param source the resource to create a sibling for
     * @param destination the name of the sibling to create with complete path
     * @param properties the individual properties for the new sibling
     * 
     * @return the new created sibling
     * 
     * @throws CmsException if something goes wrong
     * 
     * @see CmsObject#createSibling(String, String, List)
     * @see I_CmsResourceType#createSibling(CmsObject, CmsSecurityManager, CmsResource, String, List)
     */
    public CmsResource createSibling(CmsDbContext dbc, CmsResource source, String destination,
            List<CmsProperty> properties) throws CmsException {

        if (source.isFolder()) {
            throw new CmsVfsException(Messages.get().container(Messages.ERR_VFS_FOLDERS_DONT_SUPPORT_SIBLINGS_0));
        }

        // determine destination folder and resource name        
        String destinationFoldername = CmsResource.getParentFolder(destination);

        // read the destination folder (will also check read permissions)
        CmsFolder destinationFolder = readFolder(dbc, destinationFoldername, CmsResourceFilter.IGNORE_EXPIRATION);

        // no further permission check required here, will be done in createResource()

        // check the resource flags
        int flags = source.getFlags();
        if (labelResource(dbc, source, destination, 1)) {
            // set "labeled" link flag for new resource
            flags |= CmsResource.FLAG_LABELED;
        }

        // create the new resource        
        CmsResource newResource = new CmsResource(new CmsUUID(), source.getResourceId(), destination,
                source.getTypeId(), source.isFolder(), flags, dbc.currentProject().getUuid(),
                CmsResource.STATE_KEEP, source.getDateCreated(), // ensures current resource record remains untouched 
                source.getUserCreated(), source.getDateLastModified(), source.getUserLastModified(),
                source.getDateReleased(), source.getDateExpired(), source.getSiblingCount() + 1, source.getLength(),
                source.getDateContent(), source.getVersion()); // version number does not matter since it will be computed later

        // trigger "is touched" state on resource (will ensure modification date is kept unchanged)
        newResource.setDateLastModified(newResource.getDateLastModified());

        log(dbc, new CmsLogEntry(dbc, newResource.getStructureId(), CmsLogEntryType.RESOURCE_CLONED,
                new String[] { newResource.getRootPath() }), false);
        // create the resource (null content signals creation of sibling)
        newResource = createResource(dbc, destination, newResource, null, properties, false);

        // copy relations
        copyRelations(dbc, source, newResource);

        // clear the caches
        m_monitor.clearAccessControlListCache();

        List<CmsResource> modifiedResources = new ArrayList<CmsResource>();
        modifiedResources.add(source);
        modifiedResources.add(newResource);
        modifiedResources.add(destinationFolder);
        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCES_AND_PROPERTIES_MODIFIED,
                Collections.<String, Object>singletonMap(I_CmsEventListener.KEY_RESOURCES, modifiedResources)));

        return newResource;
    }

    /**
     * Creates the project for the temporary workplace files.<p>
     *
     * @param dbc the current database context
     * 
     * @return the created project for the temporary workplace files
     * 
     * @throws CmsException if something goes wrong
     */
    public CmsProject createTempfileProject(CmsDbContext dbc) throws CmsException {

        // read the needed groups from the cms
        CmsGroup projectUserGroup = readGroup(dbc, dbc.currentProject().getGroupId());
        CmsGroup projectManagerGroup = readGroup(dbc, dbc.currentProject().getManagerGroupId());

        CmsProject tempProject = getProjectDriver(dbc).createProject(dbc, new CmsUUID(), dbc.currentUser(),
                projectUserGroup, projectManagerGroup, I_CmsProjectDriver.TEMP_FILE_PROJECT_NAME,
                Messages.get().getBundle(dbc.getRequestContext().getLocale())
                        .key(Messages.GUI_WORKPLACE_TEMPFILE_PROJECT_DESC_0),
                CmsProject.PROJECT_FLAG_HIDDEN, CmsProject.PROJECT_TYPE_NORMAL);
        getProjectDriver(dbc).createProjectResource(dbc, tempProject.getUuid(), "/");

        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_PROJECT_MODIFIED,
                Collections.<String, Object>singletonMap("project", tempProject)));

        return tempProject;
    }

    /**
     * Creates a new user.<p>
     *
     * @param dbc the current database context
     * @param name the name for the new user
     * @param password the password for the new user
     * @param description the description for the new user
     * @param additionalInfos the additional infos for the user
     *
     * @return the created user
     * 
     * @see CmsObject#createUser(String, String, String, Map)
     * 
     * @throws CmsException if something goes wrong
     * @throws CmsIllegalArgumentException if the name for the user is not valid
     */
    public CmsUser createUser(CmsDbContext dbc, String name, String password, String description,
            Map<String, Object> additionalInfos) throws CmsException, CmsIllegalArgumentException {

        // no space before or after the name
        name = name.trim();
        // check the user name
        String userName = CmsOrganizationalUnit.getSimpleName(name);
        OpenCms.getValidationHandler().checkUserName(userName);
        if (CmsStringUtil.isEmptyOrWhitespaceOnly(userName)) {
            throw new CmsIllegalArgumentException(Messages.get().container(Messages.ERR_BAD_USER_1, userName));
        }
        // check the ou
        CmsOrganizationalUnit ou = readOrganizationalUnit(dbc, CmsOrganizationalUnit.getParentFqn(name));
        // check the password
        validatePassword(password);

        Map<String, Object> info = new HashMap<String, Object>();
        if (additionalInfos != null) {
            info.putAll(additionalInfos);
        }
        if (description != null) {
            info.put(CmsUserSettings.ADDITIONAL_INFO_DESCRIPTION, description);
        }
        int flags = 0;
        if (ou.hasFlagWebuser()) {
            flags += I_CmsPrincipal.FLAG_USER_WEBUSER;
        }
        CmsUser user = getUserDriver(dbc).createUser(dbc, new CmsUUID(), name,
                OpenCms.getPasswordHandler().digest(password), " ", " ", " ", 0,
                I_CmsPrincipal.FLAG_ENABLED + flags, 0, info);

        if (!dbc.getProjectId().isNullUUID()) {
            // user modified event is not needed
            return user;
        }
        // fire user modified event
        Map<String, Object> eventData = new HashMap<String, Object>();
        eventData.put(I_CmsEventListener.KEY_USER_ID, user.getId().toString());
        eventData.put(I_CmsEventListener.KEY_USER_ACTION,
                I_CmsEventListener.VALUE_USER_MODIFIED_ACTION_CREATE_USER);
        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_USER_MODIFIED, eventData));
        return user;
    }

    /**
     * Deletes all property values of a file or folder.<p>
     * 
     * If there are no other siblings than the specified resource,
     * both the structure and resource property values get deleted.
     * If the specified resource has siblings, only the structure
     * property values get deleted.<p>
     * 
     * @param dbc the current database context
     * @param resourcename the name of the resource for which all properties should be deleted
     * 
     * @throws CmsException if operation was not successful
     */
    public void deleteAllProperties(CmsDbContext dbc, String resourcename) throws CmsException {

        CmsResource resource = null;
        List<CmsResource> resources = new ArrayList<CmsResource>();

        try {
            // read the resource
            resource = readResource(dbc, resourcename, CmsResourceFilter.IGNORE_EXPIRATION);

            // check the security
            m_securityManager.checkPermissions(dbc, resource, CmsPermissionSet.ACCESS_WRITE, false,
                    CmsResourceFilter.ALL);

            // delete the property values
            if (resource.getSiblingCount() > 1) {
                // the resource has siblings- delete only the (structure) properties of this sibling
                getVfsDriver(dbc).deletePropertyObjects(dbc, dbc.currentProject().getUuid(), resource,
                        CmsProperty.DELETE_OPTION_DELETE_STRUCTURE_VALUES);
                resources.addAll(readSiblings(dbc, resource, CmsResourceFilter.ALL));

            } else {
                // the resource has no other siblings- delete all (structure+resource) properties
                getVfsDriver(dbc).deletePropertyObjects(dbc, dbc.currentProject().getUuid(), resource,
                        CmsProperty.DELETE_OPTION_DELETE_STRUCTURE_AND_RESOURCE_VALUES);
                resources.add(resource);
            }
        } finally {
            // clear the driver manager cache
            m_monitor.flushCache(CmsMemoryMonitor.CacheType.PROPERTY, CmsMemoryMonitor.CacheType.PROPERTY_LIST);

            // fire an event that all properties of a resource have been deleted
            OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCES_AND_PROPERTIES_MODIFIED,
                    Collections.<String, Object>singletonMap(I_CmsEventListener.KEY_RESOURCES, resources)));
        }
    }

    /**
     * Deletes all entries in the published resource table.<p>
     * 
     * @param dbc the current database context
     * @param linkType the type of resource deleted (0= non-paramter, 1=parameter)
     * 
     * @throws CmsException if something goes wrong
     */
    public void deleteAllStaticExportPublishedResources(CmsDbContext dbc, int linkType) throws CmsException {

        getProjectDriver(dbc).deleteAllStaticExportPublishedResources(dbc, linkType);
    }

    /**
     * Deletes a group, where all permissions, users and children of the group
     * are transfered to a replacement group.<p>
     * 
     * @param dbc the current request context
     * @param group the id of the group to be deleted
     * @param replacementId the id of the group to be transfered, can be <code>null</code>
     *
     * @throws CmsException if operation was not successful
     * @throws CmsDataAccessException if group to be deleted contains user
     */
    public void deleteGroup(CmsDbContext dbc, CmsGroup group, CmsUUID replacementId)
            throws CmsDataAccessException, CmsException {

        CmsGroup replacementGroup = null;
        if (replacementId != null) {
            replacementGroup = readGroup(dbc, replacementId);
        }
        // get all child groups of the group
        List<CmsGroup> children = getChildren(dbc, group, false);
        // get all users in this group
        List<CmsUser> users = getUsersOfGroup(dbc, group.getName(), true, true, group.isRole());
        // get online project
        CmsProject onlineProject = readProject(dbc, CmsProject.ONLINE_PROJECT_ID);
        if (replacementGroup == null) {
            // remove users
            Iterator<CmsUser> itUsers = users.iterator();
            while (itUsers.hasNext()) {
                CmsUser user = itUsers.next();
                if (userInGroup(dbc, user.getName(), group.getName(), group.isRole())) {
                    removeUserFromGroup(dbc, user.getName(), group.getName(), group.isRole());
                }
            }
            // transfer children to grandfather if possible
            CmsUUID parentId = group.getParentId();
            if (parentId == null) {
                parentId = CmsUUID.getNullUUID();
            }
            Iterator<CmsGroup> itChildren = children.iterator();
            while (itChildren.hasNext()) {
                CmsGroup child = itChildren.next();
                child.setParentId(parentId);
                writeGroup(dbc, child);
            }
        } else {
            // move children
            Iterator<CmsGroup> itChildren = children.iterator();
            while (itChildren.hasNext()) {
                CmsGroup child = itChildren.next();
                child.setParentId(replacementId);
                writeGroup(dbc, child);
            }
            // move users
            Iterator<CmsUser> itUsers = users.iterator();
            while (itUsers.hasNext()) {
                CmsUser user = itUsers.next();
                addUserToGroup(dbc, user.getName(), replacementGroup.getName(), group.isRole());
                removeUserFromGroup(dbc, user.getName(), group.getName(), group.isRole());
            }
            // transfer for offline
            transferPrincipalResources(dbc, dbc.currentProject(), group.getId(), replacementId, true);
            // transfer for online
            transferPrincipalResources(dbc, onlineProject, group.getId(), replacementId, true);
        }
        // remove the group
        getUserDriver(dbc).removeAccessControlEntriesForPrincipal(dbc, dbc.currentProject(), onlineProject,
                group.getId());
        getUserDriver(dbc).deleteGroup(dbc, group.getName());
        // backup the group
        getHistoryDriver(dbc).writePrincipal(dbc, group);
        if (OpenCms.getSubscriptionManager().isEnabled()) {
            // delete all subscribed resources for group
            unsubscribeAllResourcesFor(dbc, OpenCms.getSubscriptionManager().getPoolName(), group);
        }

        // clear the relevant caches
        m_monitor.uncacheGroup(group);
        m_monitor.flushCache(CmsMemoryMonitor.CacheType.USERGROUPS, CmsMemoryMonitor.CacheType.USER_LIST,
                CmsMemoryMonitor.CacheType.ACL);

        if (!dbc.getProjectId().isNullUUID()) {
            // group modified event is not needed
            return;
        }
        // fire group modified event
        Map<String, Object> eventData = new HashMap<String, Object>();
        eventData.put(I_CmsEventListener.KEY_GROUP_ID, group.getId().toString());
        eventData.put(I_CmsEventListener.KEY_GROUP_NAME, group.getName());
        eventData.put(I_CmsEventListener.KEY_USER_ACTION, I_CmsEventListener.VALUE_GROUP_MODIFIED_ACTION_DELETE);
        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_GROUP_MODIFIED, eventData));
    }

    /**
     * Deletes the versions from the history tables, keeping the given number of versions per resource.<p>
     * 
     * if the <code>cleanUp</code> option is set, additionally versions of deleted resources will be removed.<p>
     * 
     * @param dbc the current database context
     * @param versionsToKeep number of versions to keep, is ignored if negative 
     * @param versionsDeleted number of versions to keep for deleted resources, is ignored if negative
     * @param timeDeleted deleted resources older than this will also be deleted, is ignored if negative
     * @param report the report for output logging
     * 
     * @throws CmsException if operation was not successful
     */
    public void deleteHistoricalVersions(CmsDbContext dbc, int versionsToKeep, int versionsDeleted,
            long timeDeleted, I_CmsReport report) throws CmsException {

        report.println(Messages.get().container(Messages.RPT_START_DELETE_VERSIONS_0), I_CmsReport.FORMAT_HEADLINE);
        if (versionsToKeep >= 0) {
            report.println(
                    Messages.get().container(Messages.RPT_START_DELETE_ACT_VERSIONS_1, new Integer(versionsToKeep)),
                    I_CmsReport.FORMAT_HEADLINE);

            List<I_CmsHistoryResource> resources = getHistoryDriver(dbc).getAllNotDeletedEntries(dbc);
            if (resources.isEmpty()) {
                report.println(Messages.get().container(Messages.RPT_DELETE_NOTHING_0), I_CmsReport.FORMAT_OK);
            }
            int n = resources.size();
            int m = 1;
            Iterator<I_CmsHistoryResource> itResources = resources.iterator();
            while (itResources.hasNext()) {
                I_CmsHistoryResource histResource = itResources.next();

                report.print(
                        org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_SUCCESSION_2,
                                String.valueOf(m), String.valueOf(n)),
                        I_CmsReport.FORMAT_NOTE);
                report.print(org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_ARGUMENT_1,
                        dbc.removeSiteRoot(histResource.getRootPath())));
                report.print(org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_DOTS_0));

                try {
                    int deleted = getHistoryDriver(dbc).deleteEntries(dbc, histResource, versionsToKeep, -1);

                    report.print(Messages.get().container(Messages.RPT_VERSION_DELETING_1, new Integer(deleted)),
                            I_CmsReport.FORMAT_NOTE);
                    report.print(
                            org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_DOTS_0));
                    report.println(
                            org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_OK_0),
                            I_CmsReport.FORMAT_OK);
                } catch (CmsDataAccessException e) {
                    report.println(
                            org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_ERROR_0),
                            I_CmsReport.FORMAT_ERROR);

                    if (LOG.isDebugEnabled()) {
                        LOG.debug(e.getLocalizedMessage(), e);
                    }
                }

                m++;
            }

            report.println(Messages.get().container(Messages.RPT_END_DELETE_ACT_VERSIONS_0),
                    I_CmsReport.FORMAT_HEADLINE);
        }
        if ((versionsDeleted >= 0) || (timeDeleted >= 0)) {
            if (timeDeleted >= 0) {
                report.println(Messages.get().container(Messages.RPT_START_DELETE_DEL_VERSIONS_2,
                        new Integer(versionsDeleted), new Date(timeDeleted)), I_CmsReport.FORMAT_HEADLINE);
            } else {
                report.println(Messages.get().container(Messages.RPT_START_DELETE_DEL_VERSIONS_1,
                        new Integer(versionsDeleted)), I_CmsReport.FORMAT_HEADLINE);
            }
            List<I_CmsHistoryResource> resources = getHistoryDriver(dbc).getAllDeletedEntries(dbc);
            if (resources.isEmpty()) {
                report.println(Messages.get().container(Messages.RPT_DELETE_NOTHING_0), I_CmsReport.FORMAT_OK);
            }
            int n = resources.size();
            int m = 1;
            Iterator<I_CmsHistoryResource> itResources = resources.iterator();
            while (itResources.hasNext()) {
                I_CmsHistoryResource histResource = itResources.next();

                report.print(
                        org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_SUCCESSION_2,
                                String.valueOf(m), String.valueOf(n)),
                        I_CmsReport.FORMAT_NOTE);
                report.print(org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_ARGUMENT_1,
                        dbc.removeSiteRoot(histResource.getRootPath())));
                report.print(org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_DOTS_0));

                try {
                    int deleted = getHistoryDriver(dbc).deleteEntries(dbc, histResource, versionsDeleted,
                            timeDeleted);

                    report.print(Messages.get().container(Messages.RPT_VERSION_DELETING_1, new Integer(deleted)),
                            I_CmsReport.FORMAT_NOTE);
                    report.print(
                            org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_DOTS_0));
                    report.println(
                            org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_OK_0),
                            I_CmsReport.FORMAT_OK);
                } catch (CmsDataAccessException e) {
                    report.println(
                            org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_ERROR_0),
                            I_CmsReport.FORMAT_ERROR);

                    if (LOG.isDebugEnabled()) {
                        LOG.debug(e.getLocalizedMessage(), e);
                    }
                }

                m++;
            }
            report.println(Messages.get().container(Messages.RPT_END_DELETE_DEL_VERSIONS_0),
                    I_CmsReport.FORMAT_HEADLINE);
        }
        report.println(Messages.get().container(Messages.RPT_END_DELETE_VERSIONS_0), I_CmsReport.FORMAT_HEADLINE);
    }

    /**
     * Deletes all log entries matching the given filter.<p>
     * 
     * @param dbc the current db context
     * @param filter the filter to use for deletion
     * 
     * @throws CmsException if something goes wrong
     * 
     * @see CmsSecurityManager#deleteLogEntries(CmsRequestContext, CmsLogFilter)
     */
    public void deleteLogEntries(CmsDbContext dbc, CmsLogFilter filter) throws CmsException {

        updateLog(dbc);
        m_projectDriver.deleteLog(dbc, filter);
    }

    /**
     * Deletes an organizational unit.<p>
     *
     * Only organizational units that contain no suborganizational unit can be deleted.<p>
     * 
     * The organizational unit can not be delete if it is used in the request context, 
     * or if the current user belongs to it.<p>
     * 
     * All users and groups in the given organizational unit will be deleted.<p>
     * 
     * @param dbc the current db context
     * @param organizationalUnit the organizational unit to delete
     * 
     * @throws CmsException if operation was not successful
     * 
     * @see org.opencms.security.CmsOrgUnitManager#deleteOrganizationalUnit(CmsObject, String)
     */
    public void deleteOrganizationalUnit(CmsDbContext dbc, CmsOrganizationalUnit organizationalUnit)
            throws CmsException {

        // check organizational unit in context
        if (dbc.getRequestContext().getOuFqn().equals(organizationalUnit.getName())) {
            throw new CmsDbConsistencyException(Messages.get().container(Messages.ERR_ORGUNIT_DELETE_IN_CONTEXT_1,
                    organizationalUnit.getName()));
        }
        // check organizational unit for user
        if (dbc.currentUser().getOuFqn().equals(organizationalUnit.getName())) {
            throw new CmsDbConsistencyException(Messages.get().container(Messages.ERR_ORGUNIT_DELETE_CURRENT_USER_1,
                    organizationalUnit.getName()));
        }
        // check sub organizational units
        if (!getOrganizationalUnits(dbc, organizationalUnit, true).isEmpty()) {
            throw new CmsDbConsistencyException(Messages.get().container(Messages.ERR_ORGUNIT_DELETE_SUB_ORGUNITS_1,
                    organizationalUnit.getName()));
        }
        // check groups
        List<CmsGroup> groups = getGroups(dbc, organizationalUnit, true, false);
        Iterator<CmsGroup> itGroups = groups.iterator();
        while (itGroups.hasNext()) {
            CmsGroup group = itGroups.next();
            if (!OpenCms.getDefaultUsers().isDefaultGroup(group.getName())) {
                throw new CmsDbConsistencyException(Messages.get().container(Messages.ERR_ORGUNIT_DELETE_GROUPS_1,
                        organizationalUnit.getName()));
            }
        }
        // check users
        if (!getUsers(dbc, organizationalUnit, true).isEmpty()) {
            throw new CmsDbConsistencyException(
                    Messages.get().container(Messages.ERR_ORGUNIT_DELETE_USERS_1, organizationalUnit.getName()));
        }

        // delete default groups if needed
        itGroups = groups.iterator();
        while (itGroups.hasNext()) {
            CmsGroup group = itGroups.next();
            deleteGroup(dbc, group, null);
        }

        // delete projects
        Iterator<CmsProject> itProjects = getProjectDriver(dbc).readProjects(dbc, organizationalUnit.getName())
                .iterator();
        while (itProjects.hasNext()) {
            CmsProject project = itProjects.next();
            deleteProject(dbc, project);
        }

        // delete roles
        Iterator<CmsGroup> itRoles = getGroups(dbc, organizationalUnit, true, true).iterator();
        while (itRoles.hasNext()) {
            CmsGroup role = itRoles.next();
            deleteGroup(dbc, role, null);
        }

        // create a publish list for the 'virtual' publish event
        CmsResource resource = readResource(dbc, organizationalUnit.getId(), CmsResourceFilter.DEFAULT);
        CmsPublishList pl = new CmsPublishList(resource, false);
        pl.add(resource, false);

        // remove the organizational unit itself
        getUserDriver(dbc).deleteOrganizationalUnit(dbc, organizationalUnit);

        // write the publish history entry
        getProjectDriver(dbc).writePublishHistory(dbc, pl.getPublishHistoryId(),
                new CmsPublishedResource(resource, -1, CmsResourceState.STATE_DELETED));

        // flush relevant caches
        m_monitor.clearPrincipalsCache();
        m_monitor.flushCache(CmsMemoryMonitor.CacheType.PROPERTY, CmsMemoryMonitor.CacheType.PROPERTY_LIST);

        // fire the 'virtual' publish event
        Map<String, Object> eventData = new HashMap<String, Object>();
        eventData.put(I_CmsEventListener.KEY_PUBLISHID, pl.getPublishHistoryId().toString());
        eventData.put(I_CmsEventListener.KEY_PROJECTID, dbc.currentProject().getUuid());
        eventData.put(I_CmsEventListener.KEY_DBCONTEXT, dbc);
        CmsEvent afterPublishEvent = new CmsEvent(I_CmsEventListener.EVENT_PUBLISH_PROJECT, eventData);
        OpenCms.fireCmsEvent(afterPublishEvent);

        m_lockManager.removeDeletedResource(dbc, resource.getRootPath());

        if (!dbc.getProjectId().isNullUUID()) {
            // OU modified event is not needed
            return;
        }
        // fire OU modified event
        Map<String, Object> event2Data = new HashMap<String, Object>();
        event2Data.put(I_CmsEventListener.KEY_OU_NAME, organizationalUnit.getName());
        event2Data.put(I_CmsEventListener.KEY_USER_ACTION, I_CmsEventListener.VALUE_OU_MODIFIED_ACTION_DELETE);
        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_OU_MODIFIED, event2Data));

    }

    /**
     * Deletes a project.<p>
     *
     * Only the admin or the owner of the project can do this.
     * 
     * @param dbc the current database context
     * @param deleteProject the project to be deleted
     *
     * @throws CmsException if something goes wrong
     */
    public void deleteProject(CmsDbContext dbc, CmsProject deleteProject) throws CmsException {

        CmsUUID projectId = deleteProject.getUuid();
        // changed/new/deleted files in the specified project
        List<CmsResource> modifiedFiles = readChangedResourcesInsideProject(dbc, projectId, RCPRM_FILES_ONLY_MODE);
        // changed/new/deleted folders in the specified project
        List<CmsResource> modifiedFolders = readChangedResourcesInsideProject(dbc, projectId,
                RCPRM_FOLDERS_ONLY_MODE);

        // all resources inside the project have to be be reset to their online state.
        // 1. step: delete all new files
        for (int i = 0; i < modifiedFiles.size(); i++) {
            CmsResource currentFile = modifiedFiles.get(i);
            if (currentFile.getState().isNew()) {
                CmsLock lock = getLock(dbc, currentFile);
                if (lock.isNullLock()) {
                    // lock the resource
                    lockResource(dbc, currentFile, CmsLockType.EXCLUSIVE);
                } else if (!lock.isOwnedBy(dbc.currentUser()) || !lock.isInProject(dbc.currentProject())) {
                    changeLock(dbc, currentFile, CmsLockType.EXCLUSIVE);
                }
                // delete the properties
                getVfsDriver(dbc).deletePropertyObjects(dbc, projectId, currentFile,
                        CmsProperty.DELETE_OPTION_DELETE_STRUCTURE_AND_RESOURCE_VALUES);
                // delete the file
                getVfsDriver(dbc).removeFile(dbc, dbc.currentProject().getUuid(), currentFile);
                // remove the access control entries
                getUserDriver(dbc).removeAccessControlEntries(dbc, dbc.currentProject(),
                        currentFile.getResourceId());
                // fire the corresponding event
                OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_AND_PROPERTIES_MODIFIED,
                        Collections.<String, Object>singletonMap(I_CmsEventListener.KEY_RESOURCE, currentFile)));
            }
        }

        // 2. step: delete all new folders
        for (int i = 0; i < modifiedFolders.size(); i++) {
            CmsResource currentFolder = modifiedFolders.get(i);
            if (currentFolder.getState().isNew()) {
                // delete the properties
                getVfsDriver(dbc).deletePropertyObjects(dbc, projectId, currentFolder,
                        CmsProperty.DELETE_OPTION_DELETE_STRUCTURE_AND_RESOURCE_VALUES);
                // delete the folder
                getVfsDriver(dbc).removeFolder(dbc, dbc.currentProject(), currentFolder);
                // remove the access control entries
                getUserDriver(dbc).removeAccessControlEntries(dbc, dbc.currentProject(),
                        currentFolder.getResourceId());
                // fire the corresponding event
                OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_AND_PROPERTIES_MODIFIED,
                        Collections.<String, Object>singletonMap(I_CmsEventListener.KEY_RESOURCE, currentFolder)));
            }
        }

        // 3. step: undo changes on all changed or deleted folders
        for (int i = 0; i < modifiedFolders.size(); i++) {
            CmsResource currentFolder = modifiedFolders.get(i);
            if ((currentFolder.getState().isChanged()) || (currentFolder.getState().isDeleted())) {
                CmsLock lock = getLock(dbc, currentFolder);
                if (lock.isNullLock()) {
                    // lock the resource
                    lockResource(dbc, currentFolder, CmsLockType.EXCLUSIVE);
                } else if (!lock.isOwnedBy(dbc.currentUser()) || !lock.isInProject(dbc.currentProject())) {
                    changeLock(dbc, currentFolder, CmsLockType.EXCLUSIVE);
                }
                // undo all changes in the folder
                undoChanges(dbc, currentFolder, CmsResource.UNDO_CONTENT);
                // fire the corresponding event
                OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_AND_PROPERTIES_MODIFIED,
                        Collections.<String, Object>singletonMap(I_CmsEventListener.KEY_RESOURCE, currentFolder)));
            }
        }

        // 4. step: undo changes on all changed or deleted files 
        for (int i = 0; i < modifiedFiles.size(); i++) {
            CmsResource currentFile = modifiedFiles.get(i);
            if (currentFile.getState().isChanged() || currentFile.getState().isDeleted()) {
                CmsLock lock = getLock(dbc, currentFile);
                if (lock.isNullLock()) {
                    // lock the resource
                    lockResource(dbc, currentFile, CmsLockType.EXCLUSIVE);
                } else if (!lock.isOwnedInProjectBy(dbc.currentUser(), dbc.currentProject())) {
                    if (lock.isLockableBy(dbc.currentUser())) {
                        changeLock(dbc, currentFile, CmsLockType.EXCLUSIVE);
                    }
                }
                // undo all changes in the file
                undoChanges(dbc, currentFile, CmsResource.UNDO_CONTENT);
                // fire the corresponding event
                OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_AND_PROPERTIES_MODIFIED,
                        Collections.<String, Object>singletonMap(I_CmsEventListener.KEY_RESOURCE, currentFile)));
            }
        }

        // unlock all resources in the project
        m_lockManager.removeResourcesInProject(deleteProject.getUuid(), true);
        m_monitor.clearAccessControlListCache();
        m_monitor.clearResourceCache();

        // set project to online project if current project is the one which will be deleted 
        if (projectId.equals(dbc.currentProject().getUuid())) {
            dbc.getRequestContext().setCurrentProject(readProject(dbc, CmsProject.ONLINE_PROJECT_ID));
        }

        // delete the project itself
        getProjectDriver(dbc).deleteProject(dbc, deleteProject);
        m_monitor.uncacheProject(deleteProject);

        // fire the corresponding event
        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_PROJECT_MODIFIED,
                Collections.<String, Object>singletonMap("project", deleteProject)));

    }

    /**
     * Deletes a property definition.<p>
     *
     * @param dbc the current database context
     * @param name the name of the property definition to delete
     * 
     * @throws CmsException if something goes wrong
     */
    public void deletePropertyDefinition(CmsDbContext dbc, String name) throws CmsException {

        CmsPropertyDefinition propertyDefinition = null;

        try {
            // first read and then delete the metadefinition.            
            propertyDefinition = readPropertyDefinition(dbc, name);
            getVfsDriver(dbc).deletePropertyDefinition(dbc, propertyDefinition);
            getHistoryDriver(dbc).deletePropertyDefinition(dbc, propertyDefinition);
        } finally {

            // fire an event that a property of a resource has been deleted
            OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_PROPERTY_DEFINITION_MODIFIED,
                    Collections.<String, Object>singletonMap("propertyDefinition", propertyDefinition)));
        }
    }

    /**
     * Deletes a publish job identified by its history id.<p>
     * 
     * @param dbc the current database context
     * @param publishHistoryId the history id identifying the publish job
     * 
     * @throws CmsException if something goes wrong
     */
    public void deletePublishJob(CmsDbContext dbc, CmsUUID publishHistoryId) throws CmsException {

        getProjectDriver(dbc).deletePublishJob(dbc, publishHistoryId);
    }

    /**
     * Deletes the publish list assigned to a publish job.<p>
     * 
     * @param dbc the current database context 
     * @param publishHistoryId the history id identifying the publish job
     * @throws CmsException if something goes wrong
     */
    public void deletePublishList(CmsDbContext dbc, CmsUUID publishHistoryId) throws CmsException {

        getProjectDriver(dbc).deletePublishList(dbc, publishHistoryId);
    }

    /**
     * Deletes all relations for the given resource matching the given filter.<p>
     * 
     * @param dbc the current db context
     * @param resource the resource to delete the relations for
     * @param filter the filter to use for deletion
     * 
     * @throws CmsException if something goes wrong
     * 
     * @see CmsSecurityManager#deleteRelationsForResource(CmsRequestContext, CmsResource, CmsRelationFilter)
     */
    public void deleteRelationsForResource(CmsDbContext dbc, CmsResource resource, CmsRelationFilter filter)
            throws CmsException {

        if (filter.includesDefinedInContent()) {
            throw new CmsIllegalArgumentException(
                    Messages.get().container(Messages.ERR_DELETE_RELATION_IN_CONTENT_2,
                            dbc.removeSiteRoot(resource.getRootPath()), filter.getTypes()));
        }
        getVfsDriver(dbc).deleteRelations(dbc, dbc.currentProject().getUuid(), resource, filter);
        setDateLastModified(dbc, resource, System.currentTimeMillis());
        log(dbc, new CmsLogEntry(dbc, resource.getStructureId(), CmsLogEntryType.RESOURCE_REMOVE_RELATION,
                new String[] { resource.getRootPath(), filter.toString() }), false);
    }

    /**
     * Deletes a resource.<p>
     * 
     * The <code>siblingMode</code> parameter controls how to handle siblings 
     * during the delete operation.
     * Possible values for this parameter are: 
     * <ul>
     * <li><code>{@link CmsResource#DELETE_REMOVE_SIBLINGS}</code></li>
     * <li><code>{@link CmsResource#DELETE_PRESERVE_SIBLINGS}</code></li>
     * </ul><p>
     * 
     * @param dbc the current database context
     * @param resource the name of the resource to delete (full path)
     * @param siblingMode indicates how to handle siblings of the deleted resource
     * 
     * @throws CmsException if something goes wrong
     * 
     * @see CmsObject#deleteResource(String, CmsResource.CmsResourceDeleteMode)
     * @see I_CmsResourceType#deleteResource(CmsObject, CmsSecurityManager, CmsResource, CmsResource.CmsResourceDeleteMode)
     */
    public void deleteResource(CmsDbContext dbc, CmsResource resource,
            CmsResource.CmsResourceDeleteMode siblingMode) throws CmsException {

        // upgrade a potential inherited, non-shared lock into a common lock
        CmsLock currentLock = getLock(dbc, resource);
        if (currentLock.getEditionLock().isDirectlyInherited()) {
            // upgrade the lock status if required
            lockResource(dbc, resource, CmsLockType.EXCLUSIVE);
        }

        // check if siblings of the resource exist and must be deleted as well
        if (resource.isFolder()) {
            // folder can have no siblings
            siblingMode = CmsResource.DELETE_PRESERVE_SIBLINGS;
        }

        // if selected, add all siblings of this resource to the list of resources to be deleted    
        boolean allSiblingsRemoved;
        List<CmsResource> resources;
        if (siblingMode == CmsResource.DELETE_REMOVE_SIBLINGS) {
            resources = new ArrayList<CmsResource>(readSiblings(dbc, resource, CmsResourceFilter.ALL));
            allSiblingsRemoved = true;

            // ensure that the resource requested to be deleted is the last resource that gets actually deleted
            // to keep the shared locks of the siblings while those get deleted.
            resources.remove(resource);
            resources.add(resource);
        } else {
            // only delete the resource, no siblings
            resources = Collections.singletonList(resource);
            allSiblingsRemoved = false;
        }

        int size = resources.size();
        // if we have only one resource no further check is required
        if (size > 1) {
            CmsMultiException me = new CmsMultiException();
            // ensure that each sibling is unlocked or locked by the current user
            for (int i = 0; i < size; i++) {
                CmsResource currentResource = resources.get(i);
                currentLock = getLock(dbc, currentResource);
                if (!currentLock.getEditionLock().isUnlocked() && !currentLock.isOwnedBy(dbc.currentUser())) {
                    // the resource is locked by a user different from the current user
                    CmsRequestContext context = dbc.getRequestContext();
                    me.addException(new CmsLockException(org.opencms.lock.Messages.get().container(
                            org.opencms.lock.Messages.ERR_SIBLING_LOCKED_2, context.getSitePath(currentResource),
                            context.getSitePath(resource))));
                }
            }
            if (!me.getExceptions().isEmpty()) {
                throw me;
            }
        }

        boolean removeAce = true;

        if (resource.isFolder()) {
            // check if the folder has any resources in it
            Iterator<CmsResource> childResources = getVfsDriver(dbc)
                    .readChildResources(dbc, dbc.currentProject(), resource, true, true).iterator();

            CmsUUID projectId = CmsProject.ONLINE_PROJECT_ID;
            if (dbc.currentProject().isOnlineProject()) {
                projectId = CmsUUID.getOpenCmsUUID(); // HACK: to get an offline project id
            }

            // collect the names of the resources inside the folder, excluding the moved resources
            StringBuffer errorResNames = new StringBuffer(128);
            while (childResources.hasNext()) {
                CmsResource errorRes = childResources.next();
                if (errorRes.getState().isDeleted()) {
                    continue;
                }
                // if deleting offline, or not moved, or just renamed inside the deleted folder
                // so, it may remain some orphan online entries for moved resources
                // which will be fixed during the publishing of the moved resources
                boolean error = !dbc.currentProject().isOnlineProject();
                if (!error) {
                    try {
                        String originalPath = getVfsDriver(dbc)
                                .readResource(dbc, projectId, errorRes.getRootPath(), true).getRootPath();
                        error = originalPath.equals(errorRes.getRootPath())
                                || originalPath.startsWith(resource.getRootPath());
                    } catch (CmsVfsResourceNotFoundException e) {
                        // ignore
                    }
                }
                if (error) {
                    if (errorResNames.length() != 0) {
                        errorResNames.append(", ");
                    }
                    errorResNames.append("[" + dbc.removeSiteRoot(errorRes.getRootPath()) + "]");
                }
            }

            // the current implementation only deletes empty folders
            if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(errorResNames.toString())) {
                throw new CmsVfsException(org.opencms.db.generic.Messages.get().container(
                        org.opencms.db.generic.Messages.ERR_DELETE_NONEMTY_FOLDER_2,
                        dbc.removeSiteRoot(resource.getRootPath()), errorResNames.toString()));
            }
        }

        // delete all collected resources
        for (int i = 0; i < size; i++) {
            CmsResource currentResource = resources.get(i);

            // try to delete/remove the resource only if the user has write access to the resource
            // check permissions only for the sibling, the resource it self was already checked or 
            // is to be removed without write permissions, ie. while deleting a folder
            if (!currentResource.equals(resource)
                    && (I_CmsPermissionHandler.PERM_ALLOWED != m_securityManager.hasPermissions(dbc,
                            currentResource, CmsPermissionSet.ACCESS_WRITE, true, CmsResourceFilter.ALL))) {

                // no write access to sibling - must keep ACE (see below)
                allSiblingsRemoved = false;
            } else {
                // write access to sibling granted                 
                boolean existsOnline = (getVfsDriver(dbc).validateStructureIdExists(dbc,
                        CmsProject.ONLINE_PROJECT_ID, currentResource.getStructureId())
                        || !(currentResource.getState().equals(CmsResource.STATE_NEW)));
                if (!existsOnline) {
                    // the resource does not exist online => remove the resource
                    // this means the resource is "new" (blue) in the offline project                

                    // delete all properties of this resource
                    deleteAllProperties(dbc, currentResource.getRootPath());

                    if (currentResource.isFolder()) {
                        getVfsDriver(dbc).removeFolder(dbc, dbc.currentProject(), currentResource);
                    } else {
                        // check labels
                        if (currentResource.isLabeled() && !labelResource(dbc, currentResource, null, 2)) {
                            // update the resource flags to "un label" the other siblings
                            int flags = currentResource.getFlags();
                            flags &= ~CmsResource.FLAG_LABELED;
                            currentResource.setFlags(flags);
                        }
                        getVfsDriver(dbc).removeFile(dbc, dbc.currentProject().getUuid(), currentResource);
                    }

                    // ensure an exclusive lock is removed in the lock manager for a deleted new resource,
                    // otherwise it would "stick" in the lock manager, preventing other users from creating 
                    // a file with the same name (issue with temp files in editor)
                    m_lockManager.removeDeletedResource(dbc, currentResource.getRootPath());
                    // delete relations
                    getVfsDriver(dbc).deleteRelations(dbc, dbc.currentProject().getUuid(), currentResource,
                            CmsRelationFilter.TARGETS);
                    getVfsDriver(dbc).deleteUrlNameMappingEntries(dbc, false,
                            CmsUrlNameMappingFilter.ALL.filterStructureId(currentResource.getStructureId()));
                } else {
                    // the resource exists online => mark the resource as deleted
                    // structure record is removed during next publish
                    // if one (or more) siblings are not removed, the ACE can not be removed
                    removeAce = false;

                    // set resource state to deleted
                    currentResource.setState(CmsResource.STATE_DELETED);
                    getVfsDriver(dbc).writeResourceState(dbc, dbc.currentProject(), currentResource,
                            UPDATE_STRUCTURE, false);

                    // update the project ID
                    getVfsDriver(dbc).writeLastModifiedProjectId(dbc, dbc.currentProject(),
                            dbc.currentProject().getUuid(), currentResource);
                    // log it
                    log(dbc, new CmsLogEntry(dbc, currentResource.getStructureId(),
                            CmsLogEntryType.RESOURCE_DELETED, new String[] { currentResource.getRootPath() }),
                            true);
                }
            }
        }

        if ((resource.getSiblingCount() <= 1) || allSiblingsRemoved) {
            if (removeAce) {
                // remove the access control entries
                getUserDriver(dbc).removeAccessControlEntries(dbc, dbc.currentProject(), resource.getResourceId());
            }
        }

        // flush all caches
        m_monitor.clearAccessControlListCache();
        m_monitor.flushCache(CmsMemoryMonitor.CacheType.PROPERTY, CmsMemoryMonitor.CacheType.PROPERTY_LIST,
                CmsMemoryMonitor.CacheType.PROJECT_RESOURCES);

        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_DELETED,
                Collections.<String, Object>singletonMap(I_CmsEventListener.KEY_RESOURCES, resources)));
    }

    /**
     * Deletes an entry in the published resource table.<p>
     * 
     * @param dbc the current database context
     * @param resourceName The name of the resource to be deleted in the static export
     * @param linkType the type of resource deleted (0= non-parameter, 1=parameter)
     * @param linkParameter the parameters of the resource
     * 
     * @throws CmsException if something goes wrong
     */
    public void deleteStaticExportPublishedResource(CmsDbContext dbc, String resourceName, int linkType,
            String linkParameter) throws CmsException {

        getProjectDriver(dbc).deleteStaticExportPublishedResource(dbc, resourceName, linkType, linkParameter);
    }

    /**
     * Deletes a user, where all permissions and resources attributes of the user
     * were transfered to a replacement user, if given.<p>
     *
     * Only users, which are in the group "administrators" are granted.<p>
     * 
     * @param dbc the current database context
     * @param project the current project
     * @param username the name of the user to be deleted
     * @param replacementUsername the name of the user to be transfered, can be <code>null</code>
     * 
     * @throws CmsException if operation was not successful
     */
    public void deleteUser(CmsDbContext dbc, CmsProject project, String username, String replacementUsername)
            throws CmsException {

        // Test if the users exists
        CmsUser user = readUser(dbc, username);
        CmsUser replacementUser = null;
        if (replacementUsername != null) {
            replacementUser = readUser(dbc, replacementUsername);
        }

        CmsProject onlineProject = readProject(dbc, CmsProject.ONLINE_PROJECT_ID);
        boolean withACEs = true;
        if (replacementUser == null) {
            withACEs = false;
            replacementUser = readUser(dbc, OpenCms.getDefaultUsers().getUserDeletedResource());
        }

        boolean isVfsManager = m_securityManager.hasRole(dbc, replacementUser, CmsRole.VFS_MANAGER);

        // iterate groups and roles
        for (boolean readRoles = false; !readRoles; readRoles = !readRoles) {
            Iterator<CmsGroup> itGroups = getGroupsOfUser(dbc, username, "", true, readRoles, true,
                    dbc.getRequestContext().getRemoteAddress()).iterator();
            while (itGroups.hasNext()) {
                CmsGroup group = itGroups.next();
                if (!isVfsManager) {
                    // add replacement user to user groups
                    if (!userInGroup(dbc, replacementUser.getName(), group.getName(), readRoles)) {
                        addUserToGroup(dbc, replacementUser.getName(), group.getName(), readRoles);
                    }
                }
                // remove user from groups
                if (userInGroup(dbc, username, group.getName(), readRoles)) {
                    // we need this additional check because removing a user from a group
                    // may also automatically remove him from other groups if the group was
                    // associated with a role.
                    removeUserFromGroup(dbc, username, group.getName(), readRoles);
                }
            }
        }
        // remove all locks set for the deleted user
        m_lockManager.removeLocks(user.getId());
        // offline
        if (dbc.getProjectId().isNullUUID()) {
            // offline project available
            transferPrincipalResources(dbc, project, user.getId(), replacementUser.getId(), withACEs);
        }
        // online
        transferPrincipalResources(dbc, onlineProject, user.getId(), replacementUser.getId(), withACEs);
        getUserDriver(dbc).removeAccessControlEntriesForPrincipal(dbc, project, onlineProject, user.getId());
        getHistoryDriver(dbc).writePrincipal(dbc, user);
        getUserDriver(dbc).deleteUser(dbc, username);
        // delete user from cache
        m_monitor.clearUserCache(user);

        if (!dbc.getProjectId().isNullUUID()) {
            // user modified event is not needed
            return;
        }
        // fire user modified event
        Map<String, Object> eventData = new HashMap<String, Object>();
        eventData.put(I_CmsEventListener.KEY_USER_ID, user.getId().toString());
        eventData.put(I_CmsEventListener.KEY_USER_NAME, user.getName());
        eventData.put(I_CmsEventListener.KEY_USER_ACTION,
                I_CmsEventListener.VALUE_USER_MODIFIED_ACTION_DELETE_USER);
        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_USER_MODIFIED, eventData));
    }

    /**
     * Destroys this driver manager and releases all allocated resources.<p>
     */
    public void destroy() {

        try {
            if (m_projectDriver != null) {
                try {
                    m_projectDriver.destroy();
                } catch (Throwable t) {
                    LOG.error(Messages.get().getBundle().key(Messages.ERR_CLOSE_PROJECT_DRIVER_0), t);
                }
                m_projectDriver = null;
            }
            if (m_userDriver != null) {
                try {
                    m_userDriver.destroy();
                } catch (Throwable t) {
                    LOG.error(Messages.get().getBundle().key(Messages.ERR_CLOSE_USER_DRIVER_0), t);
                }
                m_userDriver = null;
            }
            if (m_vfsDriver != null) {
                try {
                    m_vfsDriver.destroy();
                } catch (Throwable t) {
                    LOG.error(Messages.get().getBundle().key(Messages.ERR_CLOSE_VFS_DRIVER_0), t);
                }
                m_vfsDriver = null;
            }
            if (m_historyDriver != null) {
                try {
                    m_historyDriver.destroy();
                } catch (Throwable t) {
                    LOG.error(Messages.get().getBundle().key(Messages.ERR_CLOSE_HISTORY_DRIVER_0), t);
                }
                m_historyDriver = null;
            }

            if (m_connectionPools != null) {
                for (int i = 0; i < m_connectionPools.size(); i++) {
                    PoolingDriver driver = m_connectionPools.get(i);
                    String[] pools = driver.getPoolNames();
                    for (int j = 0; j < pools.length; j++) {
                        try {
                            driver.closePool(pools[j]);
                            if (CmsLog.INIT.isDebugEnabled()) {
                                CmsLog.INIT.debug(
                                        Messages.get().getBundle().key(Messages.INIT_CLOSE_CONN_POOL_1, pools[j]));
                            }
                        } catch (Throwable t) {
                            LOG.error(
                                    Messages.get().getBundle().key(Messages.LOG_CLOSE_CONN_POOL_ERROR_1, pools[j]),
                                    t);
                        }
                    }
                }
                m_connectionPools = null;
            }

            m_monitor.clearCache();

            m_lockManager = null;
            m_htmlLinkValidator = null;
        } catch (Throwable t) {
            // ignore
        }
        if (CmsLog.INIT.isInfoEnabled()) {
            CmsLog.INIT.info(
                    Messages.get().getBundle().key(Messages.INIT_DRIVER_MANAGER_DESTROY_1, getClass().getName()));
        }

        org.opencms.db.jpa.CmsSqlManager.destroy();
    }

    /**
     * Tests if a resource with the given resourceId does already exist in the Database.<p>
     * 
     * @param dbc the current database context
     * @param resourceId the resource id to test for
     * @return true if a resource with the given id was found, false otherweise
     * @throws CmsException if something goes wrong
     */
    public boolean existsResourceId(CmsDbContext dbc, CmsUUID resourceId) throws CmsException {

        return getVfsDriver(dbc).validateResourceIdExists(dbc, dbc.currentProject().getUuid(), resourceId);
    }

    /**
     * Fills the given publish list with the the VFS resources that actually get published.<p>
     * 
     * Please refer to the source code of this method for the rules on how to decide whether a
     * new/changed/deleted <code>{@link CmsResource}</code> object can be published or not.<p>
     * 
     * @param dbc the current database context
     * @param publishList must be initialized with basic publish information (Project or direct publish operation),
     *                    the given publish list will be filled with all new/changed/deleted files from the current 
     *                    (offline) project that will be actually published 
     * 
     * @throws CmsException if something goes wrong
     * 
     * @see org.opencms.db.CmsPublishList
     */
    public void fillPublishList(CmsDbContext dbc, CmsPublishList publishList) throws CmsException {

        if (!publishList.isDirectPublish()) {
            // when publishing a project
            // all modified resources with the last change done in the current project are candidates if unlocked
            List<CmsResource> folderList = getVfsDriver(dbc).readResourceTree(dbc, dbc.currentProject().getUuid(),
                    CmsDriverManager.READ_IGNORE_PARENT, CmsDriverManager.READ_IGNORE_TYPE,
                    CmsResource.STATE_UNCHANGED, CmsDriverManager.READ_IGNORE_TIME,
                    CmsDriverManager.READ_IGNORE_TIME, CmsDriverManager.READ_IGNORE_TIME,
                    CmsDriverManager.READ_IGNORE_TIME, CmsDriverManager.READ_IGNORE_TIME,
                    CmsDriverManager.READ_IGNORE_TIME,
                    CmsDriverManager.READMODE_INCLUDE_TREE | CmsDriverManager.READMODE_INCLUDE_PROJECT
                            | CmsDriverManager.READMODE_EXCLUDE_STATE | CmsDriverManager.READMODE_ONLY_FOLDERS);

            publishList.addAll(filterResources(dbc, null, folderList), true);

            List<CmsResource> fileList = getVfsDriver(dbc).readResourceTree(dbc, dbc.currentProject().getUuid(),
                    CmsDriverManager.READ_IGNORE_PARENT, CmsDriverManager.READ_IGNORE_TYPE,
                    CmsResource.STATE_UNCHANGED, CmsDriverManager.READ_IGNORE_TIME,
                    CmsDriverManager.READ_IGNORE_TIME, CmsDriverManager.READ_IGNORE_TIME,
                    CmsDriverManager.READ_IGNORE_TIME, CmsDriverManager.READ_IGNORE_TIME,
                    CmsDriverManager.READ_IGNORE_TIME,
                    CmsDriverManager.READMODE_INCLUDE_TREE | CmsDriverManager.READMODE_INCLUDE_PROJECT
                            | CmsDriverManager.READMODE_EXCLUDE_STATE | CmsDriverManager.READMODE_ONLY_FILES);

            publishList.addAll(filterResources(dbc, publishList, fileList), true);
        } else {
            // this is a direct publish
            Iterator<CmsResource> it = publishList.getDirectPublishResources().iterator();
            while (it.hasNext()) {
                // iterate all resources in the direct publish list
                CmsResource directPublishResource = it.next();
                if (directPublishResource.isFolder()) {
                    // when publishing a folder directly, 
                    // the folder and all modified resources within the tree below this folder 
                    // and with the last change done in the current project are candidates if lockable
                    CmsLock lock = getLock(dbc, directPublishResource);
                    if (!directPublishResource.getState().isUnchanged() && lock.isLockableBy(dbc.currentUser())) {

                        try {
                            m_securityManager.checkPermissions(dbc, directPublishResource,
                                    CmsPermissionSet.ACCESS_DIRECT_PUBLISH, false, CmsResourceFilter.ALL);
                            publishList.add(directPublishResource, true);
                        } catch (CmsException e) {
                            // skip if not enough permissions
                        }
                    }
                    boolean shouldPublishDeletedSubResources = publishList.isUserPublishList()
                            && directPublishResource.getState().isDeleted();
                    if (publishList.isPublishSubResources() || shouldPublishDeletedSubResources) {
                        addSubResources(dbc, publishList, directPublishResource);
                    }
                } else if (directPublishResource.isFile() && !directPublishResource.getState().isUnchanged()) {

                    // when publishing a file directly this file is the only candidate
                    // if it is modified and lockable
                    CmsLock lock = getLock(dbc, directPublishResource);
                    if (lock.isLockableBy(dbc.currentUser())) {
                        // check permissions
                        try {
                            m_securityManager.checkPermissions(dbc, directPublishResource,
                                    CmsPermissionSet.ACCESS_DIRECT_PUBLISH, false, CmsResourceFilter.ALL);
                            publishList.add(directPublishResource, true);
                        } catch (CmsException e) {
                            // skip if not enough permissions
                        }
                    }
                }
            }
        }

        // Step 2: if desired, extend the list of files to publish with related siblings
        if (publishList.isPublishSiblings()) {
            List<CmsResource> publishFiles = publishList.getFileList();
            int size = publishFiles.size();

            // Improved: first calculate closure of all siblings, then filter and add them
            Set<CmsResource> siblingsClosure = new HashSet<CmsResource>(publishFiles);
            for (int i = 0; i < size; i++) {
                CmsResource currentFile = publishFiles.get(i);
                if (currentFile.getSiblingCount() > 1) {
                    siblingsClosure.addAll(readSiblings(dbc, currentFile, CmsResourceFilter.ALL_MODIFIED));
                }
            }
            publishList.addAll(filterSiblings(dbc, publishList, siblingsClosure), true);
        }
        publishList.initialize();
    }

    /**
     * Returns the list of access control entries of a resource given its name.<p>
     * 
     * @param dbc the current database context
     * @param resource the resource to read the access control entries for
     * @param getInherited true if the result should include all access control entries inherited by parent folders
     * 
     * @return a list of <code>{@link CmsAccessControlEntry}</code> objects defining all permissions for the given resource
     * 
     * @throws CmsException if something goes wrong
     */
    public List<CmsAccessControlEntry> getAccessControlEntries(CmsDbContext dbc, CmsResource resource,
            boolean getInherited) throws CmsException {

        // get the ACE of the resource itself
        I_CmsUserDriver userDriver = getUserDriver(dbc);
        I_CmsVfsDriver vfsDriver = getVfsDriver(dbc);
        List<CmsAccessControlEntry> ace = userDriver.readAccessControlEntries(dbc, dbc.currentProject(),
                resource.getResourceId(), false);

        // sort and check if we got the 'overwrite all' ace to stop looking up
        boolean overwriteAll = sortAceList(ace);

        // get the ACE of each parent folder
        // Note: for the immediate parent, get non-inherited access control entries too,
        // if the resource is not a folder
        String parentPath = CmsResource.getParentFolder(resource.getRootPath());
        int d = (resource.isFolder()) ? 1 : 0;

        while (!overwriteAll && getInherited && (parentPath != null)) {
            resource = vfsDriver.readFolder(dbc, dbc.currentProject().getUuid(), parentPath);
            List<CmsAccessControlEntry> entries = userDriver.readAccessControlEntries(dbc, dbc.currentProject(),
                    resource.getResourceId(), d > 0);

            // sort and check if we got the 'overwrite all' ace to stop looking up
            overwriteAll = sortAceList(entries);

            for (Iterator<CmsAccessControlEntry> i = entries.iterator(); i.hasNext();) {
                CmsAccessControlEntry e = i.next();
                e.setFlags(CmsAccessControlEntry.ACCESS_FLAGS_INHERITED);
            }

            ace.addAll(entries);
            parentPath = CmsResource.getParentFolder(resource.getRootPath());
            d++;
        }

        return ace;
    }

    /**
     * Returns the full access control list of a given resource.<p>
     * 
     * @param dbc the current database context
     * @param resource the resource
     * 
     * @return the access control list of the resource
     * 
     * @throws CmsException if something goes wrong
     */
    public CmsAccessControlList getAccessControlList(CmsDbContext dbc, CmsResource resource) throws CmsException {

        return getAccessControlList(dbc, resource, false);
    }

    /**
     * Returns the access control list of a given resource.<p>
     *
     * If <code>inheritedOnly</code> is set, only inherited access control entries 
     * are returned.<p>
     * 
     * Note: For file resources, *all* permissions set at the immediate parent folder are inherited,
     * not only these marked to inherit. 
     * 
     * @param dbc the current database context
     * @param resource the resource
     * @param inheritedOnly skip non-inherited entries if set
     * 
     * @return the access control list of the resource
     * 
     * @throws CmsException if something goes wrong
     */
    public CmsAccessControlList getAccessControlList(CmsDbContext dbc, CmsResource resource, boolean inheritedOnly)
            throws CmsException {

        return getAccessControlList(dbc, resource, inheritedOnly, resource.isFolder(), 0);
    }

    /** 
     * Returns the number of active connections managed by a pool.<p> 
     * 
     * @param dbPoolUrl the url of a pool 
     * @return the number of active connections 
     * @throws CmsDbException if something goes wrong 
     */
    public int getActiveConnections(String dbPoolUrl) throws CmsDbException {

        try {
            for (Iterator<PoolingDriver> i = m_connectionPools.iterator(); i.hasNext();) {
                PoolingDriver d = i.next();
                ObjectPool p = d.getConnectionPool(dbPoolUrl);
                return p.getNumActive();
            }
        } catch (Exception exc) {
            CmsMessageContainer message = Messages.get().container(Messages.ERR_ACCESSING_POOL_1, dbPoolUrl);
            throw new CmsDbException(message, exc);
        }

        CmsMessageContainer message = Messages.get().container(Messages.ERR_UNKNOWN_POOL_URL_1, dbPoolUrl);
        throw new CmsDbException(message);
    }

    /**
     * Returns all projects which are owned by the current user or which are 
     * accessible by the current user.<p>
     *
     * @param dbc the current database context
     * @param orgUnit the organizational unit to search project in
     * @param includeSubOus if to include sub organizational units
     * 
     * @return a list of objects of type <code>{@link CmsProject}</code>
     * 
     * @throws CmsException if something goes wrong
     */
    public List<CmsProject> getAllAccessibleProjects(CmsDbContext dbc, CmsOrganizationalUnit orgUnit,
            boolean includeSubOus) throws CmsException {

        Set<CmsProject> projects = new HashSet<CmsProject>();

        // get the ous where the user has the project manager role
        List<CmsOrganizationalUnit> ous = getOrgUnitsForRole(dbc,
                CmsRole.PROJECT_MANAGER.forOrgUnit(orgUnit.getName()), includeSubOus);

        // get the groups of the user if needed
        Set<CmsUUID> userGroupIds = new HashSet<CmsUUID>();
        Iterator<CmsGroup> itGroups = getGroupsOfUser(dbc, dbc.currentUser().getName(), false).iterator();
        while (itGroups.hasNext()) {
            CmsGroup group = itGroups.next();
            userGroupIds.add(group.getId());
        }

        // TODO: this could be optimize if this method would have an additional parameter 'includeSubOus'
        // get all projects that might come in question
        projects.addAll(getProjectDriver(dbc).readProjects(dbc, orgUnit.getName()));

        // filter hidden and not accessible projects
        Iterator<CmsProject> itProjects = projects.iterator();
        while (itProjects.hasNext()) {
            CmsProject project = itProjects.next();
            boolean accessible = true;
            // if hidden
            accessible = accessible && !project.isHidden();

            if (!includeSubOus) {
                // if not exact in the given ou
                accessible = accessible && project.getOuFqn().equals(orgUnit.getName());
            } else {
                // if not in the given ou
                accessible = accessible && project.getOuFqn().startsWith(orgUnit.getName());
            }

            if (!accessible) {
                itProjects.remove();
                continue;
            }

            accessible = false;
            // online project
            accessible = accessible || project.isOnlineProject();
            // if owner
            accessible = accessible || project.getOwnerId().equals(dbc.currentUser().getId());

            // project managers
            Iterator<CmsOrganizationalUnit> itOus = ous.iterator();
            while (!accessible && itOus.hasNext()) {
                CmsOrganizationalUnit ou = itOus.next();
                // for project managers check visibility
                accessible = accessible || project.getOuFqn().startsWith(ou.getName());
            }

            if (!accessible) {
                // if direct user or manager of project 
                CmsUUID groupId = null;
                if (userGroupIds.contains(project.getGroupId())) {
                    groupId = project.getGroupId();
                } else if (userGroupIds.contains(project.getManagerGroupId())) {
                    groupId = project.getManagerGroupId();
                }
                if (groupId != null) {
                    String oufqn = readGroup(dbc, groupId).getOuFqn();
                    accessible = accessible || (oufqn.startsWith(dbc.getRequestContext().getOuFqn()));
                }
            }
            if (!accessible) {
                // remove not accessible project
                itProjects.remove();
            }
        }

        List<CmsProject> accessibleProjects = new ArrayList<CmsProject>(projects);
        // sort the list of projects based on the project name
        Collections.sort(accessibleProjects);
        // ensure the online project is in first place
        CmsProject onlineProject = readProject(dbc, CmsProject.ONLINE_PROJECT_ID);
        if (accessibleProjects.contains(onlineProject)) {
            accessibleProjects.remove(onlineProject);
        }
        accessibleProjects.add(0, onlineProject);

        return accessibleProjects;
    }

    /**
     * Returns a list with all projects from history.<p>
     *
     * @param dbc the current database context
     * 
     * @return list of <code>{@link CmsHistoryProject}</code> objects 
     *           with all projects from history.
     * 
     * @throws CmsException if operation was not successful
     */
    public List<CmsHistoryProject> getAllHistoricalProjects(CmsDbContext dbc) throws CmsException {

        // user is allowed to access all existing projects for the ous he has the project_manager role
        Set<CmsOrganizationalUnit> manOus = new HashSet<CmsOrganizationalUnit>(
                getOrgUnitsForRole(dbc, CmsRole.PROJECT_MANAGER, true));

        List<CmsHistoryProject> projects = getHistoryDriver(dbc).readProjects(dbc);
        Iterator<CmsHistoryProject> itProjects = projects.iterator();
        while (itProjects.hasNext()) {
            CmsHistoryProject project = itProjects.next();
            if (project.isHidden()) {
                // project is hidden
                itProjects.remove();
                continue;
            }
            if (!project.getOuFqn().startsWith(dbc.currentUser().getOuFqn())) {
                // project is not visible from the users ou
                itProjects.remove();
                continue;
            }
            CmsOrganizationalUnit ou = readOrganizationalUnit(dbc, project.getOuFqn());
            if (manOus.contains(ou)) {
                // user is project manager for this project
                continue;
            } else if (project.getOwnerId().equals(dbc.currentUser().getId())) {
                // user is owner of the project
                continue;
            } else {
                boolean found = false;
                Iterator<CmsGroup> itGroups = getGroupsOfUser(dbc, dbc.currentUser().getName(), false).iterator();
                while (itGroups.hasNext()) {
                    CmsGroup group = itGroups.next();
                    if (project.getManagerGroupId().equals(group.getId())) {
                        found = true;
                        break;
                    }
                }
                if (found) {
                    // user is member of the manager group of the project
                    continue;
                }
            }
            itProjects.remove();
        }
        return projects;
    }

    /**
     * Returns all projects which are owned by the current user or which are manageable
     * for the group of the user.<p>
     *
     * @param dbc the current database context
     * @param orgUnit the organizational unit to search project in
     * @param includeSubOus if to include sub organizational units
     * 
     * @return a list of objects of type <code>{@link CmsProject}</code>
     * 
     * @throws CmsException if operation was not successful
     */
    public List<CmsProject> getAllManageableProjects(CmsDbContext dbc, CmsOrganizationalUnit orgUnit,
            boolean includeSubOus) throws CmsException {

        Set<CmsProject> projects = new HashSet<CmsProject>();

        // get the ous where the user has the project manager role
        List<CmsOrganizationalUnit> ous = getOrgUnitsForRole(dbc,
                CmsRole.PROJECT_MANAGER.forOrgUnit(orgUnit.getName()), includeSubOus);

        // get the groups of the user if needed
        Set<CmsUUID> userGroupIds = new HashSet<CmsUUID>();
        Iterator<CmsGroup> itGroups = getGroupsOfUser(dbc, dbc.currentUser().getName(), false).iterator();
        while (itGroups.hasNext()) {
            CmsGroup group = itGroups.next();
            userGroupIds.add(group.getId());
        }

        // TODO: this could be optimize if this method would have an additional parameter 'includeSubOus'
        // get all projects that might come in question
        projects.addAll(getProjectDriver(dbc).readProjects(dbc, orgUnit.getName()));

        // filter hidden and not manageable projects
        Iterator<CmsProject> itProjects = projects.iterator();
        while (itProjects.hasNext()) {
            CmsProject project = itProjects.next();
            boolean manageable = true;
            // if online
            manageable = manageable && !project.isOnlineProject();
            // if hidden
            manageable = manageable && !project.isHidden();

            if (!includeSubOus) {
                // if not exact in the given ou
                manageable = manageable && project.getOuFqn().equals(orgUnit.getName());
            } else {
                // if not in the given ou
                manageable = manageable && project.getOuFqn().startsWith(orgUnit.getName());
            }

            if (!manageable) {
                itProjects.remove();
                continue;
            }

            manageable = false;
            // if owner
            manageable = manageable || project.getOwnerId().equals(dbc.currentUser().getId());

            // project managers
            Iterator<CmsOrganizationalUnit> itOus = ous.iterator();
            while (!manageable && itOus.hasNext()) {
                CmsOrganizationalUnit ou = itOus.next();
                // for project managers check visibility
                manageable = manageable || project.getOuFqn().startsWith(ou.getName());
            }

            if (!manageable) {
                // if manager of project 
                if (userGroupIds.contains(project.getManagerGroupId())) {
                    String oufqn = readGroup(dbc, project.getManagerGroupId()).getOuFqn();
                    manageable = manageable || (oufqn.startsWith(dbc.getRequestContext().getOuFqn()));
                }
            }
            if (!manageable) {
                // remove not accessible project
                itProjects.remove();
            }
        }

        List<CmsProject> manageableProjects = new ArrayList<CmsProject>(projects);
        // sort the list of projects based on the project name
        Collections.sort(manageableProjects);
        // ensure the online project is not in the list
        CmsProject onlineProject = readProject(dbc, CmsProject.ONLINE_PROJECT_ID);
        if (manageableProjects.contains(onlineProject)) {
            manageableProjects.remove(onlineProject);
        }

        return manageableProjects;
    }

    /**
     * Returns all child groups of a group.<p>
     *
     * @param dbc the current database context
     * @param group the group to get the child for
     * @param includeSubChildren if set also returns all sub-child groups of the given group
     * 
     * @return a list of all child <code>{@link CmsGroup}</code> objects
     * 
     * @throws CmsException if operation was not successful
     */
    public List<CmsGroup> getChildren(CmsDbContext dbc, CmsGroup group, boolean includeSubChildren)
            throws CmsException {

        if (!includeSubChildren) {
            return getUserDriver(dbc).readChildGroups(dbc, group.getName());
        }
        Set<CmsGroup> allChildren = new TreeSet<CmsGroup>();
        // iterate all child groups
        Iterator<CmsGroup> it = getUserDriver(dbc).readChildGroups(dbc, group.getName()).iterator();
        while (it.hasNext()) {
            CmsGroup child = it.next();
            // add the group itself
            allChildren.add(child);
            // now get all sub-children for each group
            allChildren.addAll(getChildren(dbc, child, true));
        }
        return new ArrayList<CmsGroup>(allChildren);
    }

    /**
     * Returns the date when the resource was last visited by the user.<p>
     * 
     * @param dbc the database context
     * @param poolName the name of the database pool to use
     * @param user the user to check the date
     * @param resource the resource to check the date
     * 
     * @return the date when the resource was last visited by the user
     * 
     * @throws CmsException if something goes wrong
     */
    public long getDateLastVisitedBy(CmsDbContext dbc, String poolName, CmsUser user, CmsResource resource)
            throws CmsException {

        return m_subscriptionDriver.getDateLastVisitedBy(dbc, poolName, user, resource);
    }

    /**
     * Returns all groups of the given organizational unit.<p>
     *
     * @param dbc the current db context
     * @param orgUnit the organizational unit to get the groups for
     * @param includeSubOus if all groups of sub-organizational units should be retrieved too
     * @param readRoles if to read roles or groups
     * 
     * @return all <code>{@link CmsGroup}</code> objects in the organizational unit
     *
     * @throws CmsException if operation was not successful
     * 
     * @see org.opencms.security.CmsOrgUnitManager#getResourcesForOrganizationalUnit(CmsObject, String)
     * @see org.opencms.security.CmsOrgUnitManager#getGroups(CmsObject, String, boolean)
     */
    public List<CmsGroup> getGroups(CmsDbContext dbc, CmsOrganizationalUnit orgUnit, boolean includeSubOus,
            boolean readRoles) throws CmsException {

        return getUserDriver(dbc).getGroups(dbc, orgUnit, includeSubOus, readRoles);
    }

    /**
     * Returns the groups of an user filtered by the specified IP address.<p>
     * 
     * @param dbc the current database context
     * @param username the name of the user
     * @param readRoles if to read roles or groups
     * 
     * @return the groups of the given user, as a list of {@link CmsGroup} objects
     * 
     * @throws CmsException if something goes wrong
     */
    public List<CmsGroup> getGroupsOfUser(CmsDbContext dbc, String username, boolean readRoles)
            throws CmsException {

        return getGroupsOfUser(dbc, username, "", true, readRoles, false,
                dbc.getRequestContext().getRemoteAddress());
    }

    /**
     * Returns the groups of an user filtered by the specified IP address.<p>
     * 
     * @param dbc the current database context
     * @param username the name of the user
     * @param ouFqn the fully qualified name of the organizational unit to restrict the result set for
     * @param includeChildOus include groups of child organizational units
     * @param readRoles if to read roles or groups
     * @param directGroupsOnly if set only the direct assigned groups will be returned, if not also indirect groups
     * @param remoteAddress the IP address to filter the groups in the result list 
     *
     * @return a list of <code>{@link CmsGroup}</code> objects
     * 
     * @throws CmsException if operation was not successful
     */
    public List<CmsGroup> getGroupsOfUser(CmsDbContext dbc, String username, String ouFqn, boolean includeChildOus,
            boolean readRoles, boolean directGroupsOnly, String remoteAddress) throws CmsException {

        CmsUser user = readUser(dbc, username);
        String prefix = ouFqn + "_" + includeChildOus + "_" + directGroupsOnly + "_" + readRoles + "_"
                + remoteAddress;
        String cacheKey = m_keyGenerator.getCacheKeyForUserGroups(prefix, dbc, user);
        List<CmsGroup> groups = m_monitor.getCachedUserGroups(cacheKey);
        if (groups == null) {
            // get all groups of the user
            List<CmsGroup> directGroups = getUserDriver(dbc).readGroupsOfUser(dbc, user.getId(),
                    readRoles ? "" : ouFqn, readRoles ? true : includeChildOus, remoteAddress, readRoles);
            Set<CmsGroup> allGroups = new HashSet<CmsGroup>();
            if (!readRoles) {
                allGroups.addAll(directGroups);
            }
            if (!directGroupsOnly) {
                if (!readRoles) {
                    // now get all parents of the groups
                    for (int i = 0; i < directGroups.size(); i++) {
                        CmsGroup parent = getParent(dbc, directGroups.get(i).getName());
                        while ((parent != null) && (!allGroups.contains(parent))) {
                            if (parent.getOuFqn().startsWith(ouFqn)) {
                                allGroups.add(parent);
                            }
                            // read next parent group
                            parent = getParent(dbc, parent.getName());
                        }
                    }
                }
            }
            if (readRoles) {
                // for each for role 
                for (int i = 0; i < directGroups.size(); i++) {
                    CmsGroup group = directGroups.get(i);
                    CmsRole role = CmsRole.valueOf(group);
                    if (!includeChildOus && role.getOuFqn().equals(ouFqn)) {
                        allGroups.add(group);
                    }
                    if (includeChildOus && role.getOuFqn().startsWith(ouFqn)) {
                        allGroups.add(group);
                    }
                    if (directGroupsOnly) {
                        continue;
                    }
                    // get the child roles
                    Iterator<CmsRole> itChildRoles = role.getChildren(true).iterator();
                    while (itChildRoles.hasNext()) {
                        CmsRole childRole = itChildRoles.next();
                        if (childRole.isSystemRole()) {
                            // include system roles only
                            allGroups.add(readGroup(dbc, childRole.getGroupName()));
                        }
                    }
                    if (includeChildOus) {
                        // if needed include the roles of child ous 
                        Iterator<CmsOrganizationalUnit> itSubOus = getOrganizationalUnits(dbc,
                                readOrganizationalUnit(dbc, group.getOuFqn()), true).iterator();
                        while (itSubOus.hasNext()) {
                            CmsOrganizationalUnit subOu = itSubOus.next();
                            // add role in child ou
                            try {
                                allGroups.add(readGroup(dbc, role.forOrgUnit(subOu.getName()).getGroupName()));
                            } catch (CmsDbEntryNotFoundException e) {
                                // ignore, this may happen while deleting an orgunit
                                if (LOG.isDebugEnabled()) {
                                    LOG.debug(e.getLocalizedMessage(), e);
                                }
                            }
                            // add child roles in child ous
                            itChildRoles = role.getChildren(true).iterator();
                            while (itChildRoles.hasNext()) {
                                CmsRole childRole = itChildRoles.next();
                                try {
                                    allGroups.add(
                                            readGroup(dbc, childRole.forOrgUnit(subOu.getName()).getGroupName()));
                                } catch (CmsDbEntryNotFoundException e) {
                                    // ignore, this may happen while deleting an orgunit
                                    if (LOG.isDebugEnabled()) {
                                        LOG.debug(e.getLocalizedMessage(), e);
                                    }
                                }
                            }
                        }
                    }
                }
            }
            // make group list unmodifiable for caching
            groups = Collections.unmodifiableList(new ArrayList<CmsGroup>(allGroups));
            if (dbc.getProjectId().isNullUUID()) {
                m_monitor.cacheUserGroups(cacheKey, groups);
            }
        }

        return groups;
    }

    /**
     * Returns the history driver.<p>
     * 
     * @return the history driver
     */
    public I_CmsHistoryDriver getHistoryDriver() {

        return m_historyDriver;
    }

    /**
     * Returns the history driver for a given database context.<p>
     * 
     * @param dbc the database context 
     * @return the history driver for the database context
     */
    public I_CmsHistoryDriver getHistoryDriver(CmsDbContext dbc) {

        if ((dbc == null) || (dbc.getProjectId() == null) || dbc.getProjectId().isNullUUID()) {
            return m_historyDriver;
        }
        I_CmsHistoryDriver driver = dbc.getHistoryDriver(dbc.getProjectId());
        return driver != null ? driver : m_historyDriver;

    }

    /** 
     * Returns the number of idle connections managed by a pool.<p> 
     * 
     * @param dbPoolUrl the url of a pool 
     * @return the number of idle connections 
     * @throws CmsDbException if something goes wrong 
     */
    public int getIdleConnections(String dbPoolUrl) throws CmsDbException {

        try {
            for (Iterator<PoolingDriver> i = m_connectionPools.iterator(); i.hasNext();) {
                PoolingDriver d = i.next();
                ObjectPool p = d.getConnectionPool(dbPoolUrl);
                return p.getNumIdle();
            }
        } catch (Exception exc) {
            CmsMessageContainer message = Messages.get().container(Messages.ERR_ACCESSING_POOL_1, dbPoolUrl);
            throw new CmsDbException(message, exc);
        }

        CmsMessageContainer message = Messages.get().container(Messages.ERR_UNKNOWN_POOL_URL_1, dbPoolUrl);
        throw new CmsDbException(message);
    }

    /**
     * Returns the lock state of a resource.<p>
     * 
     * @param dbc the current database context
     * @param resource the resource to return the lock state for
     * 
     * @return the lock state of the resource
     * 
     * @throws CmsException if something goes wrong
     */
    public CmsLock getLock(CmsDbContext dbc, CmsResource resource) throws CmsException {

        return m_lockManager.getLock(dbc, resource);
    }

    /**
     * Returns all locked resources in a given folder.<p>
     *
     * @param dbc the current database context
     * @param resource the folder to search in
     * @param filter the lock filter
     * 
     * @return a list of locked resource paths (relative to current site)
     * 
     * @throws CmsException if the current project is locked
     */
    public List<String> getLockedResources(CmsDbContext dbc, CmsResource resource, CmsLockFilter filter)
            throws CmsException {

        List<String> lockedResources = new ArrayList<String>();
        // get locked resources
        Iterator<CmsLock> it = m_lockManager.getLocks(dbc, resource.getRootPath(), filter).iterator();
        while (it.hasNext()) {
            CmsLock lock = it.next();
            lockedResources.add(dbc.removeSiteRoot(lock.getResourceName()));
        }
        Collections.sort(lockedResources);
        return lockedResources;
    }

    /**
     * Returns all locked resources in a given folder.<p>
     *
     * @param dbc the current database context
     * @param resource the folder to search in
     * @param filter the lock filter
     * 
     * @return a list of locked resources
     * 
     * @throws CmsException if the current project is locked
     */
    public List<CmsResource> getLockedResourcesObjects(CmsDbContext dbc, CmsResource resource, CmsLockFilter filter)
            throws CmsException {

        return m_lockManager.getLockedResources(dbc, resource, filter);
    }

    /**
     * Returns all locked resources in a given folder, but uses a cache for resource lookups.<p>
     *
     * @param dbc the current database context
     * @param resource the folder to search in
     * @param filter the lock filter
     * @param cache the cache to use for resource lookups
     * 
     * @return a list of locked resources
     * 
     * @throws CmsException if the current project is locked
     */
    public List<CmsResource> getLockedResourcesObjectsWithCache(CmsDbContext dbc, CmsResource resource,
            CmsLockFilter filter, Map<String, CmsResource> cache) throws CmsException {

        return m_lockManager.getLockedResourcesWithCache(dbc, resource, filter, cache);
    }

    /**
     * Returns all log entries matching the given filter.<p> 
     * 
     * @param dbc the current db context
     * @param filter the filter to match the log entries
     * 
     * @return all log entries matching the given filter
     * 
     * @throws CmsException if something goes wrong
     * 
     * @see CmsSecurityManager#getLogEntries(CmsRequestContext, CmsLogFilter)
     */
    public List<CmsLogEntry> getLogEntries(CmsDbContext dbc, CmsLogFilter filter) throws CmsException {

        updateLog(dbc);
        return m_projectDriver.readLog(dbc, filter);
    }

    /**
     * Returns the next publish tag for the published historical resources.<p>
     *
     * @param dbc the current database context
     * 
     * @return the next available publish tag
     */
    public int getNextPublishTag(CmsDbContext dbc) {

        return getHistoryDriver(dbc).readNextPublishTag(dbc);
    }

    /**
     * Returns all child organizational units of the given parent organizational unit including 
     * hierarchical deeper organization units if needed.<p>
     *
     * @param dbc the current db context
     * @param parent the parent organizational unit, or <code>null</code> for the root
     * @param includeChildren if hierarchical deeper organization units should also be returned
     * 
     * @return a list of <code>{@link CmsOrganizationalUnit}</code> objects
     * 
     * @throws CmsException if operation was not successful
     * 
     * @see org.opencms.security.CmsOrgUnitManager#getOrganizationalUnits(CmsObject, String, boolean)
     */
    public List<CmsOrganizationalUnit> getOrganizationalUnits(CmsDbContext dbc, CmsOrganizationalUnit parent,
            boolean includeChildren) throws CmsException {

        if (parent == null) {
            throw new CmsIllegalArgumentException(Messages.get().container(Messages.ERR_PARENT_ORGUNIT_NULL_0));
        }
        return getUserDriver(dbc).getOrganizationalUnits(dbc, parent, includeChildren);
    }

    /**
     * Returns all the organizational units for which the current user has the given role.<p>
     * 
     * @param dbc the current database context
     * @param role the role to check
     * @param includeSubOus if sub organizational units should be included in the search 
     *  
     * @return a list of {@link org.opencms.security.CmsOrganizationalUnit} objects
     * 
     * @throws CmsException if something goes wrong
     */
    public List<CmsOrganizationalUnit> getOrgUnitsForRole(CmsDbContext dbc, CmsRole role, boolean includeSubOus)
            throws CmsException {

        String ouFqn = role.getOuFqn();
        if (ouFqn == null) {
            ouFqn = "";
            role = role.forOrgUnit("");
        }
        CmsOrganizationalUnit ou = readOrganizationalUnit(dbc, ouFqn);
        List<CmsOrganizationalUnit> orgUnits = new ArrayList<CmsOrganizationalUnit>();
        if (m_securityManager.hasRole(dbc, dbc.currentUser(), role)) {
            orgUnits.add(ou);
        }
        if (includeSubOus) {
            Iterator<CmsOrganizationalUnit> it = getOrganizationalUnits(dbc, ou, true).iterator();
            while (it.hasNext()) {
                CmsOrganizationalUnit orgUnit = it.next();
                if (m_securityManager.hasRole(dbc, dbc.currentUser(), role.forOrgUnit(orgUnit.getName()))) {
                    orgUnits.add(orgUnit);
                }
            }
        }
        return orgUnits;
    }

    /**
     * Returns the parent group of a group.<p>
     *
     * @param dbc the current database context
     * @param groupname the name of the group
     * 
     * @return group the parent group or <code>null</code>
     * 
     * @throws CmsException if operation was not successful
     */
    public CmsGroup getParent(CmsDbContext dbc, String groupname) throws CmsException {

        CmsGroup group = readGroup(dbc, groupname);
        if (group.getParentId().isNullUUID()) {
            return null;
        }

        // try to read from cache
        CmsGroup parent = m_monitor.getCachedGroup(group.getParentId().toString());
        if (parent == null) {
            parent = getUserDriver(dbc).readGroup(dbc, group.getParentId());
            m_monitor.cacheGroup(parent);
        }
        return parent;
    }

    /**
     * Returns the set of permissions of the current user for a given resource.<p>
     * 
     * @param dbc the current database context
     * @param resource the resource
     * @param user the user
     * 
     * @return bit set with allowed permissions
     * 
     * @throws CmsException if something goes wrong
     */
    public CmsPermissionSetCustom getPermissions(CmsDbContext dbc, CmsResource resource, CmsUser user)
            throws CmsException {

        CmsAccessControlList acList = getAccessControlList(dbc, resource, false);
        return acList.getPermissions(user, getGroupsOfUser(dbc, user.getName(), false), getRolesForUser(dbc, user));
    }

    /**
     * Returns the project driver.<p>
     *
     * @return the project driver
     */
    public I_CmsProjectDriver getProjectDriver() {

        return m_projectDriver;
    }

    /**
     * Returns the project driver for a given DB context.<p>
     * 
     * @param dbc the database context
     * 
     * @return the project driver for the database context 
     */
    public I_CmsProjectDriver getProjectDriver(CmsDbContext dbc) {

        if ((dbc == null) || (dbc.getProjectId() == null) || dbc.getProjectId().isNullUUID()) {
            return m_projectDriver;
        }
        I_CmsProjectDriver driver = dbc.getProjectDriver(dbc.getProjectId());
        return driver != null ? driver : m_projectDriver;
    }

    /**
     * Returns either the project driver for the DB context (if it has one) or a default project driver.<p>
     * 
     * @param dbc the DB context
     * @param defaultDriver the driver which should be returned if there is no project driver for the DB context 
     * 
     * @return either the project driver for the DB context, or the default driver 
     */
    public I_CmsProjectDriver getProjectDriver(CmsDbContext dbc, I_CmsProjectDriver defaultDriver) {

        if ((dbc == null) || (dbc.getProjectId() == null) || dbc.getProjectId().isNullUUID()) {
            return defaultDriver;
        }
        I_CmsProjectDriver driver = dbc.getProjectDriver(dbc.getProjectId());
        return driver != null ? driver : defaultDriver;
    }

    /**
     * Returns the uuid id for the given id.<p>
     * 
     * TODO: remove this method as soon as possible
     * 
     * @param dbc the current database context
     * @param id the old project id
     * 
     * @return the new uuid for the given id
     * 
     * @throws CmsException if something goes wrong 
     */
    public CmsUUID getProjectId(CmsDbContext dbc, int id) throws CmsException {

        Iterator<CmsProject> itProjects = getAllAccessibleProjects(dbc, readOrganizationalUnit(dbc, ""), true)
                .iterator();
        while (itProjects.hasNext()) {
            CmsProject project = itProjects.next();
            if (project.getUuid().hashCode() == id) {
                return project.getUuid();
            }
        }
        return null;
    }

    /**
     * Returns the configuration read from the <code>opencms.properties</code> file.<p>
     *
     * @return the configuration read from the <code>opencms.properties</code> file
     */
    public CmsParameterConfiguration getPropertyConfiguration() {

        return m_propertyConfiguration;
    }

    /**
     * Returns a new publish list that contains the unpublished resources related 
     * to all resources in the given publish list, the related resources exclude 
     * all resources in the given publish list and also locked (by other users) resources.<p>
     * 
     * @param dbc the current database context
     * @param publishList the publish list to exclude from result
     * @param filter the relation filter to use to get the related resources
     * 
     * @return a new publish list that contains the related resources
     * 
     * @throws CmsException if something goes wrong
     * 
     * @see org.opencms.publish.CmsPublishManager#getRelatedResourcesToPublish(CmsObject, CmsPublishList)
     */
    public CmsPublishList getRelatedResourcesToPublish(CmsDbContext dbc, CmsPublishList publishList,
            CmsRelationFilter filter) throws CmsException {

        Map<String, CmsResource> relations = new HashMap<String, CmsResource>();

        // check if progress should be set in the thread
        CmsProgressThread thread = null;
        if (Thread.currentThread() instanceof CmsProgressThread) {
            thread = (CmsProgressThread) Thread.currentThread();
        }

        // get all resources to publish
        List<CmsResource> publishResources = publishList.getAllResources();
        Iterator<CmsResource> itCheckList = publishResources.iterator();
        // iterate over them
        int count = 0;
        while (itCheckList.hasNext()) {

            // set progress in thread
            count++;
            if (thread != null) {

                if (thread.isInterrupted()) {
                    throw new CmsIllegalStateException(org.opencms.workplace.commons.Messages.get()
                            .container(org.opencms.workplace.commons.Messages.ERR_PROGRESS_INTERRUPTED_0));
                }
                thread.setProgress((count * 20) / publishResources.size());
                thread.setDescription(org.opencms.workplace.commons.Messages.get().getBundle().key(
                        org.opencms.workplace.commons.Messages.GUI_PROGRESS_PUBLISH_STEP1_2, new Integer(count),
                        new Integer(publishResources.size())));
            }

            CmsResource checkResource = itCheckList.next();
            // get and iterate over all related resources
            Iterator<CmsRelation> itRelations = getRelationsForResource(dbc, checkResource, filter).iterator();
            while (itRelations.hasNext()) {
                CmsRelation relation = itRelations.next();
                try {
                    // get the target of the relation, see CmsRelation#getTarget(CmsObject, CmsResourceFilter)
                    CmsResource target;
                    try {
                        // first look up by id
                        target = readResource(dbc, relation.getTargetId(), CmsResourceFilter.ALL);
                    } catch (CmsVfsResourceNotFoundException e) {
                        // then look up by name, but from the root site
                        String storedSiteRoot = dbc.getRequestContext().getSiteRoot();
                        try {
                            dbc.getRequestContext().setSiteRoot("");
                            target = readResource(dbc, relation.getTargetPath(), CmsResourceFilter.ALL);
                        } finally {
                            dbc.getRequestContext().setSiteRoot(storedSiteRoot);
                        }
                    }
                    CmsLock lock = getLock(dbc, target);
                    // just add resources that may come in question
                    if (!publishResources.contains(target) // is not in the original list
                            && !relations.containsKey(target.getRootPath()) // has not been already added by another relation
                            && !target.getState().isUnchanged() // has been changed
                            && lock.isLockableBy(dbc.currentUser())) { // is lockable by current user

                        relations.put(target.getRootPath(), target);
                        // now check the folder structure
                        CmsResource parent = getVfsDriver(dbc).readParentFolder(dbc, dbc.currentProject().getUuid(),
                                target.getStructureId());
                        while ((parent != null) && parent.getState().isNew()) {
                            // just add resources that may come in question
                            if (!publishResources.contains(parent) // is not in the original list
                                    && !relations.containsKey(parent.getRootPath())) { // has not been already added by another relation

                                relations.put(parent.getRootPath(), parent);
                            }
                            parent = getVfsDriver(dbc).readParentFolder(dbc, dbc.currentProject().getUuid(),
                                    parent.getStructureId());
                        }
                    }
                } catch (CmsVfsResourceNotFoundException e) {
                    // ignore broken links
                    if (LOG.isDebugEnabled()) {
                        LOG.debug(e.getLocalizedMessage(), e);
                    }
                }
            }
        }

        CmsPublishList ret = new CmsPublishList(publishList.getDirectPublishResources(), false, false);
        ret.addAll(relations.values(), false);
        ret.initialize();
        return ret;
    }

    /**
     * Returns all relations for the given resource matching the given filter.<p> 
     * 
     * @param dbc the current db context
     * @param resource the resource to retrieve the relations for
     * @param filter the filter to match the relation 
     * 
     * @return all relations for the given resource matching the given filter
     * 
     * @throws CmsException if something goes wrong
     * 
     * @see CmsSecurityManager#getRelationsForResource(CmsRequestContext, CmsResource, CmsRelationFilter)
     */
    public List<CmsRelation> getRelationsForResource(CmsDbContext dbc, CmsResource resource,
            CmsRelationFilter filter) throws CmsException {

        CmsUUID projectId = getProjectIdForContext(dbc);
        return getVfsDriver(dbc).readRelations(dbc, projectId, resource, filter);
    }

    /**
     * Returns the list of organizational units the given resource belongs to.<p>
     * 
     * @param dbc the current database context
     * @param resource the resource
     * 
     * @return list of {@link CmsOrganizationalUnit} objects
     * 
     * @throws CmsException if something goes wrong
     */
    public List<CmsOrganizationalUnit> getResourceOrgUnits(CmsDbContext dbc, CmsResource resource)
            throws CmsException {

        List<CmsOrganizationalUnit> result = getVfsDriver(dbc).getResourceOus(dbc, dbc.currentProject().getUuid(),
                resource);

        return result;
    }

    /**
     * Returns all resources of the given organizational unit.<p>
     *
     * @param dbc the current db context
     * @param orgUnit the organizational unit to get all resources for
     * 
     * @return all <code>{@link CmsResource}</code> objects in the organizational unit
     *
     * @throws CmsException if operation was not successful
     * 
     * @see org.opencms.security.CmsOrgUnitManager#getResourcesForOrganizationalUnit(CmsObject, String)
     * @see org.opencms.security.CmsOrgUnitManager#getUsers(CmsObject, String, boolean)
     * @see org.opencms.security.CmsOrgUnitManager#getGroups(CmsObject, String, boolean)
     */
    public List<CmsResource> getResourcesForOrganizationalUnit(CmsDbContext dbc, CmsOrganizationalUnit orgUnit)
            throws CmsException {

        return getUserDriver(dbc).getResourcesForOrganizationalUnit(dbc, orgUnit);
    }

    /**
     * Returns all resources associated to a given principal via an ACE with the given permissions.<p> 
     * 
     * If the <code>includeAttr</code> flag is set it returns also all resources associated to 
     * a given principal through some of following attributes.<p> 
     * 
     * <ul>
     *    <li>User Created</li>
     *    <li>User Last Modified</li>
     * </ul><p>
     * 
     * @param dbc the current database context
     * @param project the to read the entries from
     * @param principalId the id of the principal
     * @param permissions a set of permissions to match, can be <code>null</code> for all ACEs
     * @param includeAttr a flag to include resources associated by attributes
     * 
     * @return a set of <code>{@link CmsResource}</code> objects
     * 
     * @throws CmsException if something goes wrong
     */
    public Set<CmsResource> getResourcesForPrincipal(CmsDbContext dbc, CmsProject project, CmsUUID principalId,
            CmsPermissionSet permissions, boolean includeAttr) throws CmsException {

        Set<CmsResource> resources = new HashSet<CmsResource>(
                getVfsDriver(dbc).readResourcesForPrincipalACE(dbc, project, principalId));
        if (permissions != null) {
            Iterator<CmsResource> itRes = resources.iterator();
            while (itRes.hasNext()) {
                CmsAccessControlEntry ace = readAccessControlEntry(dbc, itRes.next(), principalId);
                if ((ace.getPermissions().getPermissions() & permissions.getPermissions()) != permissions
                        .getPermissions()) {
                    // remove if permissions does not match
                    itRes.remove();
                }
            }
        }
        if (includeAttr) {
            resources.addAll(getVfsDriver(dbc).readResourcesForPrincipalAttr(dbc, project, principalId));
        }
        return resources;
    }

    /**
     * Collects the groups which constitute a given role.<p>
     *   
     * @param dbc the database context 
     * @param roleGroupName the group related to the role 
     * @param directUsersOnly if true, only the group belonging to the entry itself wil
     * 
     * @return the set of groups which constitute the role
     *  
     * @throws CmsException
     */
    public Set<CmsGroup> getRoleGroups(CmsDbContext dbc, String roleGroupName, boolean directUsersOnly)
            throws CmsException {

        return getRoleGroupsImpl(dbc, roleGroupName, directUsersOnly, new HashMap<String, Set<CmsGroup>>());
    }

    /**
     * Collects the groups which constitute a given role.<p>
     *   
     * @param dbc the database context 
     * @param roleGroupName the group related to the role 
     * @param directUsersOnly if true, only the group belonging to the entry itself wil
     * @param accumulator a map for memoizing return values of recursive calls  
     * 
     * @return the set of groups which constitute the role
     *  
     * @throws CmsException
     */
    public Set<CmsGroup> getRoleGroupsImpl(CmsDbContext dbc, String roleGroupName, boolean directUsersOnly,
            Map<String, Set<CmsGroup>> accumulator) throws CmsException {

        Set<CmsGroup> result = new HashSet<CmsGroup>();
        if (accumulator.get(roleGroupName) != null) {
            return accumulator.get(roleGroupName);
        }
        CmsGroup group = readGroup(dbc, roleGroupName); // check that the group really exists
        if ((group == null) || (!group.isRole())) {
            throw new CmsDbEntryNotFoundException(
                    Messages.get().container(Messages.ERR_UNKNOWN_GROUP_1, roleGroupName));
        }
        result.add(group);
        if (!directUsersOnly) {
            CmsRole role = CmsRole.valueOf(group);
            if (role.getParentRole() != null) {
                try {
                    String parentGroup = role.getParentRole().getGroupName();
                    // iterate the parent roles
                    result.addAll(getRoleGroupsImpl(dbc, parentGroup, directUsersOnly, accumulator));
                } catch (CmsDbEntryNotFoundException e) {
                    // ignore, this may happen while deleting an orgunit
                    if (LOG.isDebugEnabled()) {
                        LOG.debug(e.getLocalizedMessage(), e);
                    }
                }
            }
            String parentOu = CmsOrganizationalUnit.getParentFqn(group.getOuFqn());
            if (parentOu != null) {
                // iterate the parent ou's
                result.addAll(
                        getRoleGroupsImpl(dbc, parentOu + group.getSimpleName(), directUsersOnly, accumulator));
            }
        }
        accumulator.put(roleGroupName, result);
        return result;
    }

    /**
     * Returns all roles the given user has for the given resource.<p>
     * 
     * @param dbc the current database context
     * @param user the user to check
     * @param resource the resource to check the roles for 
     * 
     * @return a list of {@link CmsRole} objects
     * 
     * @throws CmsException if something goes wrong 
     */
    public List<CmsRole> getRolesForResource(CmsDbContext dbc, CmsUser user, CmsResource resource)
            throws CmsException {

        // guest user has no role
        if (user.isGuestUser()) {
            return Collections.emptyList();
        }

        // try to read from cache
        String key = user.getId().toString() + resource.getRootPath();
        List<CmsRole> result = m_monitor.getCachedRoleList(key);
        if (result != null) {
            return result;
        }
        result = new ArrayList<CmsRole>();

        Iterator<CmsOrganizationalUnit> itOus = getResourceOrgUnits(dbc, resource).iterator();
        while (itOus.hasNext()) {
            CmsOrganizationalUnit ou = itOus.next();

            // read all roles of the current user
            List<CmsGroup> groups = new ArrayList<CmsGroup>(getGroupsOfUser(dbc, user.getName(), ou.getName(),
                    false, true, false, dbc.getRequestContext().getRemoteAddress()));
            // check the roles applying to the given resource
            Iterator<CmsGroup> it = groups.iterator();
            while (it.hasNext()) {
                CmsGroup group = it.next();
                CmsRole givenRole = CmsRole.valueOf(group).forOrgUnit(null);
                if (givenRole.isOrganizationalUnitIndependent() || result.contains(givenRole)) {
                    // skip already added roles
                    continue;
                }
                result.add(givenRole);
            }
        }

        result = Collections.unmodifiableList(result);
        m_monitor.cacheRoleList(key, result);
        return result;
    }

    /**
     * Returns all roles the given user has independent of the resource.<p>
     * 
     * @param dbc the current database context
     * @param user the user to check
     * 
     * @return a list of {@link CmsRole} objects
     * 
     * @throws CmsException if something goes wrong 
     */
    public List<CmsRole> getRolesForUser(CmsDbContext dbc, CmsUser user) throws CmsException {

        // guest user has no role
        if (user.isGuestUser()) {
            return Collections.emptyList();
        }

        // try to read from cache
        String key = user.getId().toString();
        List<CmsRole> result = m_monitor.getCachedRoleList(key);
        if (result != null) {
            return result;
        }
        result = new ArrayList<CmsRole>();

        // read all roles of the current user
        List<CmsGroup> groups = new ArrayList<CmsGroup>(getGroupsOfUser(dbc, user.getName(), "", true, true, false,
                dbc.getRequestContext().getRemoteAddress()));

        // check the roles applying to the given resource
        Iterator<CmsGroup> it = groups.iterator();
        while (it.hasNext()) {
            CmsGroup group = it.next();
            CmsRole givenRole = CmsRole.valueOf(group);
            givenRole = givenRole.forOrgUnit(null);
            if (!result.contains(givenRole)) {
                result.add(givenRole);
            }
        }
        result = Collections.unmodifiableList(result);
        m_monitor.cacheRoleList(key, result);
        return result;
    }

    /**
     * Returns the security manager this driver manager belongs to.<p>
     * 
     * @return the security manager this driver manager belongs to
     */
    public CmsSecurityManager getSecurityManager() {

        return m_securityManager;
    }

    /**
     * Returns an instance of the common sql manager.<p>
     * 
     * @return an instance of the common sql manager
     */
    public CmsSqlManager getSqlManager() {

        return m_sqlManager;
    }

    /**
     * Returns the subscription driver of this driver manager.<p>
     * 
     * @return a subscription driver 
     */
    public I_CmsSubscriptionDriver getSubscriptionDriver() {

        return m_subscriptionDriver;
    }

    /**
     * Returns the user driver.<p>
     *
     * @return the user driver 
     */
    public I_CmsUserDriver getUserDriver() {

        return m_userDriver;
    }

    /**
     * Returns the user driver for a given database context.<p>
     * 
     * @param dbc the database context
     * 
     * @return the user driver for the database context 
     */
    public I_CmsUserDriver getUserDriver(CmsDbContext dbc) {

        if ((dbc == null) || (dbc.getProjectId() == null) || dbc.getProjectId().isNullUUID()) {
            return m_userDriver;
        }
        I_CmsUserDriver driver = dbc.getUserDriver(dbc.getProjectId());
        return driver != null ? driver : m_userDriver;

    }

    /**
     * Returns either the user driver for the given DB context (if it has one) or a default value instead.<p>
     * 
     * @param dbc the DB context
     * @param defaultDriver the driver that should be returned if no driver for the DB context was found
     * 
     * @return either the user driver for the DB context, or <code>defaultDriver</code> if none were found 
     */
    public I_CmsUserDriver getUserDriver(CmsDbContext dbc, I_CmsUserDriver defaultDriver) {

        if ((dbc == null) || (dbc.getProjectId() == null) || dbc.getProjectId().isNullUUID()) {
            return defaultDriver;
        }
        I_CmsUserDriver driver = dbc.getUserDriver(dbc.getProjectId());
        return driver != null ? driver : defaultDriver;
    }

    /**
     * Returns all direct users of the given organizational unit.<p>
     *
     * @param dbc the current db context
     * @param orgUnit the organizational unit to get all users for
     * @param recursive if all groups of sub-organizational units should be retrieved too
     * 
     * @return all <code>{@link CmsUser}</code> objects in the organizational unit
     *
     * @throws CmsException if operation was not successful
     * 
     * @see org.opencms.security.CmsOrgUnitManager#getResourcesForOrganizationalUnit(CmsObject, String)
     * @see org.opencms.security.CmsOrgUnitManager#getUsers(CmsObject, String, boolean)
     */
    public List<CmsUser> getUsers(CmsDbContext dbc, CmsOrganizationalUnit orgUnit, boolean recursive)
            throws CmsException {

        return getUserDriver(dbc).getUsers(dbc, orgUnit, recursive);
    }

    /**
     * Returns a list of users in a group.<p>
     *
     * @param dbc the current database context
     * @param groupname the name of the group to list users from
     * @param includeOtherOuUsers include users of other organizational units
     * @param directUsersOnly if set only the direct assigned users will be returned, 
     *                        if not also indirect users, ie. members of parent roles, 
     *                        this parameter only works with roles
     * @param readRoles if to read roles or groups
     * 
     * @return all <code>{@link CmsUser}</code> objects in the group
     * 
     * @throws CmsException if operation was not successful
     */
    public List<CmsUser> getUsersOfGroup(CmsDbContext dbc, String groupname, boolean includeOtherOuUsers,
            boolean directUsersOnly, boolean readRoles) throws CmsException {

        return internalUsersOfGroup(dbc, CmsOrganizationalUnit.getParentFqn(groupname), groupname,
                includeOtherOuUsers, directUsersOnly, readRoles);
    }

    /**
     * Returns the given user's publish list.<p>
     * 
     * @param dbc the database context
     * @param userId the user's id
     * 
     * @return the given user's publish list
     * 
     * @throws CmsDataAccessException if something goes wrong
     */
    public List<CmsResource> getUsersPubList(CmsDbContext dbc, CmsUUID userId) throws CmsDataAccessException {

        updateLog(dbc);
        return m_projectDriver.getUsersPubList(dbc, userId);
    }

    /**
     * Returns all direct users of the given organizational unit, without their additional info.<p>
     *
     * @param dbc the current db context
     * @param orgUnit the organizational unit to get all users for
     * @param recursive if all groups of sub-organizational units should be retrieved too
     * 
     * @return all <code>{@link CmsUser}</code> objects in the organizational unit
     *
     * @throws CmsException if operation was not successful
     * 
     * @see org.opencms.security.CmsOrgUnitManager#getResourcesForOrganizationalUnit(CmsObject, String)
     * @see org.opencms.security.CmsOrgUnitManager#getUsers(CmsObject, String, boolean)
     */
    public List<CmsUser> getUsersWithoutAdditionalInfo(CmsDbContext dbc, CmsOrganizationalUnit orgUnit,
            boolean recursive) throws CmsException {

        return getUserDriver(dbc).getUsersWithoutAdditionalInfo(dbc, orgUnit, recursive);
    }

    /**
     * Returns the VFS driver.<p>
     * 
     * @return the VFS driver
     */
    public I_CmsVfsDriver getVfsDriver() {

        return m_vfsDriver;
    }

    /**
     * Returns the VFS driver for the given database context.<p>
     * 
     * @param dbc the database context
     *  
     * @return a VFS driver  
     */
    public I_CmsVfsDriver getVfsDriver(CmsDbContext dbc) {

        if ((dbc == null) || (dbc.getProjectId() == null) || dbc.getProjectId().isNullUUID()) {
            return m_vfsDriver;
        }
        I_CmsVfsDriver driver = dbc.getVfsDriver(dbc.getProjectId());
        return driver != null ? driver : m_vfsDriver;

    }

    /**
     * Writes a vector of access control entries as new access control entries of a given resource.<p>
     * 
     * Already existing access control entries of this resource are removed before.
     * Access is granted, if:<p>
     * <ul>
     * <li>the current user has control permission on the resource</li>
     * </ul>
     * 
     * @param dbc the current database context
     * @param resource the resource
     * @param acEntries a list of <code>{@link CmsAccessControlEntry}</code> objects
     * 
     * @throws CmsException if something goes wrong
     */
    public void importAccessControlEntries(CmsDbContext dbc, CmsResource resource,
            List<CmsAccessControlEntry> acEntries) throws CmsException {

        I_CmsUserDriver userDriver = getUserDriver(dbc);
        userDriver.removeAccessControlEntries(dbc, dbc.currentProject(), resource.getResourceId());

        Iterator<CmsAccessControlEntry> i = acEntries.iterator();
        while (i.hasNext()) {
            userDriver.writeAccessControlEntry(dbc, dbc.currentProject(), i.next());
        }
        m_monitor.clearAccessControlListCache();
    }

    /**
     * Creates a new user by import.<p>
     * 
     * @param dbc the current database context
     * @param id the id of the user
     * @param name the new name for the user
     * @param password the new password for the user (already encrypted)
     * @param firstname the firstname of the user
     * @param lastname the lastname of the user
     * @param email the email of the user
     * @param flags the flags for a user (for example <code>{@link I_CmsPrincipal#FLAG_ENABLED}</code>)
     * @param dateCreated the creation date
     * @param additionalInfos the additional user infos
     * 
     * @return the imported user
     *
     * @throws CmsException if something goes wrong
     */
    public CmsUser importUser(CmsDbContext dbc, String id, String name, String password, String firstname,
            String lastname, String email, int flags, long dateCreated, Map<String, Object> additionalInfos)
            throws CmsException {

        // no space before or after the name
        name = name.trim();
        // check the user name
        String userName = CmsOrganizationalUnit.getSimpleName(name);
        OpenCms.getValidationHandler().checkUserName(userName);
        if (CmsStringUtil.isEmptyOrWhitespaceOnly(userName)) {
            throw new CmsIllegalArgumentException(Messages.get().container(Messages.ERR_BAD_USER_1, userName));
        }
        // check the ou
        CmsOrganizationalUnit ou = readOrganizationalUnit(dbc, CmsOrganizationalUnit.getParentFqn(name));

        // check webuser ou
        if (ou.hasFlagWebuser() && ((flags & I_CmsPrincipal.FLAG_USER_WEBUSER) == 0)) {
            flags += I_CmsPrincipal.FLAG_USER_WEBUSER;
        }
        CmsUser newUser = getUserDriver(dbc).createUser(dbc, new CmsUUID(id), name, password, firstname, lastname,
                email, 0, flags, dateCreated, additionalInfos);
        return newUser;
    }

    /**
     * Increments a counter and returns its value before incrementing.<p> 
     * 
     * @param dbc the current database context 
     * @param name the name of the counter which should be incremented  
     * 
     * @return the value of the counter
     *  
     * @throws CmsException if something goes wrong 
     */
    public int incrementCounter(CmsDbContext dbc, String name) throws CmsException {

        return getVfsDriver(dbc).incrementCounter(dbc, name);
    }

    /**
     * Initializes the driver and sets up all required modules and connections.<p>
     * 
     * @param configurationManager the configuration manager
     * @param dbContextFactory the db context factory
     * 
     * @throws CmsException if something goes wrong
     * @throws Exception if something goes wrong
     */
    public void init(CmsConfigurationManager configurationManager, I_CmsDbContextFactory dbContextFactory)
            throws CmsException, Exception {

        // initialize the access-module.
        if (CmsLog.INIT.isInfoEnabled()) {
            CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_DRIVER_MANAGER_START_PHASE4_0));
        }
        // store local reference to the memory monitor to avoid multiple lookups through the OpenCms singelton
        m_monitor = OpenCms.getMemoryMonitor();

        CmsSystemConfiguration systemConfiguation = (CmsSystemConfiguration) configurationManager
                .getConfiguration(CmsSystemConfiguration.class);
        CmsCacheSettings settings = systemConfiguation.getCacheSettings();

        // initialize the key generator
        m_keyGenerator = (I_CmsCacheKey) Class.forName(settings.getCacheKeyGenerator()).newInstance();

        // initialize the HTML link validator
        m_htmlLinkValidator = new CmsRelationSystemValidator(this);

        // fills the defaults if needed
        CmsDbContext dbc1 = dbContextFactory.getDbContext();
        getUserDriver().fillDefaults(dbc1);
        getProjectDriver().fillDefaults(dbc1);

        // set the driver manager in the publish engine
        m_publishEngine.setDriverManager(this);
        // create the root organizational unit if needed
        CmsDbContext dbc2 = dbContextFactory.getDbContext(new CmsRequestContext(
                readUser(dbc1, OpenCms.getDefaultUsers().getUserAdmin()),
                readProject(dbc1, CmsProject.ONLINE_PROJECT_ID), null, "", null, null, null, 0, null, null, ""));
        dbc1.clear();
        getUserDriver().createRootOrganizationalUnit(dbc2);
        dbc2.clear();
    }

    /**
     * Checks if the specified resource is inside the current project.<p>
     * 
     * The project "view" is determined by a set of path prefixes. 
     * If the resource starts with any one of this prefixes, it is considered to 
     * be "inside" the project.<p>
     * 
     * @param dbc the current database context
     * @param resourcename the specified resource name (full path)
     * 
     * @return <code>true</code>, if the specified resource is inside the current project
     */
    public boolean isInsideCurrentProject(CmsDbContext dbc, String resourcename) {

        List<String> projectResources = null;
        try {
            projectResources = readProjectResources(dbc, dbc.currentProject());
        } catch (CmsException e) {
            if (LOG.isErrorEnabled()) {
                LOG.error(Messages.get().getBundle().key(Messages.LOG_CHECK_RESOURCE_INSIDE_CURRENT_PROJECT_2,
                        resourcename, dbc.currentProject().getName()), e);
            }
            return false;
        }
        return CmsProject.isInsideProject(projectResources, resourcename);
    }

    /**
     * Checks whether the subscription driver is available.<p>
     * 
     * @return true if the subscription driver is available 
     */
    public boolean isSubscriptionDriverAvailable() {

        return m_subscriptionDriver != null;
    }

    /**
     * Checks if a project is the tempfile project.<p>
     * @param project the project to test
     * @return true if the project is the tempfile project
     */
    public boolean isTempfileProject(CmsProject project) {

        return project.getName().equals("tempFileProject");
    }

    /**
     * Checks if one of the resources (except the resource itself) 
     * is a sibling in a "labeled" site folder.<p>
     * 
     * This method is used when creating a new sibling 
     * (use the <code>newResource</code> parameter & <code>action = 1</code>) 
     * or deleting/importing a resource (call with <code>action = 2</code>).<p> 
     *   
     * @param dbc the current database context
     * @param resource the resource
     * @param newResource absolute path for a resource sibling which will be created
     * @param action the action which has to be performed (1: create VFS link, 2: all other actions)
     * 
     * @return <code>true</code> if the flag should be set for the resource, otherwise <code>false</code>
     * 
     * @throws CmsDataAccessException if something goes wrong
     */
    public boolean labelResource(CmsDbContext dbc, CmsResource resource, String newResource, int action)
            throws CmsDataAccessException {

        // get the list of labeled site folders from the runtime property
        List<String> labeledSites = OpenCms.getWorkplaceManager().getLabelSiteFolders();

        if (labeledSites.size() == 0) {
            // no labeled sites defined, just return false 
            return false;
        }

        if (action == 1) {
            // CASE 1: a new resource is created, check the sites
            if (!resource.isLabeled()) {
                // source isn't labeled yet, so check!
                boolean linkInside = false;
                boolean sourceInside = false;
                for (int i = 0; i < labeledSites.size(); i++) {
                    String curSite = labeledSites.get(i);
                    if (newResource.startsWith(curSite)) {
                        // the link lies in a labeled site
                        linkInside = true;
                    }
                    if (resource.getRootPath().startsWith(curSite)) {
                        // the source lies in a labeled site
                        sourceInside = true;
                    }
                    if (linkInside && sourceInside) {
                        break;
                    }
                }
                // return true when either source or link is in labeled site, otherwise false
                return (linkInside != sourceInside);
            }
            // resource is already labeled
            return false;

        } else {
            // CASE 2: the resource will be deleted or created (import)
            // check if at least one of the other siblings resides inside a "labeled site"
            // and if at least one of the other siblings resides outside a "labeled site"
            boolean isInside = false;
            boolean isOutside = false;
            // check if one of the other vfs links lies in a labeled site folder
            List<CmsResource> siblings = getVfsDriver(dbc).readSiblings(dbc, dbc.currentProject().getUuid(),
                    resource, false);
            updateContextDates(dbc, siblings);
            Iterator<CmsResource> i = siblings.iterator();
            while (i.hasNext() && (!isInside || !isOutside)) {
                CmsResource currentResource = i.next();
                if (currentResource.equals(resource)) {
                    // dont't check the resource itself!
                    continue;
                }
                String curPath = currentResource.getRootPath();
                boolean curInside = false;
                for (int k = 0; k < labeledSites.size(); k++) {
                    if (curPath.startsWith(labeledSites.get(k))) {
                        // the link is in the labeled site
                        isInside = true;
                        curInside = true;
                        break;
                    }
                }
                if (!curInside) {
                    // the current link was not found in labeled site, so it is outside
                    isOutside = true;
                }
            }
            // now check the new resource name if present
            if (newResource != null) {
                boolean curInside = false;
                for (int k = 0; k < labeledSites.size(); k++) {
                    if (newResource.startsWith(labeledSites.get(k))) {
                        // the new resource is in the labeled site
                        isInside = true;
                        curInside = true;
                        break;
                    }
                }
                if (!curInside) {
                    // the new resource was not found in labeled site, so it is outside
                    isOutside = true;
                }
            }
            return (isInside && isOutside);
        }
    }

    /**
     * Returns the user, who had locked the resource.<p>
     *
     * A user can lock a resource, so he is the only one who can write this
     * resource. This methods checks, if a resource was locked.
     *
     * @param dbc the current database context
     * @param resource the resource
     *
     * @return the user, who had locked the resource
     *
     * @throws CmsException will be thrown, if the user has not the rights for this resource
     */
    public CmsUser lockedBy(CmsDbContext dbc, CmsResource resource) throws CmsException {

        return readUser(dbc, m_lockManager.getLock(dbc, resource).getEditionLock().getUserId());
    }

    /**
     * Locks a resource.<p>
     *
     * The <code>type</code> parameter controls what kind of lock is used.<br>
     * Possible values for this parameter are: <br>
     * <ul>
     * <li><code>{@link org.opencms.lock.CmsLockType#EXCLUSIVE}</code></li>
     * <li><code>{@link org.opencms.lock.CmsLockType#TEMPORARY}</code></li>
     * <li><code>{@link org.opencms.lock.CmsLockType#PUBLISH}</code></li>
     * </ul><p>
     * 
     * @param dbc the current database context
     * @param resource the resource to lock
     * @param type type of the lock
     * 
     * @throws CmsException if something goes wrong
     * 
     * @see CmsObject#lockResource(String)
     * @see CmsObject#lockResourceTemporary(String)
     * @see org.opencms.file.types.I_CmsResourceType#lockResource(CmsObject, CmsSecurityManager, CmsResource, CmsLockType)
     */
    public void lockResource(CmsDbContext dbc, CmsResource resource, CmsLockType type) throws CmsException {

        // update the resource cache
        m_monitor.clearResourceCache();

        CmsProject project = dbc.currentProject();

        // add the resource to the lock dispatcher
        m_lockManager.addResource(dbc, resource, dbc.currentUser(), project, type);

        if (!resource.getState().isUnchanged() && !resource.getState().isKeep()) {
            // update the project flag of a modified resource as "last modified inside the current project"
            getVfsDriver(dbc).writeLastModifiedProjectId(dbc, project, project.getUuid(), resource);
        }

        // we must also clear the permission cache
        m_monitor.flushCache(CmsMemoryMonitor.CacheType.PERMISSION);

        // fire resource modification event
        Map<String, Object> data = new HashMap<String, Object>(2);
        data.put(I_CmsEventListener.KEY_RESOURCE, resource);
        data.put(I_CmsEventListener.KEY_CHANGE, new Integer(NOTHING_CHANGED));
        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_MODIFIED, data));
    }

    /**
     * Adds the given log entry to the current user's log.<p>
     * 
     * This operation works only on memory, to get the log entries actually 
     * written to DB you have to call the {@link #updateLog(CmsDbContext)} method.<p>
     * 
     * @param dbc the current database context
     * @param logEntry the log entry to create
     * @param force forces the log entry to be counted, 
     *              if not only the first log entry in a transaction will be taken into account
     */
    public void log(CmsDbContext dbc, CmsLogEntry logEntry, boolean force) {

        if (dbc == null) {
            return;
        }
        // check log level
        if (!logEntry.getType().isActive()) {
            // do not log inactive entries
            return;
        }
        // if not forcing
        if (!force) {
            // operation already logged
            boolean abort = (dbc.getAttribute(CmsLogEntry.ATTR_LOG_ENTRY) != null);
            // disabled logging from outside
            abort |= (dbc.getRequestContext().getAttribute(CmsLogEntry.ATTR_LOG_ENTRY) != null);
            if (abort) {
                return;
            }
        }
        // prevent several entries for the same operation
        dbc.setAttribute(CmsLogEntry.ATTR_LOG_ENTRY, Boolean.TRUE);
        // keep it for later
        m_log.add(logEntry);
    }

    /**
     * Attempts to authenticate a user into OpenCms with the given password.<p>
     * 
     * @param dbc the current database context
     * @param userName the name of the user to be logged in
     * @param password the password of the user
     * @param remoteAddress the ip address of the request
     * 
     * @return the logged in user
     *
     * @throws CmsAuthentificationException if the login was not successful
     * @throws CmsDataAccessException in case of errors accessing the database
     * @throws CmsPasswordEncryptionException in case of errors encrypting the users password
     */
    public CmsUser loginUser(CmsDbContext dbc, String userName, String password, String remoteAddress)
            throws CmsAuthentificationException, CmsDataAccessException, CmsPasswordEncryptionException {

        if (CmsStringUtil.isEmptyOrWhitespaceOnly(password)) {
            throw new CmsDbEntryNotFoundException(Messages.get().container(Messages.ERR_UNKNOWN_USER_1, userName));
        }
        CmsUser newUser;
        try {
            // read the user from the driver to avoid the cache
            newUser = getUserDriver(dbc).readUser(dbc, userName, password, remoteAddress);
        } catch (CmsDbEntryNotFoundException e) {
            // this indicates that the username / password combination does not exist
            // any other exception indicates database issues, these are not catched here

            // check if a user with this name exists at all 
            CmsUser user = null;
            try {
                user = readUser(dbc, userName);
            } catch (CmsDataAccessException e2) {
                // apparently this user does not exist in the database
            }

            if (user != null) {
                if (dbc.currentUser().isGuestUser()) {
                    // add an invalid login attempt for this user to the storage
                    OpenCms.getLoginManager().addInvalidLogin(userName, remoteAddress);
                }
                OpenCms.getLoginManager().checkInvalidLogins(userName, remoteAddress);
                throw new CmsAuthentificationException(org.opencms.security.Messages.get()
                        .container(org.opencms.security.Messages.ERR_LOGIN_FAILED_2, userName, remoteAddress), e);
            } else {
                String userOu = CmsOrganizationalUnit.getParentFqn(userName);
                if (userOu != null) {
                    String parentOu = CmsOrganizationalUnit.getParentFqn(userOu);
                    if (parentOu != null) {
                        // try a higher level ou
                        String uName = CmsOrganizationalUnit.getSimpleName(userName);
                        return loginUser(dbc, parentOu + uName, password, remoteAddress);
                    }
                }
                throw new CmsAuthentificationException(
                        org.opencms.security.Messages.get().container(
                                org.opencms.security.Messages.ERR_LOGIN_FAILED_NO_USER_2, userName, remoteAddress),
                        e);
            }
        }
        // check if the "enabled" flag is set for the user
        if (!newUser.isEnabled()) {
            // user is disabled, throw a securiy exception
            throw new CmsAuthentificationException(org.opencms.security.Messages.get()
                    .container(org.opencms.security.Messages.ERR_LOGIN_FAILED_DISABLED_2, userName, remoteAddress));
        }

        if (dbc.currentUser().isGuestUser()) {
            // check if this account is temporarily disabled because of too many invalid login attempts
            // this will throw an exception if the test fails
            OpenCms.getLoginManager().checkInvalidLogins(userName, remoteAddress);
            // test successful, remove all previous invalid login attempts for this user from the storage
            OpenCms.getLoginManager().removeInvalidLogins(userName, remoteAddress);
        }

        if (!m_securityManager.hasRole(dbc, newUser,
                CmsRole.ADMINISTRATOR.forOrgUnit(dbc.getRequestContext().getOuFqn()))) {
            // new user is not Administrator, check if login is currently allowed
            OpenCms.getLoginManager().checkLoginAllowed();
        }
        m_monitor.clearUserCache(newUser);
        // set the last login time to the current time
        newUser.setLastlogin(System.currentTimeMillis());
        dbc.setAttribute(ATTRIBUTE_LOGIN, newUser.getName());
        // write the changed user object back to the user driver
        getUserDriver(dbc).writeUser(dbc, newUser);

        // update cache
        m_monitor.cacheUser(newUser);

        // invalidate all user dependent caches
        m_monitor.flushCache(CmsMemoryMonitor.CacheType.ACL, CmsMemoryMonitor.CacheType.GROUP,
                CmsMemoryMonitor.CacheType.ORG_UNIT, CmsMemoryMonitor.CacheType.USERGROUPS,
                CmsMemoryMonitor.CacheType.USER_LIST, CmsMemoryMonitor.CacheType.PERMISSION,
                CmsMemoryMonitor.CacheType.RESOURCE_LIST);

        // fire user modified event
        Map<String, Object> eventData = new HashMap<String, Object>();
        eventData.put(I_CmsEventListener.KEY_USER_ID, newUser.getId().toString());
        eventData.put(I_CmsEventListener.KEY_USER_NAME, newUser.getName());
        eventData.put(I_CmsEventListener.KEY_USER_ACTION, I_CmsEventListener.VALUE_USER_MODIFIED_ACTION_WRITE_USER);
        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_USER_MODIFIED, eventData));

        // return the user object read from the driver
        return newUser;
    }

    /**
     * Lookup and read the user or group with the given UUID.<p>
     * 
     * @param dbc the current database context
     * @param principalId the UUID of the principal to lookup
     * 
     * @return the principal (group or user) if found, otherwise <code>null</code>
     */
    public I_CmsPrincipal lookupPrincipal(CmsDbContext dbc, CmsUUID principalId) {

        try {
            CmsGroup group = getUserDriver(dbc).readGroup(dbc, principalId);
            if (group != null) {
                return group;
            }
        } catch (Exception e) {
            // ignore this exception 
        }

        try {
            CmsUser user = readUser(dbc, principalId);
            if (user != null) {
                return user;
            }
        } catch (Exception e) {
            // ignore this exception
        }

        return null;
    }

    /**
     * Lookup and read the user or group with the given name.<p>
     * 
     * @param dbc the current database context
     * @param principalName the name of the principal to lookup
     * 
     * @return the principal (group or user) if found, otherwise <code>null</code>
     */
    public I_CmsPrincipal lookupPrincipal(CmsDbContext dbc, String principalName) {

        try {
            CmsGroup group = getUserDriver(dbc).readGroup(dbc, principalName);
            if (group != null) {
                return group;
            }
        } catch (Exception e) {
            // ignore this exception
        }

        try {
            CmsUser user = readUser(dbc, principalName);
            if (user != null) {
                return user;
            }
        } catch (Exception e) {
            // ignore this exception
        }

        return null;
    }

    /**
     * Mark the given resource as visited by the user.<p>
     * 
     * @param dbc the database context
     * @param poolName the name of the database pool to use
     * @param resource the resource to mark as visited
     * @param user the user that visited the resource
     * 
     * @throws CmsException if something goes wrong
     */
    public void markResourceAsVisitedBy(CmsDbContext dbc, String poolName, CmsResource resource, CmsUser user)
            throws CmsException {

        getSubscriptionDriver().markResourceAsVisitedBy(dbc, poolName, resource, user);
    }

    /**
     * Moves a resource.<p>
     * 
     * You must ensure that the parent of the destination path is an absolute, valid and
     * existing VFS path. Relative paths from the source are not supported.<p>
     * 
     * The moved resource will always be locked to the current user
     * after the move operation.<p>
     * 
     * In case the target resource already exists, it will be overwritten with the 
     * source resource if possible.<p>
     * 
     * @param dbc the current database context
     * @param source the resource to move
     * @param destination the name of the move destination with complete path
     * @param internal if set nothing more than the path is modified
     * 
     * @throws CmsException if something goes wrong
     * 
     * @see CmsSecurityManager#moveResource(CmsRequestContext, CmsResource, String)
     */
    public void moveResource(CmsDbContext dbc, CmsResource source, String destination, boolean internal)
            throws CmsException {

        CmsFolder destinationFolder = readFolder(dbc, CmsResource.getParentFolder(destination),
                CmsResourceFilter.ALL);
        m_securityManager.checkPermissions(dbc, destinationFolder, CmsPermissionSet.ACCESS_WRITE, false,
                CmsResourceFilter.ALL);

        if (source.isFolder()) {
            m_monitor.flushCache(CmsMemoryMonitor.CacheType.HAS_ROLE, CmsMemoryMonitor.CacheType.ROLE_LIST);
        }
        getVfsDriver(dbc).moveResource(dbc, dbc.getRequestContext().getCurrentProject().getUuid(), source,
                destination);

        if (!internal) {
            CmsResourceState newState = CmsResource.STATE_CHANGED;
            if (source.getState().isNew()) {
                newState = CmsResource.STATE_NEW;
            } else if (source.getState().isDeleted()) {
                newState = CmsResource.STATE_DELETED;
            }
            source.setState(newState);
            // safe since this operation always uses the ids instead of the resource path
            getVfsDriver(dbc).writeResourceState(dbc, dbc.currentProject(), source,
                    CmsDriverManager.UPDATE_STRUCTURE_STATE, false);
            // log it
            log(dbc, new CmsLogEntry(dbc, source.getStructureId(), CmsLogEntryType.RESOURCE_MOVED,
                    new String[] { source.getRootPath(), destination }), false);
        }

        CmsResource destRes = readResource(dbc, destination, CmsResourceFilter.ALL);
        // move lock 
        m_lockManager.moveResource(source.getRootPath(), destRes.getRootPath());

        // flush all relevant caches
        m_monitor.clearAccessControlListCache();
        m_monitor.flushCache(CmsMemoryMonitor.CacheType.PROPERTY, CmsMemoryMonitor.CacheType.PROPERTY_LIST,
                CmsMemoryMonitor.CacheType.PROJECT_RESOURCES);

        List<CmsResource> resources = new ArrayList<CmsResource>(4);
        // source
        resources.add(source);
        try {
            resources
                    .add(readFolder(dbc, CmsResource.getParentFolder(source.getRootPath()), CmsResourceFilter.ALL));
        } catch (Exception e) {
            if (LOG.isDebugEnabled()) {
                LOG.debug(e);
            }
        }
        // destination
        resources.add(destRes);
        resources.add(destinationFolder);

        // fire the events
        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_MOVED,
                Collections.<String, Object>singletonMap(I_CmsEventListener.KEY_RESOURCES, resources)));
    }

    /**
     * Moves a resource to the "lost and found" folder.<p>
     * 
     * The method can also be used to check get the name of a resource
     * in the "lost and found" folder only without actually moving the
     * the resource. To do this, the <code>returnNameOnly</code> flag
     * must be set to <code>true</code>.<p>
     * 
     * @param dbc the current database context
     * @param resource the resource to apply this operation to
     * @param returnNameOnly if <code>true</code>, only the name of the resource in the "lost and found" 
     *        folder is returned, the move operation is not really performed
     * 
     * @return the name of the resource inside the "lost and found" folder
     * 
     * @throws CmsException if something goes wrong
     * @throws CmsIllegalArgumentException if the <code>resourcename</code> argument is null or of length 0
     * 
     * @see CmsObject#moveToLostAndFound(String)
     * @see CmsObject#getLostAndFoundName(String)
     */
    public String moveToLostAndFound(CmsDbContext dbc, CmsResource resource, boolean returnNameOnly)
            throws CmsException, CmsIllegalArgumentException {

        String resourcename = dbc.removeSiteRoot(resource.getRootPath());

        String siteRoot = dbc.getRequestContext().getSiteRoot();
        dbc.getRequestContext().setSiteRoot("");
        String destination = CmsDriverManager.LOST_AND_FOUND_FOLDER + resourcename;
        // create the required folders if necessary
        try {
            // collect all folders...
            String folderPath = CmsResource.getParentFolder(destination);
            folderPath = folderPath.substring(1, folderPath.length() - 1); // cut out leading and trailing '/'
            Iterator<String> folders = CmsStringUtil.splitAsList(folderPath, '/').iterator();
            // ...now create them....
            folderPath = "/";
            while (folders.hasNext()) {
                folderPath += folders.next().toString() + "/";
                try {
                    readFolder(dbc, folderPath, CmsResourceFilter.IGNORE_EXPIRATION);
                } catch (Exception e1) {
                    if (returnNameOnly) {
                        // we can use the original name without risk, and we do not need to recreate the parent folders 
                        break;
                    }
                    // the folder is not existing, so create it
                    createResource(dbc, folderPath, CmsResourceTypeFolder.RESOURCE_TYPE_ID, null,
                            new ArrayList<CmsProperty>());
                }
            }
            // check if this resource name does already exist
            // if so add a postfix to the name
            String des = destination;
            int postfix = 1;
            boolean found = true;
            while (found) {
                try {
                    // try to read the file.....
                    found = true;
                    readResource(dbc, des, CmsResourceFilter.ALL);
                    // ....it's there, so add a postfix and try again
                    String path = destination.substring(0, destination.lastIndexOf('/') + 1);
                    String filename = destination.substring(destination.lastIndexOf('/') + 1, destination.length());

                    des = path;

                    if (filename.lastIndexOf('.') > 0) {
                        des += filename.substring(0, filename.lastIndexOf('.'));
                    } else {
                        des += filename;
                    }
                    des += "_" + postfix;
                    if (filename.lastIndexOf('.') > 0) {
                        des += filename.substring(filename.lastIndexOf('.'), filename.length());
                    }
                    postfix++;
                } catch (CmsException e3) {
                    // the file does not exist, so we can use this filename                               
                    found = false;
                }
            }
            destination = des;

            if (!returnNameOnly) {
                // do not use the move semantic here! to prevent links pointing to the lost & found folder
                copyResource(dbc, resource, destination, CmsResource.COPY_AS_SIBLING);
                deleteResource(dbc, resource, CmsResource.DELETE_PRESERVE_SIBLINGS);
            }
        } catch (CmsException e2) {
            throw e2;
        } finally {
            // set the site root to the old value again
            dbc.getRequestContext().setSiteRoot(siteRoot);
        }
        return destination;
    }

    /**
     * Gets a new driver instance.<p>
     * 
     * @param dbc the database context
     * @param configurationManager the configuration manager
     * @param driverName the driver name
     * @param successiveDrivers the list of successive drivers
     * 
     * @return the driver object
     * @throws CmsInitException if the selected driver could not be initialized
     */
    public Object newDriverInstance(CmsDbContext dbc, CmsConfigurationManager configurationManager,
            String driverName, List<String> successiveDrivers) throws CmsInitException {

        Class<?> driverClass = null;
        I_CmsDriver driver = null;

        try {
            // try to get the class
            driverClass = Class.forName(driverName);
            if (CmsLog.INIT.isInfoEnabled()) {
                CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_DRIVER_START_1, driverName));
            }

            // try to create a instance
            driver = (I_CmsDriver) driverClass.newInstance();
            if (CmsLog.INIT.isInfoEnabled()) {
                CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_DRIVER_INITIALIZING_1, driverName));
            }

            // invoke the init-method of this access class
            driver.init(dbc, configurationManager, successiveDrivers, this);
            if (CmsLog.INIT.isInfoEnabled()) {
                CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_DRIVER_INIT_FINISHED_0));
            }

        } catch (Throwable t) {
            CmsMessageContainer message = Messages.get().container(Messages.ERR_ERROR_INITIALIZING_DRIVER_1,
                    driverName);
            if (LOG.isErrorEnabled()) {
                LOG.error(message.key(), t);
            }
            throw new CmsInitException(message, t);
        }

        return driver;
    }

    /**
     * Method to create a new instance of a driver.<p>
     * 
     * @param configuration the configurations from the propertyfile
     * @param driverName the class name of the driver
     * @param driverPoolUrl the pool url for the driver
     * @return an initialized instance of the driver
     * @throws CmsException if something goes wrong
     */
    public Object newDriverInstance(CmsParameterConfiguration configuration, String driverName,
            String driverPoolUrl) throws CmsException {

        Class<?>[] initParamClasses = { CmsParameterConfiguration.class, String.class, CmsDriverManager.class };
        Object[] initParams = { configuration, driverPoolUrl, this };

        Class<?> driverClass = null;
        Object driver = null;

        try {
            // try to get the class
            driverClass = Class.forName(driverName);
            if (CmsLog.INIT.isInfoEnabled()) {
                CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_DRIVER_START_1, driverName));
            }

            // try to create a instance
            driver = driverClass.newInstance();
            if (CmsLog.INIT.isInfoEnabled()) {
                CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_DRIVER_INITIALIZING_1, driverName));
            }

            // invoke the init-method of this access class
            driver.getClass().getMethod("init", initParamClasses).invoke(driver, initParams);
            if (CmsLog.INIT.isInfoEnabled()) {
                CmsLog.INIT
                        .info(Messages.get().getBundle().key(Messages.INIT_DRIVER_INIT_FINISHED_1, driverPoolUrl));
            }

        } catch (Exception exc) {

            CmsMessageContainer message = Messages.get().container(Messages.ERR_INIT_DRIVER_MANAGER_1);
            if (LOG.isFatalEnabled()) {
                LOG.fatal(message.key(), exc);
            }
            throw new CmsDbException(message, exc);

        }

        return driver;
    }

    /**
     * Method to create a new instance of a pool.<p>
     * 
     * @param configuration the configurations from the propertyfile
     * @param poolName the configuration name of the pool
     * 
     * @throws CmsInitException if the pools could not be initialized
     */
    public void newPoolInstance(CmsParameterConfiguration configuration, String poolName) throws CmsInitException {

        PoolingDriver driver;

        try {
            driver = CmsDbPool.createDriverManagerConnectionPool(configuration, poolName);
        } catch (Exception e) {

            CmsMessageContainer message = Messages.get().container(Messages.ERR_INIT_CONN_POOL_1, poolName);
            if (LOG.isErrorEnabled()) {
                LOG.error(message.key(), e);
            }
            throw new CmsInitException(message, e);
        }

        m_connectionPools.add(driver);
    }

    /**
     * Publishes the given publish job.<p>
     * 
     * @param cms the cms context
     * @param dbc the db context
     * @param publishList the list of resources to publish
     * @param report the report to write to
     * 
     * @throws CmsException if something goes wrong
     */
    public void publishJob(CmsObject cms, CmsDbContext dbc, CmsPublishList publishList, I_CmsReport report)
            throws CmsException {

        try {
            // check state and lock
            List<CmsResource> allResources = new ArrayList<CmsResource>(publishList.getFolderList());
            allResources.addAll(publishList.getDeletedFolderList());
            allResources.addAll(publishList.getFileList());
            Iterator<CmsResource> itResources = allResources.iterator();
            while (itResources.hasNext()) {
                CmsResource resource = itResources.next();
                try {
                    resource = readResource(dbc, resource.getStructureId(), CmsResourceFilter.ALL);
                } catch (CmsVfsResourceNotFoundException e) {
                    continue;
                }
                if (resource.getState().isUnchanged()) {
                    // remove files that were published by a concurrent job
                    if (LOG.isDebugEnabled()) {
                        LOG.debug(Messages.get().getBundle().key(Messages.RPT_PUBLISH_REMOVED_RESOURCE_1,
                                dbc.removeSiteRoot(resource.getRootPath())));
                    }
                    publishList.remove(resource);
                    unlockResource(dbc, resource, true, true);
                    continue;
                }
                CmsLock lock = m_lockManager.getLock(dbc, resource, false);
                if (!lock.getSystemLock().isPublish()) {
                    // remove files that are not locked for publishing
                    if (LOG.isDebugEnabled()) {
                        LOG.debug(Messages.get().getBundle().key(Messages.RPT_PUBLISH_REMOVED_RESOURCE_1,
                                dbc.removeSiteRoot(resource.getRootPath())));
                    }
                    publishList.remove(resource);
                    continue;
                }
            }

            CmsProject onlineProject = readProject(dbc, CmsProject.ONLINE_PROJECT_ID);

            // clear the cache
            m_monitor.clearCache();

            int publishTag = getNextPublishTag(dbc);
            getProjectDriver(dbc).publishProject(dbc, report, onlineProject, publishList, publishTag);

            // iterate the initialized module action instances
            Iterator<String> i = OpenCms.getModuleManager().getModuleNames().iterator();
            while (i.hasNext()) {
                CmsModule module = OpenCms.getModuleManager().getModule(i.next());
                if ((module != null) && (module.getActionInstance() != null)) {
                    module.getActionInstance().publishProject(cms, publishList, publishTag, report);
                }
            }

            boolean temporaryProject = (cms.getRequestContext().getCurrentProject()
                    .getType() == CmsProject.PROJECT_TYPE_TEMPORARY);
            // the project was stored in the history tables for history
            // it will be deleted if the project_flag is PROJECT_TYPE_TEMPORARY
            if ((temporaryProject) && (!publishList.isDirectPublish())) {
                try {
                    getProjectDriver(dbc).deleteProject(dbc, dbc.currentProject());
                } catch (CmsException e) {
                    LOG.error(Messages.get().getBundle().key(Messages.LOG_DELETE_TEMP_PROJECT_FAILED_1,
                            cms.getRequestContext().getCurrentProject().getName()));
                }
                // if project was temporary set context to online project
                cms.getRequestContext().setCurrentProject(onlineProject);
            }
        } finally {
            // clear the cache again
            m_monitor.clearCache();
        }
    }

    /**
     * Publishes the resources of a specified publish list.<p>
     *
     * @param cms the current request context
     * @param dbc the current database context
     * @param publishList a publish list
     * @param report an instance of <code>{@link I_CmsReport}</code> to print messages
     * 
     * @throws CmsException if something goes wrong
     * 
     * @see #fillPublishList(CmsDbContext, CmsPublishList)
     */
    public synchronized void publishProject(CmsObject cms, CmsDbContext dbc, CmsPublishList publishList,
            I_CmsReport report) throws CmsException {

        // check the parent folders
        checkParentFolders(dbc, publishList);
        ensureSubResourcesOfMovedFoldersPublished(cms, dbc, publishList);

        try {
            // fire an event that a project is to be published
            Map<String, Object> eventData = new HashMap<String, Object>();
            eventData.put(I_CmsEventListener.KEY_REPORT, report);
            eventData.put(I_CmsEventListener.KEY_PUBLISHLIST, publishList);
            eventData.put(I_CmsEventListener.KEY_PROJECTID, dbc.currentProject().getUuid());
            eventData.put(I_CmsEventListener.KEY_DBCONTEXT, dbc);
            CmsEvent beforePublishEvent = new CmsEvent(I_CmsEventListener.EVENT_BEFORE_PUBLISH_PROJECT, eventData);
            OpenCms.fireCmsEvent(beforePublishEvent);
        } catch (Throwable t) {
            if (report != null) {
                report.addError(t);
                report.println(t);
            }
            if (LOG.isErrorEnabled()) {
                LOG.error(t.getLocalizedMessage(), t);
            }
        }

        // lock all resources with the special publish lock
        Iterator<CmsResource> itResources = new ArrayList<CmsResource>(publishList.getAllResources()).iterator();
        while (itResources.hasNext()) {
            CmsResource resource = itResources.next();
            CmsLock lock = m_lockManager.getLock(dbc, resource, false);
            if (lock.getSystemLock().isUnlocked() && lock.isLockableBy(dbc.currentUser())) {
                if (getLock(dbc, resource).getEditionLock().isNullLock()) {
                    lockResource(dbc, resource, CmsLockType.PUBLISH);
                } else {
                    changeLock(dbc, resource, CmsLockType.PUBLISH);
                }
            } else if (lock.getSystemLock().isPublish()) {
                if (LOG.isWarnEnabled()) {
                    LOG.warn(Messages.get().getBundle().key(Messages.RPT_PUBLISH_REMOVED_RESOURCE_1,
                            dbc.removeSiteRoot(resource.getRootPath())));
                }
                // remove files that are already waiting to be published
                publishList.remove(resource);
                continue;
            } else {
                // this is needed to fix TestPublishIsssues#testPublishScenarioE
                changeLock(dbc, resource, CmsLockType.PUBLISH);
            }
            // now re-check the lock state
            lock = m_lockManager.getLock(dbc, resource, false);
            if (!lock.getSystemLock().isPublish()) {
                if (report != null) {
                    report.println(Messages.get().container(Messages.RPT_PUBLISH_REMOVED_RESOURCE_1,
                            dbc.removeSiteRoot(resource.getRootPath())), I_CmsReport.FORMAT_WARNING);
                }
                if (LOG.isWarnEnabled()) {
                    LOG.warn(Messages.get().getBundle().key(Messages.RPT_PUBLISH_REMOVED_RESOURCE_1,
                            dbc.removeSiteRoot(resource.getRootPath())));
                }
                // remove files that could not be locked
                publishList.remove(resource);
            }
        }

        // enqueue the publish job
        CmsException enqueueException = null;
        try {
            m_publishEngine.enqueuePublishJob(cms, publishList, report);
        } catch (CmsException exc) {
            enqueueException = exc;
        }

        // if an exception was raised, remove the publish locks
        // and throw the exception again
        if (enqueueException != null) {
            itResources = publishList.getAllResources().iterator();
            while (itResources.hasNext()) {
                CmsResource resource = itResources.next();
                CmsLock lock = m_lockManager.getLock(dbc, resource, false);
                if (lock.getSystemLock().isPublish() && lock.getSystemLock().isOwnedInProjectBy(
                        cms.getRequestContext().getCurrentUser(), cms.getRequestContext().getCurrentProject())) {
                    unlockResource(dbc, resource, true, true);
                }
            }

            throw enqueueException;
        }
    }

    /**
     * Transfers the new URL name mappings (if any) for a given resource to the online project.<p>
     * 
     * @param dbc the current database context 
     * @param res the resource whose new URL name mappings should be transferred to the online project
     *  
     * @throws CmsDataAccessException if something goes wrong 
     */
    public void publishUrlNameMapping(CmsDbContext dbc, CmsResource res) throws CmsDataAccessException {

        I_CmsVfsDriver vfsDriver = getVfsDriver(dbc);

        if (res.getState().isDeleted()) {
            // remove both offline and online mappings 
            CmsUrlNameMappingFilter idFilter = CmsUrlNameMappingFilter.ALL.filterStructureId(res.getStructureId());
            vfsDriver.deleteUrlNameMappingEntries(dbc, true, idFilter);
            vfsDriver.deleteUrlNameMappingEntries(dbc, false, idFilter);
        } else {
            // copy the new entries to the online table  
            List<CmsUrlNameMappingEntry> entries = vfsDriver.readUrlNameMappingEntries(dbc, false,
                    CmsUrlNameMappingFilter.ALL.filterStructureId(res.getStructureId())
                            .filterState(CmsUrlNameMappingEntry.MAPPING_STATUS_NEW));
            if (!entries.isEmpty()) {
                long now = System.currentTimeMillis();
                for (CmsUrlNameMappingEntry entry : entries) {
                    CmsUrlNameMappingFilter nameFilter = CmsUrlNameMappingFilter.ALL.filterName(entry.getName());
                    vfsDriver.deleteUrlNameMappingEntries(dbc, true, nameFilter);
                    vfsDriver.deleteUrlNameMappingEntries(dbc, false, nameFilter);
                    CmsUrlNameMappingEntry newEntry = new CmsUrlNameMappingEntry(entry.getName(),
                            entry.getStructureId(), CmsUrlNameMappingEntry.MAPPING_STATUS_PUBLISHED, now,
                            entry.getLocale());
                    vfsDriver.addUrlNameMappingEntry(dbc, true, newEntry);
                    vfsDriver.addUrlNameMappingEntry(dbc, false, newEntry);
                }
            }
        }
    }

    /**
     * Reads an access control entry from the cms.<p>
     * 
     * The access control entries of a resource are readable by everyone.
     * 
     * @param dbc the current database context
     * @param resource the resource
     * @param principal the id of a group or a user any other entity
     * @return an access control entry that defines the permissions of the entity for the given resource
     * @throws CmsException if something goes wrong
     */
    public CmsAccessControlEntry readAccessControlEntry(CmsDbContext dbc, CmsResource resource, CmsUUID principal)
            throws CmsException {

        return getUserDriver(dbc).readAccessControlEntry(dbc, dbc.currentProject(), resource.getResourceId(),
                principal);
    }

    /**
     * Reads all versions of the given resource.<br>
     * 
     * This method returns a list with the history of the given resource, i.e.
     * the historical resource entries, independent of the project they were attached to.<br>
     *
     * The reading excludes the file content.<p>
     *
     * @param dbc the current database context
     * @param resource the resource to read the history for
     * 
     * @return a list of file headers, as <code>{@link I_CmsHistoryResource}</code> objects
     * 
     * @throws CmsException if something goes wrong
     */
    public List<I_CmsHistoryResource> readAllAvailableVersions(CmsDbContext dbc, CmsResource resource)
            throws CmsException {

        // read the historical resources
        List<I_CmsHistoryResource> versions = getHistoryDriver(dbc).readAllAvailableVersions(dbc,
                resource.getStructureId());
        if ((versions.size() > OpenCms.getSystemInfo().getHistoryVersions())
                && (OpenCms.getSystemInfo().getHistoryVersions() > -1)) {
            return versions.subList(0, OpenCms.getSystemInfo().getHistoryVersions());
        }
        return versions;
    }

    /**
     * Reads all property definitions for the given mapping type.<p>
     *
     * @param dbc the current database context
     * 
     * @return a list with the <code>{@link CmsPropertyDefinition}</code> objects (may be empty)
     * 
     * @throws CmsException if something goes wrong
     */
    public List<CmsPropertyDefinition> readAllPropertyDefinitions(CmsDbContext dbc) throws CmsException {

        List<CmsPropertyDefinition> result = getVfsDriver(dbc).readPropertyDefinitions(dbc,
                dbc.currentProject().getUuid());
        Collections.sort(result);
        return result;
    }

    /**
     * Returns all resources subscribed by the given user or group.<p>
     * 
     * @param dbc the database context
     * @param poolName the name of the database pool to use
     * @param principal the principal to read the subscribed resources
     * 
     * @return all resources subscribed by the given user or group
     * 
     * @throws CmsException if something goes wrong
     */
    public List<CmsResource> readAllSubscribedResources(CmsDbContext dbc, String poolName, CmsPrincipal principal)
            throws CmsException {

        List<CmsResource> result = getSubscriptionDriver().readAllSubscribedResources(dbc, poolName, principal);
        result = filterPermissions(dbc, result, CmsResourceFilter.DEFAULT);
        return result;
    }

    /**
     * Selects the best url name for a given resource and locale.<p>
     * 
     * @param dbc the database context 
     * @param id the resource's structure id 
     * @param locale the requested locale 
     * @param defaultLocales the default locales to use if the locale isn't available 
     * 
     * @return the URL name which was found 
     * 
     * @throws CmsDataAccessException if the database operation failed 
     */
    public String readBestUrlName(CmsDbContext dbc, CmsUUID id, Locale locale, List<Locale> defaultLocales)
            throws CmsDataAccessException {

        List<CmsUrlNameMappingEntry> entries = getVfsDriver(dbc).readUrlNameMappingEntries(dbc,
                dbc.currentProject().isOnlineProject(), CmsUrlNameMappingFilter.ALL.filterStructureId(id));
        if (entries.isEmpty()) {
            return null;
        }

        ArrayListMultimap<String, CmsUrlNameMappingEntry> entriesByLocale = ArrayListMultimap.create();
        for (CmsUrlNameMappingEntry entry : entries) {
            entriesByLocale.put(entry.getLocale(), entry);
        }
        List<CmsUrlNameMappingEntry> lastEntries = new ArrayList<CmsUrlNameMappingEntry>();
        Comparator<CmsUrlNameMappingEntry> dateChangedComparator = new UrlNameMappingComparator();
        for (String localeKey : entriesByLocale.keySet()) {
            // for each locale select the latest mapping entry 
            CmsUrlNameMappingEntry latestEntryForLocale = Collections.max(entriesByLocale.get(localeKey),
                    dateChangedComparator);
            lastEntries.add(latestEntryForLocale);
        }
        CmsLocaleManager localeManager = OpenCms.getLocaleManager();
        List<Locale> availableLocales = new ArrayList<Locale>();
        for (CmsUrlNameMappingEntry entry : lastEntries) {
            availableLocales.add(CmsLocaleManager.getLocale(entry.getLocale()));
        }
        Locale bestLocale = localeManager.getBestMatchingLocale(locale, defaultLocales, availableLocales);
        String bestLocaleStr = bestLocale.getLanguage();
        for (CmsUrlNameMappingEntry entry : lastEntries) {
            if (entry.getLocale().equals(bestLocaleStr)) {
                return entry.getName();
            }
        }
        return null;
    }

    /**
     * Returns the child resources of a resource, that is the resources
     * contained in a folder.<p>
     * 
     * With the parameters <code>getFolders</code> and <code>getFiles</code>
     * you can control what type of resources you want in the result list:
     * files, folders, or both.<p>
     * 
     * This method is mainly used by the workplace explorer.<p> 
     * 
     * @param dbc the current database context
     * @param resource the resource to return the child resources for
     * @param filter the resource filter to use
     * @param getFolders if true the child folders are included in the result
     * @param getFiles if true the child files are included in the result
     * @param checkPermissions if the resources should be filtered with the current user permissions
     * 
     * @return a list of all child resources
     * 
     * @throws CmsException if something goes wrong
     */
    public List<CmsResource> readChildResources(CmsDbContext dbc, CmsResource resource, CmsResourceFilter filter,
            boolean getFolders, boolean getFiles, boolean checkPermissions) throws CmsException {

        String cacheKey = null;
        List<CmsResource> resourceList = null;
        if (m_monitor.isEnabled(CmsMemoryMonitor.CacheType.RESOURCE_LIST)) { // check this here to skip the complex cache key generation
            String time = "";
            if (checkPermissions) {
                // ensure correct caching if site time offset is set
                if ((dbc.getRequestContext() != null) && (OpenCms.getSiteManager()
                        .getSiteForSiteRoot(dbc.getRequestContext().getSiteRoot()) != null)) {
                    time += OpenCms.getSiteManager().getSiteForSiteRoot(dbc.getRequestContext().getSiteRoot())
                            .getSiteMatcher().getTimeOffset();
                }
            }
            // try to get the sub resources from the cache
            cacheKey = getCacheKey(new String[] { dbc.currentUser().getName(),
                    getFolders ? (getFiles ? CmsCacheKey.CACHE_KEY_SUBALL : CmsCacheKey.CACHE_KEY_SUBFOLDERS)
                            : CmsCacheKey.CACHE_KEY_SUBFILES,
                    checkPermissions ? "+" + time : "-", filter.getCacheId(), resource.getRootPath() }, dbc);

            resourceList = m_monitor.getCachedResourceList(cacheKey);
        }
        if ((resourceList == null) || !dbc.getProjectId().isNullUUID()) {
            // read the result form the database
            resourceList = getVfsDriver(dbc).readChildResources(dbc, dbc.currentProject(), resource, getFolders,
                    getFiles);

            if (checkPermissions) {
                // apply the permission filter
                resourceList = filterPermissions(dbc, resourceList, filter);
            }
            // cache the sub resources
            if (dbc.getProjectId().isNullUUID()) {
                m_monitor.cacheResourceList(cacheKey, resourceList);
            }
        }

        // we must always apply the result filter and update the context dates
        return updateContextDates(dbc, resourceList, filter);
    }

    /**
     * Returns the default file for the given folder.<p>
     * 
     * If the given resource is a file, then this file is returned.<p>
     * 
     * Otherwise, in case of a folder:<br> 
     * <ol>
     *   <li>the {@link CmsPropertyDefinition#PROPERTY_DEFAULT_FILE} is checked, and
     *   <li>if still no file could be found, the configured default files in the 
     *       <code>opencms-vfs.xml</code> configuration are iterated until a match is 
     *       found, and
     *   <li>if still no file could be found, <code>null</code> is retuned
     * </ol>
     * 
     * @param dbc the database context
     * @param resource the folder to get the default file for
     * @param resourceFilter the resource filter
     * 
     * @return the default file for the given folder
     */
    public CmsResource readDefaultFile(CmsDbContext dbc, CmsResource resource, CmsResourceFilter resourceFilter) {

        // resource exists, lets check if we have a file or a folder
        if (resource.isFolder()) {
            // the resource is a folder, check if PROPERTY_DEFAULT_FILE is set on folder
            try {
                String defaultFileName = readPropertyObject(dbc, resource,
                        CmsPropertyDefinition.PROPERTY_DEFAULT_FILE, false).getValue();
                // check if the default file property does not match the navigation level folder marker value
                if (defaultFileName != null) {
                    // property was set, so look up this file first
                    String folderName = CmsResource.getFolderPath(resource.getRootPath());
                    resource = readResource(dbc, folderName + defaultFileName, resourceFilter.addRequireFile());
                }
            } catch (CmsException e) {
                // ignore all other exceptions and continue the lookup process
                if (LOG.isDebugEnabled()) {
                    LOG.debug(e.getLocalizedMessage(), e);
                }
            }
            if (resource.isFolder()) {
                String folderName = CmsResource.getFolderPath(resource.getRootPath());
                // resource is (still) a folder, check default files specified in configuration
                Iterator<String> it = OpenCms.getDefaultFiles().iterator();
                while (it.hasNext()) {
                    String tmpResourceName = folderName + it.next();
                    try {
                        resource = readResource(dbc, tmpResourceName, resourceFilter.addRequireFile());
                        // no exception? So we have found the default file
                        // stop looking for default files   
                        break;
                    } catch (CmsException e) {
                        // ignore all other exceptions and continue the lookup process
                        if (LOG.isDebugEnabled()) {
                            LOG.debug(e.getLocalizedMessage(), e);
                        }
                    }
                }
            }
        }
        if (resource.isFolder()) {
            // we only want files as a result for further processing
            resource = null;
        }
        return resource;
    }

    /**
     * Reads all deleted (historical) resources below the given path, 
     * including the full tree below the path, if required.<p>
     * 
     * @param dbc the current db context
     * @param resource the parent resource to read the resources from
     * @param readTree <code>true</code> to read all subresources
     * @param isVfsManager <code>true</code> if the current user has the vfs manager role
     * 
     * @return a list of <code>{@link I_CmsHistoryResource}</code> objects
     * 
     * @throws CmsException if something goes wrong
     * 
     * @see CmsObject#readResource(CmsUUID, int)
     * @see CmsObject#readResources(String, CmsResourceFilter, boolean)
     * @see CmsObject#readDeletedResources(String, boolean)
     */
    public List<I_CmsHistoryResource> readDeletedResources(CmsDbContext dbc, CmsResource resource, boolean readTree,
            boolean isVfsManager) throws CmsException {

        Set<I_CmsHistoryResource> result = new HashSet<I_CmsHistoryResource>();
        List<I_CmsHistoryResource> deletedResources;
        dbc.getRequestContext().setAttribute("ATTR_RESOURCE_NAME", resource.getRootPath());
        try {
            deletedResources = getHistoryDriver(dbc).readDeletedResources(dbc, resource.getStructureId(),
                    isVfsManager ? null : dbc.currentUser().getId());
        } finally {
            dbc.getRequestContext().removeAttribute("ATTR_RESOURCE_NAME");
        }
        result.addAll(deletedResources);
        Set<I_CmsHistoryResource> newResult = new HashSet<I_CmsHistoryResource>(result.size());
        I_CmsVfsDriver vfsDriver = getVfsDriver(dbc);
        Iterator<I_CmsHistoryResource> it = result.iterator();
        while (it.hasNext()) {
            I_CmsHistoryResource histRes = it.next();
            // adjust the paths
            try {
                if (vfsDriver.validateStructureIdExists(dbc, dbc.currentProject().getUuid(),
                        histRes.getStructureId())) {
                    newResult.add(histRes);
                    continue;
                }
                // adjust the path in case of deleted files
                String resourcePath = histRes.getRootPath();
                String resName = CmsResource.getName(resourcePath);
                String path = CmsResource.getParentFolder(resourcePath);

                CmsUUID parentId = histRes.getParentId();
                try {
                    // first look for the path through the parent id
                    path = readResource(dbc, parentId, CmsResourceFilter.IGNORE_EXPIRATION).getRootPath();
                } catch (CmsDataAccessException e) {
                    // if the resource with the parent id is not found, try to get a new parent id with the path
                    try {
                        parentId = readResource(dbc, path, CmsResourceFilter.IGNORE_EXPIRATION).getStructureId();
                    } catch (CmsDataAccessException e1) {
                        // ignore, the parent folder has been completely deleted
                    }
                }
                resourcePath = path + resName;

                boolean isFolder = resourcePath.endsWith("/");
                if (isFolder) {
                    newResult.add(new CmsHistoryFolder(histRes.getPublishTag(), histRes.getStructureId(),
                            histRes.getResourceId(), resourcePath, histRes.getTypeId(), histRes.getFlags(),
                            histRes.getProjectLastModified(), histRes.getState(), histRes.getDateCreated(),
                            histRes.getUserCreated(), histRes.getDateLastModified(), histRes.getUserLastModified(),
                            histRes.getDateReleased(), histRes.getDateExpired(), histRes.getVersion(), parentId,
                            histRes.getResourceVersion(), histRes.getStructureVersion()));
                } else {
                    newResult.add(new CmsHistoryFile(histRes.getPublishTag(), histRes.getStructureId(),
                            histRes.getResourceId(), resourcePath, histRes.getTypeId(), histRes.getFlags(),
                            histRes.getProjectLastModified(), histRes.getState(), histRes.getDateCreated(),
                            histRes.getUserCreated(), histRes.getDateLastModified(), histRes.getUserLastModified(),
                            histRes.getDateReleased(), histRes.getDateExpired(), histRes.getLength(),
                            histRes.getDateContent(), histRes.getVersion(), parentId, null,
                            histRes.getResourceVersion(), histRes.getStructureVersion()));
                }
            } catch (CmsDataAccessException e) {
                // should never happen
                if (LOG.isErrorEnabled()) {
                    LOG.error(e.getLocalizedMessage(), e);
                }
            }
        }
        if (readTree) {
            Iterator<I_CmsHistoryResource> itDeleted = deletedResources.iterator();
            while (itDeleted.hasNext()) {
                I_CmsHistoryResource delResource = itDeleted.next();
                if (delResource.isFolder()) {
                    newResult.addAll(readDeletedResources(dbc, (CmsFolder) delResource, readTree, isVfsManager));
                }
            }
            try {
                readResource(dbc, resource.getStructureId(), CmsResourceFilter.ALL);
                // resource exists, so recurse
                Iterator<CmsResource> itResources = readResources(dbc, resource,
                        CmsResourceFilter.ALL.addRequireFolder(), readTree).iterator();
                while (itResources.hasNext()) {
                    CmsResource subResource = itResources.next();
                    if (subResource.isFolder()) {
                        newResult.addAll(readDeletedResources(dbc, subResource, readTree, isVfsManager));
                    }
                }
            } catch (Exception e) {
                // resource does not exists
                if (LOG.isDebugEnabled()) {
                    LOG.debug(e.getLocalizedMessage(), e);
                }
            }
        }
        List<I_CmsHistoryResource> finalRes = new ArrayList<I_CmsHistoryResource>(newResult);
        Collections.sort(finalRes, I_CmsResource.COMPARE_ROOT_PATH);
        return finalRes;
    }

    /**
     * Reads a file resource (including it's binary content) from the VFS,
     * using the specified resource filter.<p>
     * 
     * In case you do not need the file content, 
     * use <code>{@link #readResource(CmsDbContext, String, CmsResourceFilter)}</code> instead.<p>
     * 
     * The specified filter controls what kind of resources should be "found" 
     * during the read operation. This will depend on the application. For example, 
     * using <code>{@link CmsResourceFilter#DEFAULT}</code> will only return currently
     * "valid" resources, while using <code>{@link CmsResourceFilter#IGNORE_EXPIRATION}</code>
     * will ignore the date release / date expired information of the resource.<p>
     * 
     * @param dbc the current database context
     * @param resource the base file resource (without content)
     * @return the file read from the VFS
     * @throws CmsException if operation was not successful
     */
    public CmsFile readFile(CmsDbContext dbc, CmsResource resource) throws CmsException {

        if (resource.isFolder()) {
            throw new CmsVfsResourceNotFoundException(Messages.get().container(Messages.ERR_ACCESS_FOLDER_AS_FILE_1,
                    dbc.removeSiteRoot(resource.getRootPath())));
        }

        CmsUUID projectId = dbc.currentProject().getUuid();
        CmsFile file = null;
        if (resource instanceof I_CmsHistoryResource) {
            file = new CmsHistoryFile((I_CmsHistoryResource) resource);
            file.setContents(getHistoryDriver(dbc).readContent(dbc, resource.getResourceId(),
                    ((I_CmsHistoryResource) resource).getPublishTag()));
        } else {
            file = new CmsFile(resource);
            file.setContents(getVfsDriver(dbc).readContent(dbc, projectId, resource.getResourceId()));
        }
        return file;
    }

    /**
     * Reads a folder from the VFS,
     * using the specified resource filter.<p>
     * 
     * @param dbc the current database context
     * @param resourcename the name of the folder to read (full path)
     * @param filter the resource filter to use while reading
     *
     * @return the folder that was read
     *
     * @throws CmsDataAccessException if something goes wrong
     *
     * @see #readResource(CmsDbContext, String, CmsResourceFilter)
     * @see CmsObject#readFolder(String)
     * @see CmsObject#readFolder(String, CmsResourceFilter)
     */
    public CmsFolder readFolder(CmsDbContext dbc, String resourcename, CmsResourceFilter filter)
            throws CmsDataAccessException {

        CmsResource resource = readResource(dbc, resourcename, filter);

        return convertResourceToFolder(resource);
    }

    /**
     * Reads the group of a project.<p>
     *
     * @param dbc the current database context
     * @param project the project to read from
     * 
     * @return the group of a resource
     */
    public CmsGroup readGroup(CmsDbContext dbc, CmsProject project) {

        try {
            return readGroup(dbc, project.getGroupId());
        } catch (CmsException exc) {
            return new CmsGroup(CmsUUID.getNullUUID(), CmsUUID.getNullUUID(), project.getGroupId() + "",
                    "deleted group", 0);
        }
    }

    /**
     * Reads a group based on its id.<p>
     *
     * @param dbc the current database context
     * @param groupId the id of the group that is to be read
     * 
     * @return the requested group
     * 
     * @throws CmsException if operation was not successful
     */
    public CmsGroup readGroup(CmsDbContext dbc, CmsUUID groupId) throws CmsException {

        CmsGroup group = null;
        // try to read group from cache
        group = m_monitor.getCachedGroup(groupId.toString());
        if (group == null) {
            group = getUserDriver(dbc).readGroup(dbc, groupId);
            m_monitor.cacheGroup(group);
        }
        return group;
    }

    /**
     * Reads a group based on its name.<p>
     * 
     * @param dbc the current database context
     * @param groupname the name of the group that is to be read
     *
     * @return the requested group
     * 
     * @throws CmsDataAccessException if operation was not successful
     */
    public CmsGroup readGroup(CmsDbContext dbc, String groupname) throws CmsDataAccessException {

        CmsGroup group = null;
        // try to read group from cache
        group = m_monitor.getCachedGroup(groupname);
        if (group == null) {
            group = getUserDriver(dbc).readGroup(dbc, groupname);
            m_monitor.cacheGroup(group);
        }
        return group;
    }

    /**
     * Reads a principal (an user or group) from the historical archive based on its ID.<p>
     * 
     * @param dbc the current database context
     * @param principalId the id of the principal to read
     * 
     * @return the historical principal entry with the given id
     * 
     * @throws CmsException if something goes wrong, ie. {@link CmsDbEntryNotFoundException}
     * 
     * @see CmsObject#readUser(CmsUUID)
     * @see CmsObject#readGroup(CmsUUID)
     * @see CmsObject#readHistoryPrincipal(CmsUUID)
     */
    public CmsHistoryPrincipal readHistoricalPrincipal(CmsDbContext dbc, CmsUUID principalId) throws CmsException {

        return getHistoryDriver(dbc).readPrincipal(dbc, principalId);
    }

    /**
     * Returns the latest historical project entry with the given id.<p>
     *
     * @param dbc the current database context
     * @param projectId the project id
     * 
     * @return the requested historical project entry
     * 
     * @throws CmsException if something goes wrong
     */
    public CmsHistoryProject readHistoryProject(CmsDbContext dbc, CmsUUID projectId) throws CmsException {

        return getHistoryDriver(dbc).readProject(dbc, projectId);
    }

    /**
     * Returns a historical project entry.<p>
     *
     * @param dbc the current database context
     * @param publishTag the publish tag of the project
     * 
     * @return the requested historical project entry
     * 
     * @throws CmsException if something goes wrong
     */
    public CmsHistoryProject readHistoryProject(CmsDbContext dbc, int publishTag) throws CmsException {

        return getHistoryDriver(dbc).readProject(dbc, publishTag);
    }

    /**
     * Reads the list of all <code>{@link CmsProperty}</code> objects that belongs to the given historical resource.<p>
     * 
     * @param dbc the current database context
     * @param historyResource the historical resource to read the properties for
     * 
     * @return the list of <code>{@link CmsProperty}</code> objects
     * 
     * @throws CmsException if something goes wrong
     */
    public List<CmsProperty> readHistoryPropertyObjects(CmsDbContext dbc, I_CmsHistoryResource historyResource)
            throws CmsException {

        return getHistoryDriver(dbc).readProperties(dbc, historyResource);
    }

    /**
     * Reads the structure id which is mapped to a given URL name.<p>
     * 
     * @param dbc the current database context 
     * @param name the name for which the mapped structure id should be looked up
     *  
     * @return the structure id which is mapped to the given name, or null if there is no such id 
     * 
     * @throws CmsDataAccessException if something goes wrong 
     */
    public CmsUUID readIdForUrlName(CmsDbContext dbc, String name) throws CmsDataAccessException {

        List<CmsUrlNameMappingEntry> entries = getVfsDriver(dbc).readUrlNameMappingEntries(dbc,
                dbc.currentProject().isOnlineProject(), CmsUrlNameMappingFilter.ALL.filterName(name));
        if (entries.isEmpty()) {
            return null;
        }
        return entries.get(0).getStructureId();
    }

    /**
     * Reads the locks that were saved to the database in the previous run of OpenCms.<p>
     * 
     * @param dbc the current database context
     * 
     * @throws CmsException if something goes wrong
     */
    public void readLocks(CmsDbContext dbc) throws CmsException {

        m_lockManager.readLocks(dbc);
    }

    /**
     * Reads the manager group of a project.<p>
     *
     * @param dbc the current database context
     * @param project the project to read from
     * 
     * @return the group of a resource
     */
    public CmsGroup readManagerGroup(CmsDbContext dbc, CmsProject project) {

        try {
            return readGroup(dbc, project.getManagerGroupId());
        } catch (CmsException exc) {
            // the group does not exist any more - return a dummy-group
            return new CmsGroup(CmsUUID.getNullUUID(), CmsUUID.getNullUUID(), project.getManagerGroupId() + "",
                    "deleted group", 0);
        }
    }

    /**
     * Reads the URL name which has been most recently mapped to the given structure id, or null
     * if no URL name is mapped to the id.<p>
     * 
     * @param dbc the current database context 
     * @param id a structure id 
     * @return the name which has been most recently mapped to the given structure id 
     * 
     * @throws CmsDataAccessException if something goes wrong 
     */
    public String readNewestUrlNameForId(CmsDbContext dbc, CmsUUID id) throws CmsDataAccessException {

        List<CmsUrlNameMappingEntry> entries = getVfsDriver(dbc).readUrlNameMappingEntries(dbc,
                dbc.currentProject().isOnlineProject(), CmsUrlNameMappingFilter.ALL.filterStructureId(id));
        if (entries.isEmpty()) {
            return null;
        }

        Collections.sort(entries, new UrlNameMappingComparator());
        CmsUrlNameMappingEntry lastEntry = entries.get(entries.size() - 1);
        return lastEntry.getName();
    }

    /**
     * Reads an organizational Unit based on its fully qualified name.<p>
     *
     * @param dbc the current db context
     * @param ouFqn the fully qualified name of the organizational Unit to be read
     * 
     * @return the organizational Unit that with the provided fully qualified name
     * 
     * @throws CmsException if something goes wrong
     */
    public CmsOrganizationalUnit readOrganizationalUnit(CmsDbContext dbc, String ouFqn) throws CmsException {

        CmsOrganizationalUnit organizationalUnit = null;
        // try to read organizational unit from cache
        organizationalUnit = m_monitor.getCachedOrgUnit(ouFqn);
        if (organizationalUnit == null) {
            organizationalUnit = getUserDriver(dbc).readOrganizationalUnit(dbc, ouFqn);
            m_monitor.cacheOrgUnit(organizationalUnit);
        }
        return organizationalUnit;
    }

    /**
     * Reads the owner of a project.<p>
     *
     * @param dbc the current database context
     * @param project the project to get the owner from
     * 
     * @return the owner of a resource
     * @throws CmsException if something goes wrong
     */
    public CmsUser readOwner(CmsDbContext dbc, CmsProject project) throws CmsException {

        return readUser(dbc, project.getOwnerId());
    }

    /**
     * Reads the parent folder to a given structure id.<p>
     * 
     * @param dbc the current database context
     * @param structureId the structure id of the child
     * 
     * @return the parent folder resource
     * 
     * @throws CmsDataAccessException if something goes wrong
     */
    public CmsResource readParentFolder(CmsDbContext dbc, CmsUUID structureId) throws CmsDataAccessException {

        return getVfsDriver(dbc).readParentFolder(dbc, dbc.currentProject().getUuid(), structureId);
    }

    /**
     * Builds a list of resources for a given path.<p>
     * 
     * @param dbc the current database context
     * @param path the requested path
     * @param filter a filter object (only "includeDeleted" information is used!)
     * 
     * @return list of <code>{@link CmsResource}</code>s
     * 
     * @throws CmsException if something goes wrong
     */
    public List<CmsResource> readPath(CmsDbContext dbc, String path, CmsResourceFilter filter) throws CmsException {

        // splits the path into folder and filename tokens
        List<String> tokens = CmsStringUtil.splitAsList(path, '/');

        // the root folder is no token in the path but a resource which has to be added to the path
        int count = tokens.size() + 1;
        // holds the CmsResource instances in the path
        List<CmsResource> pathList = new ArrayList<CmsResource>(count);

        // true if the path doesn't end with a folder
        boolean lastResourceIsFile = false;
        // number of folders in the path
        int folderCount = count;
        if (!path.endsWith("/")) {
            folderCount--;
            lastResourceIsFile = true;
        }

        // read the root folder, because it's ID is required to read any sub-resources
        String currentResourceName = "/";
        StringBuffer currentPath = new StringBuffer(64);
        currentPath.append('/');

        String cp = currentPath.toString();
        CmsUUID projectId = getProjectIdForContext(dbc);

        // key to cache the resources
        String cacheKey = getCacheKey(null, false, projectId, cp);
        // the current resource
        CmsResource currentResource = m_monitor.getCachedResource(cacheKey);
        if ((currentResource == null) || !dbc.getProjectId().isNullUUID()) {
            currentResource = getVfsDriver(dbc).readFolder(dbc, projectId, cp);
            if (dbc.getProjectId().isNullUUID()) {
                m_monitor.cacheResource(cacheKey, currentResource);
            }
        }

        pathList.add(0, currentResource);

        if (count == 1) {
            // the root folder was requested- no further operations required
            return pathList;
        }

        Iterator<String> it = tokens.iterator();
        currentResourceName = it.next();

        // read the folder resources in the path /a/b/c/
        int i = 0;
        for (i = 1; i < folderCount; i++) {
            currentPath.append(currentResourceName);
            currentPath.append('/');
            // read the folder
            cp = currentPath.toString();
            cacheKey = getCacheKey(null, false, projectId, cp);
            currentResource = m_monitor.getCachedResource(cacheKey);
            if ((currentResource == null) || !dbc.getProjectId().isNullUUID()) {
                currentResource = getVfsDriver(dbc).readFolder(dbc, projectId, cp);
                if (dbc.getProjectId().isNullUUID()) {
                    m_monitor.cacheResource(cacheKey, currentResource);
                }
            }

            pathList.add(i, currentResource);

            if (i < (folderCount - 1)) {
                currentResourceName = it.next();
            }
        }

        // read the (optional) last file resource in the path /x.html
        if (lastResourceIsFile) {
            if (it.hasNext()) {
                // this will only be false if a resource in the 
                // top level root folder (e.g. "/index.html") was requested
                currentResourceName = it.next();
            }
            currentPath.append(currentResourceName);

            // read the file
            cp = currentPath.toString();
            cacheKey = getCacheKey(null, false, projectId, cp);
            currentResource = m_monitor.getCachedResource(cacheKey);
            if ((currentResource == null) || !dbc.getProjectId().isNullUUID()) {
                currentResource = getVfsDriver(dbc).readResource(dbc, projectId, cp, filter.includeDeleted());
                if (dbc.getProjectId().isNullUUID()) {
                    m_monitor.cacheResource(cacheKey, currentResource);
                }
            }

            pathList.add(i, currentResource);
        }

        return pathList;
    }

    /**
     * Reads a project given the projects id.<p>
     *
     * @param dbc the current database context
     * @param id the id of the project
     * 
     * @return the project read
     * 
     * @throws CmsDataAccessException if something goes wrong
     */
    public CmsProject readProject(CmsDbContext dbc, CmsUUID id) throws CmsDataAccessException {

        CmsProject project = null;
        project = m_monitor.getCachedProject(id.toString());
        if (project == null) {
            project = getProjectDriver(dbc).readProject(dbc, id);
            m_monitor.cacheProject(project);
        }
        return project;
    }

    /**
     * Reads a project.<p>
     *
     * Important: Since a project name can be used multiple times, this is NOT the most efficient 
     * way to read the project. This is only a convenience for front end developing.
     * Reading a project by name will return the first project with that name. 
     * All core classes must use the id version {@link #readProject(CmsDbContext, CmsUUID)} to ensure the right project is read.<p>
     * 
     * @param dbc the current database context
     * @param name the name of the project
     * 
     * @return the project read
     * 
     * @throws CmsException if something goes wrong
     */
    public CmsProject readProject(CmsDbContext dbc, String name) throws CmsException {

        CmsProject project = null;
        project = m_monitor.getCachedProject(name);
        if (project == null) {
            project = getProjectDriver(dbc).readProject(dbc, name);
            m_monitor.cacheProject(project);
        }
        return project;
    }

    /**
     * Returns the list of all resource names that define the "view" of the given project.<p>
     *
     * @param dbc the current database context
     * @param project the project to get the project resources for
     * 
     * @return the list of all resources, as <code>{@link String}</code> objects 
     *              that define the "view" of the given project.
     * 
     * @throws CmsException if something goes wrong
     */
    public List<String> readProjectResources(CmsDbContext dbc, CmsProject project) throws CmsException {

        return getProjectDriver(dbc).readProjectResources(dbc, project);
    }

    /**
     * Reads all resources of a project that match a given state from the VFS.<p>
     * 
     * Possible values for the <code>state</code> parameter are:<br>
     * <ul>
     * <li><code>{@link CmsResource#STATE_CHANGED}</code>: Read all "changed" resources in the project</li>
     * <li><code>{@link CmsResource#STATE_NEW}</code>: Read all "new" resources in the project</li>
     * <li><code>{@link CmsResource#STATE_DELETED}</code>: Read all "deleted" resources in the project</li>
     * <li><code>{@link CmsResource#STATE_KEEP}</code>: Read all resources either "changed", "new" or "deleted" in the project</li>
     * </ul><p>
     * 
     * @param dbc the current database context
     * @param projectId the id of the project to read the file resources for
     * @param state the resource state to match 
     *
     * @return a list of <code>{@link CmsResource}</code> objects matching the filter criteria
     * 
     * @throws CmsException if something goes wrong
     * 
     * @see CmsObject#readProjectView(CmsUUID, CmsResourceState)
     */
    public List<CmsResource> readProjectView(CmsDbContext dbc, CmsUUID projectId, CmsResourceState state)
            throws CmsException {

        List<CmsResource> resources;
        if (state.isNew() || state.isChanged() || state.isDeleted()) {
            // get all resources form the database that match the selected state
            resources = getVfsDriver(dbc).readResources(dbc, projectId, state,
                    CmsDriverManager.READMODE_MATCHSTATE);
        } else {
            // get all resources form the database that are somehow changed (i.e. not unchanged)
            resources = getVfsDriver(dbc).readResources(dbc, projectId, CmsResource.STATE_UNCHANGED,
                    CmsDriverManager.READMODE_UNMATCHSTATE);
        }

        // filter the permissions
        List<CmsResource> result = filterPermissions(dbc, resources, CmsResourceFilter.ALL);
        // sort the result
        Collections.sort(result);
        // set the full resource names
        return updateContextDates(dbc, result);
    }

    /**
     * Reads a property definition.<p>
     *
     * If no property definition with the given name is found, 
     * <code>null</code> is returned.<p>
     * 
     * @param dbc the current database context
     * @param name the name of the property definition to read
     *
     * @return the property definition that was read
     * 
     * @throws CmsException a CmsDbEntryNotFoundException is thrown if the property definition does not exist
     */
    public CmsPropertyDefinition readPropertyDefinition(CmsDbContext dbc, String name) throws CmsException {

        return getVfsDriver(dbc).readPropertyDefinition(dbc, name, dbc.currentProject().getUuid());
    }

    /**
     * Reads a property object from a resource specified by a property name.<p>
     * 
     * Returns <code>{@link CmsProperty#getNullProperty()}</code> if the property is not found.<p>
     * 
     * @param dbc the current database context
     * @param resource the resource where the property is read from
     * @param key the property key name
     * @param search if <code>true</code>, the property is searched on all parent folders of the resource. 
     *      if it's not found attached directly to the resource.
     * 
     * @return the required property, or <code>{@link CmsProperty#getNullProperty()}</code> if the property was not found
     * 
     * @throws CmsException if something goes wrong
     */
    public CmsProperty readPropertyObject(CmsDbContext dbc, CmsResource resource, String key, boolean search)
            throws CmsException {

        // use the list reading method to obtain all properties for the resource
        List<CmsProperty> properties = readPropertyObjects(dbc, resource, search);
        // create a lookup propertry object and look this up in the result map
        int i = properties.indexOf(new CmsProperty(key, null, null));
        CmsProperty result;
        if (i >= 0) {
            // property has been found in the map
            result = properties.get(i);
        } else {
            // property is not defined, return NULL property
            result = CmsProperty.getNullProperty();
        }
        // ensure the result value is not frozen
        return result.cloneAsProperty();
    }

    /**
     * Reads all property objects mapped to a specified resource from the database.<p>
     * 
     * All properties in the result List will be in frozen (read only) state, so you can't change the values.<p>
     * 
     * Returns an empty list if no properties are found at all.<p>
     * 
     * @param dbc the current database context
     * @param resource the resource where the properties are read from
     * @param search true, if the properties should be searched on all parent folders  if not found on the resource
     * 
     * @return a list of CmsProperty objects containing the structure and/or resource value
     * 
     * @throws CmsException if something goes wrong
     * 
     * @see CmsObject#readPropertyObjects(String, boolean)
     */
    public List<CmsProperty> readPropertyObjects(CmsDbContext dbc, CmsResource resource, boolean search)
            throws CmsException {

        // check if we have the result already cached
        CmsUUID projectId = getProjectIdForContext(dbc);
        String cacheKey = getCacheKey(CACHE_ALL_PROPERTIES, search, projectId, resource.getRootPath());

        List<CmsProperty> properties = m_monitor.getCachedPropertyList(cacheKey);

        if ((properties == null) || !dbc.getProjectId().isNullUUID()) {
            // result not cached, let's look it up in the DB
            if (search) {
                boolean cont;
                properties = new ArrayList<CmsProperty>();
                List<CmsProperty> parentProperties = null;

                do {
                    try {
                        parentProperties = readPropertyObjects(dbc, resource, false);

                        // make sure properties from lower folders "overwrite" properties from upper folders
                        parentProperties.removeAll(properties);
                        parentProperties.addAll(properties);

                        properties.clear();
                        properties.addAll(parentProperties);

                        cont = resource.getRootPath().length() > 1;
                    } catch (CmsSecurityException se) {
                        // a security exception (probably no read permission) we return the current result                      
                        cont = false;
                    }
                    if (cont) {
                        // no permission check on parent folder is required since we must have "read" 
                        // permissions to read the child resource anyway
                        resource = readResource(dbc, CmsResource.getParentFolder(resource.getRootPath()),
                                CmsResourceFilter.ALL);
                    }
                } while (cont);
            } else {
                properties = getVfsDriver(dbc).readPropertyObjects(dbc, dbc.currentProject(), resource);
                //                for (CmsProperty prop : properties) {
                //                    prop.setOrigin(resource.getRootPath());
                //                }
            }

            // set all properties in the result list as frozen
            CmsProperty.setFrozen(properties);
            if (dbc.getProjectId().isNullUUID()) {
                // store the result in the cache if needed
                m_monitor.cachePropertyList(cacheKey, properties);
            }
        }

        return new ArrayList<CmsProperty>(properties);
    }

    /**
     * Reads the resources that were published in a publish task for a given publish history ID.<p>
     * 
     * @param dbc the current database context
     * @param publishHistoryId unique int ID to identify each publish task in the publish history
     * 
     * @return a list of <code>{@link org.opencms.db.CmsPublishedResource}</code> objects
     * 
     * @throws CmsException if something goes wrong
     */
    public List<CmsPublishedResource> readPublishedResources(CmsDbContext dbc, CmsUUID publishHistoryId)
            throws CmsException {

        String cacheKey = publishHistoryId.toString();
        List<CmsPublishedResource> resourceList = m_monitor.getCachedPublishedResources(cacheKey);
        if ((resourceList == null) || !dbc.getProjectId().isNullUUID()) {
            resourceList = getProjectDriver(dbc).readPublishedResources(dbc, publishHistoryId);
            // store the result in the cache
            if (dbc.getProjectId().isNullUUID()) {
                m_monitor.cachePublishedResources(cacheKey, resourceList);
            }
        }
        return resourceList;
    }

    /**
     * Reads a single publish job identified by its publish history id.<p>
     * 
     * @param dbc the current database context
     * @param publishHistoryId unique id to identify the publish job in the publish history
     * @return an object of type <code>{@link CmsPublishJobInfoBean}</code> 
     * 
     * @throws CmsException if something goes wrong
     */
    public CmsPublishJobInfoBean readPublishJob(CmsDbContext dbc, CmsUUID publishHistoryId) throws CmsException {

        return getProjectDriver(dbc).readPublishJob(dbc, publishHistoryId);
    }

    /**
     * Reads all available publish jobs.<p>
     * 
     * @param dbc the current database context
     * @param startTime the start of the time range for finish time
     * @param endTime the end of the time range for finish time
     * @return a list of objects of type <code>{@link CmsPublishJobInfoBean}</code>
     * 
     * @throws CmsException if something goes wrong
     */
    public List<CmsPublishJobInfoBean> readPublishJobs(CmsDbContext dbc, long startTime, long endTime)
            throws CmsException {

        return getProjectDriver(dbc).readPublishJobs(dbc, startTime, endTime);
    }

    /**
     * Reads the publish list assigned to a publish job.<p>
     * 
     * @param dbc the current database context
     * @param publishHistoryId the history id identifying the publish job
     * @return the assigned publish list
     * @throws CmsException if something goes wrong
     */
    public CmsPublishList readPublishList(CmsDbContext dbc, CmsUUID publishHistoryId) throws CmsException {

        return getProjectDriver(dbc).readPublishList(dbc, publishHistoryId);
    }

    /**
     * Reads the publish report assigned to a publish job.<p>
     * 
     * @param dbc the current database context
     * @param publishHistoryId the history id identifying the publish job  
     * @return the content of the assigned publish report
     * @throws CmsException if something goes wrong
     */
    public byte[] readPublishReportContents(CmsDbContext dbc, CmsUUID publishHistoryId) throws CmsException {

        return getProjectDriver(dbc).readPublishReportContents(dbc, publishHistoryId);
    }

    /**
     * Reads an historical resource entry for the given resource and with the given version number.<p>
     *
     * @param dbc the current db context
     * @param resource the resource to be read
     * @param version the version number to retrieve
     *
     * @return the resource that was read
     *
     * @throws CmsException if the resource could not be read for any reason
     * 
     * @see CmsObject#restoreResourceVersion(CmsUUID, int)
     * @see CmsObject#readResource(CmsUUID, int)
     */
    public I_CmsHistoryResource readResource(CmsDbContext dbc, CmsResource resource, int version)
            throws CmsException {

        Iterator<I_CmsHistoryResource> itVersions = getHistoryDriver(dbc)
                .readAllAvailableVersions(dbc, resource.getStructureId()).iterator();
        while (itVersions.hasNext()) {
            I_CmsHistoryResource histRes = itVersions.next();
            if (histRes.getVersion() == version) {
                return histRes;
            }
        }
        throw new CmsVfsResourceNotFoundException(org.opencms.db.generic.Messages.get().container(
                org.opencms.db.generic.Messages.ERR_HISTORY_FILE_NOT_FOUND_1, resource.getStructureId()));
    }

    /**
     * Reads a resource from the VFS, using the specified resource filter.<p>
     * 
     * @param dbc the current database context
     * @param structureID the structure id of the resource to read
     * @param filter the resource filter to use while reading
     *
     * @return the resource that was read
     *
     * @throws CmsDataAccessException if something goes wrong
     * 
     * @see CmsObject#readResource(CmsUUID, CmsResourceFilter)
     * @see CmsObject#readResource(CmsUUID)
     */
    public CmsResource readResource(CmsDbContext dbc, CmsUUID structureID, CmsResourceFilter filter)
            throws CmsDataAccessException {

        CmsUUID projectId = getProjectIdForContext(dbc);
        // please note: the filter will be applied in the security manager later
        CmsResource resource = getVfsDriver(dbc).readResource(dbc, projectId, structureID, filter.includeDeleted());

        // context dates need to be updated
        updateContextDates(dbc, resource);

        // return the resource
        return resource;
    }

    /**
     * Reads a resource from the VFS, using the specified resource filter.<p>
     * 
     * @param dbc the current database context
     * @param resourcePath the name of the resource to read (full path)
     * @param filter the resource filter to use while reading
     *
     * @return the resource that was read
     *
     * @throws CmsDataAccessException if something goes wrong
     * 
     * @see CmsObject#readResource(String, CmsResourceFilter)
     * @see CmsObject#readResource(String)
     * @see CmsObject#readFile(CmsResource)
     */
    public CmsResource readResource(CmsDbContext dbc, String resourcePath, CmsResourceFilter filter)
            throws CmsDataAccessException {

        CmsUUID projectId = getProjectIdForContext(dbc);
        // please note: the filter will be applied in the security manager later
        CmsResource resource = getVfsDriver(dbc).readResource(dbc, projectId, resourcePath,
                filter.includeDeleted());

        // context dates need to be updated 
        updateContextDates(dbc, resource);

        // return the resource
        return resource;
    }

    /**
     * Reads all resources below the given path matching the filter criteria,
     * including the full tree below the path only in case the <code>readTree</code> 
     * parameter is <code>true</code>.<p>
     * 
     * @param dbc the current database context
     * @param parent the parent path to read the resources from
     * @param filter the filter
     * @param readTree <code>true</code> to read all subresources
     * 
     * @return a list of <code>{@link CmsResource}</code> objects matching the filter criteria
     *  
     * @throws CmsDataAccessException if the bare reading of the resources fails
     * @throws CmsException if security and permission checks for the resources read fail 
     */
    public List<CmsResource> readResources(CmsDbContext dbc, CmsResource parent, CmsResourceFilter filter,
            boolean readTree) throws CmsException, CmsDataAccessException {

        // try to get the sub resources from the cache
        String cacheKey = getCacheKey(new String[] { dbc.currentUser().getName(), filter.getCacheId(),
                readTree ? "+" : "-", parent.getRootPath() }, dbc);

        List<CmsResource> resourceList = m_monitor.getCachedResourceList(cacheKey);
        if ((resourceList == null) || !dbc.getProjectId().isNullUUID()) {
            // read the result from the database
            resourceList = getVfsDriver(dbc).readResourceTree(dbc, dbc.currentProject().getUuid(),
                    (readTree ? parent.getRootPath() : parent.getStructureId().toString()), filter.getType(),
                    filter.getState(), filter.getModifiedAfter(), filter.getModifiedBefore(),
                    filter.getReleaseAfter(), filter.getReleaseBefore(), filter.getExpireAfter(),
                    filter.getExpireBefore(),
                    (readTree ? CmsDriverManager.READMODE_INCLUDE_TREE : CmsDriverManager.READMODE_EXCLUDE_TREE)
                            | (filter.excludeType() ? CmsDriverManager.READMODE_EXCLUDE_TYPE : 0)
                            | (filter.excludeState() ? CmsDriverManager.READMODE_EXCLUDE_STATE : 0)
                            | ((filter.getOnlyFolders() != null) ? (filter.getOnlyFolders().booleanValue()
                                    ? CmsDriverManager.READMODE_ONLY_FOLDERS
                                    : CmsDriverManager.READMODE_ONLY_FILES) : 0));

            // HACK: do not take care of permissions if reading organizational units
            if (!parent.getRootPath().startsWith("/system/orgunits/")) {
                // apply permission filter
                resourceList = filterPermissions(dbc, resourceList, filter);
            }
            // store the result in the resourceList cache
            if (dbc.getProjectId().isNullUUID()) {
                m_monitor.cacheResourceList(cacheKey, resourceList);
            }
        }
        // we must always apply the result filter and update the context dates
        return updateContextDates(dbc, resourceList, filter);
    }

    /**
     * Returns the resources that were visited by a user set in the filter.<p>
     * 
     * @param dbc the database context
     * @param poolName the name of the database pool to use
     * @param filter the filter that is used to get the visited resources
     * 
     * @return the resources that were visited by a user set in the filter
     * 
     * @throws CmsException if something goes wrong
     */
    public List<CmsResource> readResourcesVisitedBy(CmsDbContext dbc, String poolName, CmsVisitedByFilter filter)
            throws CmsException {

        List<CmsResource> result = getSubscriptionDriver().readResourcesVisitedBy(dbc, poolName, filter);
        result = filterPermissions(dbc, result, CmsResourceFilter.DEFAULT);
        return result;
    }

    /**
     * Reads all resources that have a value (containing the given value string) set 
     * for the specified property (definition) in the given path.<p>
     * 
     * Both individual and shared properties of a resource are checked.<p>
     *
     * If the <code>value</code> parameter is <code>null</code>, all resources having the
     * given property set are returned.<p>
     * 
     * @param dbc the current database context
     * @param folder the folder to get the resources with the property from
     * @param propertyDefinition the name of the property (definition) to check for
     * @param value the string to search in the value of the property
     * @param filter the resource filter to apply to the result set
     * 
     * @return a list of all <code>{@link CmsResource}</code> objects 
     *          that have a value set for the specified property.
     * 
     * @throws CmsException if something goes wrong
     */
    public List<CmsResource> readResourcesWithProperty(CmsDbContext dbc, CmsResource folder,
            String propertyDefinition, String value, CmsResourceFilter filter) throws CmsException {

        String cacheKey;
        if (value == null) {
            cacheKey = getCacheKey(new String[] { dbc.currentUser().getName(), folder.getRootPath(),
                    propertyDefinition, filter.getCacheId() }, dbc);
        } else {
            cacheKey = getCacheKey(new String[] { dbc.currentUser().getName(), folder.getRootPath(),
                    propertyDefinition, value, filter.getCacheId() }, dbc);
        }
        List<CmsResource> resourceList = m_monitor.getCachedResourceList(cacheKey);
        if ((resourceList == null) || !dbc.getProjectId().isNullUUID()) {
            // first read the property definition
            CmsPropertyDefinition propDef = readPropertyDefinition(dbc, propertyDefinition);
            // now read the list of resources that have a value set for the property definition
            resourceList = getVfsDriver(dbc).readResourcesWithProperty(dbc, dbc.currentProject().getUuid(),
                    propDef.getId(), folder.getRootPath(), value);
            // apply permission filter
            resourceList = filterPermissions(dbc, resourceList, filter);
            // store the result in the resourceList cache
            if (dbc.getProjectId().isNullUUID()) {
                m_monitor.cacheResourceList(cacheKey, resourceList);
            }
        }
        // we must always apply the result filter and update the context dates
        return updateContextDates(dbc, resourceList, filter);
    }

    /**
     * Returns the set of users that are responsible for a specific resource.<p>
     * 
     * @param dbc the current database context
     * @param resource the resource to get the responsible users from
     * 
     * @return the set of users that are responsible for a specific resource
     * 
     * @throws CmsException if something goes wrong
     */
    public Set<I_CmsPrincipal> readResponsiblePrincipals(CmsDbContext dbc, CmsResource resource)
            throws CmsException {

        Set<I_CmsPrincipal> result = new HashSet<I_CmsPrincipal>();
        Iterator<CmsAccessControlEntry> aces = getAccessControlEntries(dbc, resource, true).iterator();
        while (aces.hasNext()) {
            CmsAccessControlEntry ace = aces.next();
            if (ace.isResponsible()) {
                I_CmsPrincipal p = lookupPrincipal(dbc, ace.getPrincipal());
                if (p != null) {
                    result.add(p);
                }
            }
        }
        return result;
    }

    /**
     * Returns the set of users that are responsible for a specific resource.<p>
     * 
     * @param dbc the current database context
     * @param resource the resource to get the responsible users from
     * 
     * @return the set of users that are responsible for a specific resource
     * 
     * @throws CmsException if something goes wrong
     */
    public Set<CmsUser> readResponsibleUsers(CmsDbContext dbc, CmsResource resource) throws CmsException {

        Set<CmsUser> result = new HashSet<CmsUser>();
        Iterator<I_CmsPrincipal> principals = readResponsiblePrincipals(dbc, resource).iterator();
        while (principals.hasNext()) {
            I_CmsPrincipal principal = principals.next();
            if (principal.isGroup()) {
                try {
                    result.addAll(getUsersOfGroup(dbc, principal.getName(), true, false, false));
                } catch (CmsException e) {
                    if (LOG.isInfoEnabled()) {
                        LOG.info(e);
                    }
                }
            } else {
                result.add((CmsUser) principal);
            }
        }
        return result;
    }

    /**
     * Returns a List of all siblings of the specified resource,
     * the specified resource being always part of the result set.<p>
     * 
     * The result is a list of <code>{@link CmsResource}</code> objects.<p>
     * 
     * @param dbc the current database context
     * @param resource the resource to read the siblings for
     * @param filter a filter object
     * 
     * @return a list of <code>{@link CmsResource}</code> Objects that 
     *          are siblings to the specified resource, 
     *          including the specified resource itself
     * 
     * @throws CmsException if something goes wrong
     */
    public List<CmsResource> readSiblings(CmsDbContext dbc, CmsResource resource, CmsResourceFilter filter)
            throws CmsException {

        List<CmsResource> siblings = getVfsDriver(dbc).readSiblings(dbc, dbc.currentProject().getUuid(), resource,
                filter.includeDeleted());

        // important: there is no permission check done on the returned list of siblings
        // this is because of possible issues with the "publish all siblings" option,
        // moreover the user has read permission for the content through
        // the selected sibling anyway
        return updateContextDates(dbc, siblings, filter);
    }

    /**
     * Returns the parameters of a resource in the table of all published template resources.<p>
     *
     * @param dbc the current database context
     * @param rfsName the rfs name of the resource
     * 
     * @return the parameter string of the requested resource
     * 
     * @throws CmsException if something goes wrong
     */
    public String readStaticExportPublishedResourceParameters(CmsDbContext dbc, String rfsName)
            throws CmsException {

        return getProjectDriver(dbc).readStaticExportPublishedResourceParameters(dbc, rfsName);
    }

    /**
     * Returns a list of all template resources which must be processed during a static export.<p>
     * 
     * @param dbc the current database context
     * @param parameterResources flag for reading resources with parameters (1) or without (0)
     * @param timestamp for reading the data from the db
     * 
     * @return a list of template resources as <code>{@link String}</code> objects
     * 
     * @throws CmsException if something goes wrong
     */
    public List<String> readStaticExportResources(CmsDbContext dbc, int parameterResources, long timestamp)
            throws CmsException {

        return getProjectDriver(dbc).readStaticExportResources(dbc, parameterResources, timestamp);
    }

    /**
     * Returns the subscribed history resources that were deleted.<p>
     * 
     * @param dbc the database context
     * @param poolName the name of the database pool to use
     * @param user the user that subscribed to the resource
     * @param groups the groups to check subscribed resources for
     * @param parent the parent resource (folder) of the deleted resources, if <code>null</code> all deleted resources will be returned
     * @param includeSubFolders indicates if the sub folders of the specified folder path should be considered, too
     * @param deletedFrom the time stamp from which the resources should have been deleted 
     * 
     * @return the subscribed history resources that were deleted
     * 
     * @throws CmsException if something goes wrong
     */
    public List<I_CmsHistoryResource> readSubscribedDeletedResources(CmsDbContext dbc, String poolName,
            CmsUser user, List<CmsGroup> groups, CmsResource parent, boolean includeSubFolders, long deletedFrom)
            throws CmsException {

        List<I_CmsHistoryResource> result = getSubscriptionDriver().readSubscribedDeletedResources(dbc, poolName,
                user, groups, parent, includeSubFolders, deletedFrom);

        return result;
    }

    /**
     * Returns the resources that were subscribed by a user or group set in the filter.<p>
     * 
     * @param dbc the database context
     * @param poolName the name of the database pool to use
     * @param filter the filter that is used to get the subscribed resources
     * 
     * @return the resources that were subscribed by a user or group set in the filter
     * 
     * @throws CmsException if something goes wrong
     */
    public List<CmsResource> readSubscribedResources(CmsDbContext dbc, String poolName,
            CmsSubscriptionFilter filter) throws CmsException {

        List<CmsResource> result = getSubscriptionDriver().readSubscribedResources(dbc, poolName, filter);

        result = filterPermissions(dbc, result, CmsResourceFilter.DEFAULT);
        return result;
    }

    /**
     * Reads URL name mapping entries which match the given filter.<p>
     * 
     * @param dbc the database context
     * @param online if true, read online URL name mappings, else offline ones 
     * @param filter the filter for matching the URL name entries
     * 
     * @return the list of URL name mapping entries which match the given filter
     * 
     * @throws CmsDataAccessException if something goes wrong 
     */
    public List<CmsUrlNameMappingEntry> readUrlNameMappingEntries(CmsDbContext dbc, boolean online,
            CmsUrlNameMappingFilter filter) throws CmsDataAccessException {

        I_CmsVfsDriver vfsDriver = getVfsDriver(dbc);
        return vfsDriver.readUrlNameMappingEntries(dbc, online, filter);
    }

    /**
     * Reads the newest URL names of a resource for all locales.<p>
     *  
     * @param dbc the database context 
     * @param id the resource's structure id
     *  
     * @return the url names for the locales 
     * 
     * @throws CmsDataAccessException if the database operation failed 
     */
    public List<String> readUrlNamesForAllLocales(CmsDbContext dbc, CmsUUID id) throws CmsDataAccessException {

        List<String> result = new ArrayList<String>();
        List<CmsUrlNameMappingEntry> entries = getVfsDriver(dbc).readUrlNameMappingEntries(dbc,
                dbc.currentProject().isOnlineProject(), CmsUrlNameMappingFilter.ALL.filterStructureId(id));
        ArrayListMultimap<String, CmsUrlNameMappingEntry> entriesByLocale = ArrayListMultimap.create();
        for (CmsUrlNameMappingEntry entry : entries) {
            String localeKey = entry.getLocale();
            entriesByLocale.put(localeKey, entry);
        }

        for (String localeKey : entriesByLocale.keySet()) {
            List<CmsUrlNameMappingEntry> entrs = entriesByLocale.get(localeKey);
            CmsUrlNameMappingEntry maxEntryForLocale = Collections.max(entrs, new UrlNameMappingComparator());
            result.add(maxEntryForLocale.getName());
        }
        return result;
    }

    /**
     * Returns a user object based on the id of a user.<p>
     *
     * @param dbc the current database context
     * @param id the id of the user to read
     *
     * @return the user read
     * 
     * @throws CmsException if something goes wrong
     */
    public CmsUser readUser(CmsDbContext dbc, CmsUUID id) throws CmsException {

        CmsUser user = m_monitor.getCachedUser(id.toString());
        if (user == null) {
            user = getUserDriver(dbc).readUser(dbc, id);
            m_monitor.cacheUser(user);
        }
        return user;
    }

    /**
     * Returns a user object.<p>
     *
     * @param dbc the current database context
     * @param username the name of the user that is to be read
     *
     * @return user read
     * 
     * @throws CmsDataAccessException if operation was not successful
     */
    public CmsUser readUser(CmsDbContext dbc, String username) throws CmsDataAccessException {

        CmsUser user = m_monitor.getCachedUser(username);
        if (user == null) {
            user = getUserDriver(dbc).readUser(dbc, username);
            m_monitor.cacheUser(user);
        }
        return user;
    }

    /**
     * Returns a user object if the password for the user is correct.<p>
     *
     * If the user/pwd pair is not valid a <code>{@link CmsException}</code> is thrown.<p>
     *
     * @param dbc the current database context
     * @param username the username of the user that is to be read
     * @param password the password of the user that is to be read
     * 
     * @return user read
     * 
     * @throws CmsException if operation was not successful
     */
    public CmsUser readUser(CmsDbContext dbc, String username, String password) throws CmsException {

        // don't read user from cache here because password may have changed
        CmsUser user = getUserDriver(dbc).readUser(dbc, username, password, null);
        m_monitor.cacheUser(user);
        return user;
    }

    /**
     * Removes an access control entry for a given resource and principal.<p>
     * 
     * @param dbc the current database context
     * @param resource the resource
     * @param principal the id of the principal to remove the the access control entry for
     * 
     * @throws CmsException if something goes wrong
     */
    public void removeAccessControlEntry(CmsDbContext dbc, CmsResource resource, CmsUUID principal)
            throws CmsException {

        // remove the ace
        getUserDriver(dbc).removeAccessControlEntry(dbc, dbc.currentProject(), resource.getResourceId(), principal);

        // log it
        log(dbc, new CmsLogEntry(dbc, resource.getStructureId(), CmsLogEntryType.RESOURCE_PERMISSIONS,
                new String[] { resource.getRootPath() }), false);

        // update the "last modified" information
        setDateLastModified(dbc, resource, resource.getDateLastModified());

        // clear the cache
        m_monitor.clearAccessControlListCache();

        // fire a resource modification event
        Map<String, Object> data = new HashMap<String, Object>(2);
        data.put(I_CmsEventListener.KEY_RESOURCE, resource);
        data.put(I_CmsEventListener.KEY_CHANGE, new Integer(CHANGED_ACCESSCONTROL));
        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_MODIFIED, data));
    }

    /**
     * Removes a resource from the given organizational unit.<p>
     * 
     * @param dbc the current db context
     * @param orgUnit the organizational unit to remove the resource from
     * @param resource the resource that is to be removed from the organizational unit
     * 
     * @throws CmsException if something goes wrong
     * 
     * @see org.opencms.security.CmsOrgUnitManager#addResourceToOrgUnit(CmsObject, String, String)
     * @see org.opencms.security.CmsOrgUnitManager#addResourceToOrgUnit(CmsObject, String, String)
     */
    public void removeResourceFromOrgUnit(CmsDbContext dbc, CmsOrganizationalUnit orgUnit, CmsResource resource)
            throws CmsException {

        m_monitor.flushCache(CmsMemoryMonitor.CacheType.HAS_ROLE, CmsMemoryMonitor.CacheType.ROLE_LIST);
        getUserDriver(dbc).removeResourceFromOrganizationalUnit(dbc, orgUnit, resource);
    }

    /**
     * Removes a resource from the current project of the user.<p>
     * 
     * @param dbc the current database context
     * @param resource the resource to apply this operation to
     * 
     * @throws CmsException if something goes wrong
     * 
     * @see CmsObject#copyResourceToProject(String)
     * @see I_CmsResourceType#copyResourceToProject(CmsObject, CmsSecurityManager, CmsResource)
     */
    public void removeResourceFromProject(CmsDbContext dbc, CmsResource resource) throws CmsException {

        // remove the resource to the project only if the resource is already in the project
        if (isInsideCurrentProject(dbc, resource.getRootPath())) {
            // check if there are already any subfolders of this resource
            I_CmsProjectDriver projectDriver = getProjectDriver(dbc);
            if (resource.isFolder()) {
                List<String> projectResources = projectDriver.readProjectResources(dbc, dbc.currentProject());
                for (int i = 0; i < projectResources.size(); i++) {
                    String resname = projectResources.get(i);
                    if (resname.startsWith(resource.getRootPath())) {
                        // delete the existing project resource first
                        projectDriver.deleteProjectResource(dbc, dbc.currentProject().getUuid(), resname);
                    }
                }
            }
            try {
                projectDriver.deleteProjectResource(dbc, dbc.currentProject().getUuid(), resource.getRootPath());
            } catch (CmsException exc) {
                // if the subfolder exists already - all is ok
            } finally {
                m_monitor.flushCache(CmsMemoryMonitor.CacheType.PROJECT_RESOURCES);

                OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_PROJECT_MODIFIED,
                        Collections.<String, Object>singletonMap("project", dbc.currentProject())));
            }
        }
    }

    /**
     * Removes the given resource to the given user's publish list.<p>
     * 
     * @param dbc the database context
     * @param userId the user's id
     * @param structureIds the collection of structure IDs to remove
     * 
     * @throws CmsDataAccessException if something goes wrong
     */
    public void removeResourceFromUsersPubList(CmsDbContext dbc, CmsUUID userId, Collection<CmsUUID> structureIds)
            throws CmsDataAccessException {

        for (CmsUUID structureId : structureIds) {
            CmsLogEntry entry = new CmsLogEntry(userId, System.currentTimeMillis(), structureId,
                    CmsLogEntryType.RESOURCE_HIDDEN,
                    new String[] { readResource(dbc, structureId, CmsResourceFilter.ALL).getRootPath() });
            log(dbc, entry, true);
        }
    }

    /**
     * Removes a user from a group.<p>
     *
     * @param dbc the current database context
     * @param username the name of the user that is to be removed from the group
     * @param groupname the name of the group
     * @param readRoles if to read roles or groups
     *
     * @throws CmsException if operation was not successful
     * @throws CmsIllegalArgumentException if the given user was not member in the given group
     * @throws CmsDbEntryNotFoundException if the given group was not found 
     * @throws CmsSecurityException if the given user was <b>read as 'null' from the database</b>
     * 
     * @see #addUserToGroup(CmsDbContext, String, String, boolean)
     */
    public void removeUserFromGroup(CmsDbContext dbc, String username, String groupname, boolean readRoles)
            throws CmsException, CmsIllegalArgumentException, CmsDbEntryNotFoundException, CmsSecurityException {

        CmsGroup group = readGroup(dbc, groupname);
        //check if group exists
        if (group == null) {
            // the group does not exists
            throw new CmsDbEntryNotFoundException(
                    Messages.get().container(Messages.ERR_UNKNOWN_GROUP_1, groupname));
        }
        if (group.isVirtual() && !readRoles) {
            // if removing a user from a virtual role treat it as removing the user from the role
            removeUserFromGroup(dbc, username, CmsRole.valueOf(group).getGroupName(), true);
            return;
        }
        if (group.isVirtual()) {
            // this is an hack so to prevent a unlimited recursive calls
            readRoles = false;
        }
        if ((readRoles && !group.isRole()) || (!readRoles && group.isRole())) {
            // we want a role but we got a group, or the other way
            throw new CmsDbEntryNotFoundException(
                    Messages.get().container(Messages.ERR_UNKNOWN_GROUP_1, groupname));
        }

        // test if this user is existing in the group
        if (!userInGroup(dbc, username, groupname, readRoles)) {
            // user is not in the group, throw exception
            throw new CmsIllegalArgumentException(
                    Messages.get().container(Messages.ERR_USER_NOT_IN_GROUP_2, username, groupname));
        }

        CmsUser user = readUser(dbc, username);
        //check if the user exists
        if (user == null) {
            // the user does not exists
            throw new CmsIllegalArgumentException(
                    Messages.get().container(Messages.ERR_USER_NOT_IN_GROUP_2, username, groupname));
        }

        if (readRoles) {
            CmsRole role = CmsRole.valueOf(group);
            // the workplace user role can only be removed if no other user has no other role
            if (role.equals(CmsRole.WORKPLACE_USER.forOrgUnit(role.getOuFqn()))) {
                if (getGroupsOfUser(dbc, username, role.getOuFqn(), false, true, true,
                        dbc.getRequestContext().getRemoteAddress()).size() > 1) {
                    return;
                }
            }
            // update virtual groups
            Iterator<CmsGroup> it = getVirtualGroupsForRole(dbc, role).iterator();
            while (it.hasNext()) {
                CmsGroup virtualGroup = it.next();
                if (userInGroup(dbc, username, virtualGroup.getName(), false)) {
                    // here we say readroles = true, to prevent an unlimited recursive calls
                    removeUserFromGroup(dbc, username, virtualGroup.getName(), true);
                }
            }
        }
        getUserDriver(dbc).deleteUserInGroup(dbc, user.getId(), group.getId());

        // flush relevant caches
        if (readRoles) {
            m_monitor.flushCache(CmsMemoryMonitor.CacheType.HAS_ROLE, CmsMemoryMonitor.CacheType.ROLE_LIST);
        }
        m_monitor.flushCache(CmsMemoryMonitor.CacheType.USERGROUPS, CmsMemoryMonitor.CacheType.USER_LIST);

        if (!dbc.getProjectId().isNullUUID()) {
            // user modified event is not needed
            return;
        }
        // fire user modified event
        Map<String, Object> eventData = new HashMap<String, Object>();
        eventData.put(I_CmsEventListener.KEY_USER_ID, user.getId().toString());
        eventData.put(I_CmsEventListener.KEY_USER_NAME, user.getName());
        eventData.put(I_CmsEventListener.KEY_GROUP_ID, group.getId().toString());
        eventData.put(I_CmsEventListener.KEY_GROUP_NAME, group.getName());
        eventData.put(I_CmsEventListener.KEY_USER_ACTION,
                I_CmsEventListener.VALUE_USER_MODIFIED_ACTION_REMOVE_USER_FROM_GROUP);
        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_USER_MODIFIED, eventData));

    }

    /**
     * Repairs broken categories.<p>
     * 
     * @param dbc the database context
     * @param projectId the project id
     * @param resource the resource to repair the categories for
     * 
     * @throws CmsException if something goes wrong
     */
    public void repairCategories(CmsDbContext dbc, CmsUUID projectId, CmsResource resource) throws CmsException {

        CmsObject cms = OpenCms.initCmsObject(new CmsObject(getSecurityManager(), dbc.getRequestContext()));
        cms.getRequestContext().setSiteRoot("");
        cms.getRequestContext().setCurrentProject(readProject(dbc, projectId));
        CmsCategoryService.getInstance().repairRelations(cms, resource);
    }

    /**
     * Replaces the content, type and properties of a resource.<p>
     * 
     * @param dbc the current database context
     * @param resource the name of the resource to apply this operation to
     * @param type the new type of the resource
     * @param content the new content of the resource
     * @param properties the new properties of the resource
     * 
     * @throws CmsException if something goes wrong
     * 
     * @see CmsObject#replaceResource(String, int, byte[], List)
     * @see I_CmsResourceType#replaceResource(CmsObject, CmsSecurityManager, CmsResource, int, byte[], List)
     */
    public void replaceResource(CmsDbContext dbc, CmsResource resource, int type, byte[] content,
            List<CmsProperty> properties) throws CmsException {

        // replace the existing with the new file content
        getVfsDriver(dbc).replaceResource(dbc, resource, content, type);

        if ((properties != null) && !properties.isEmpty()) {
            // write the properties
            getVfsDriver(dbc).writePropertyObjects(dbc, dbc.currentProject(), resource, properties);
            m_monitor.flushCache(CmsMemoryMonitor.CacheType.PROPERTY, CmsMemoryMonitor.CacheType.PROPERTY_LIST);
        }

        // update the resource state
        if (resource.getState().isUnchanged()) {
            resource.setState(CmsResource.STATE_CHANGED);
        }
        resource.setUserLastModified(dbc.currentUser().getId());

        // log it
        log(dbc, new CmsLogEntry(dbc, resource.getStructureId(), CmsLogEntryType.RESOURCE_CONTENT_MODIFIED,
                new String[] { resource.getRootPath() }), false);

        setDateLastModified(dbc, resource, System.currentTimeMillis());

        getVfsDriver(dbc).writeResourceState(dbc, dbc.currentProject(), resource, UPDATE_RESOURCE, false);

        deleteRelationsWithSiblings(dbc, resource);

        // clear the cache
        m_monitor.clearResourceCache();

        if ((properties != null) && !properties.isEmpty()) {
            // resource and properties were modified
            OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_AND_PROPERTIES_MODIFIED,
                    Collections.<String, Object>singletonMap(I_CmsEventListener.KEY_RESOURCE, resource)));
        } else {
            // only the resource was modified
            Map<String, Object> data = new HashMap<String, Object>(2);
            data.put(I_CmsEventListener.KEY_RESOURCE, resource);
            data.put(I_CmsEventListener.KEY_CHANGE, new Integer(CHANGED_RESOURCE | CHANGED_CONTENT));
            OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_MODIFIED, data));
        }
    }

    /**
     * Resets the password for a specified user.<p>
     *
     * @param dbc the current database context
     * @param username the name of the user
     * @param oldPassword the old password
     * @param newPassword the new password
     * 
     * @throws CmsException if the user data could not be read from the database
     * @throws CmsSecurityException if the specified username and old password could not be verified
     */
    public void resetPassword(CmsDbContext dbc, String username, String oldPassword, String newPassword)
            throws CmsException, CmsSecurityException {

        if ((oldPassword != null) && (newPassword != null)) {

            CmsUser user = null;

            validatePassword(newPassword);

            // read the user as a system user to verify that the specified old password is correct
            try {
                user = getUserDriver(dbc).readUser(dbc, username, oldPassword, null);
            } catch (CmsDbEntryNotFoundException e) {
                throw new CmsDataAccessException(Messages.get().container(Messages.ERR_RESET_PASSWORD_1, username),
                        e);
            }

            if ((user == null) || user.isManaged()) {
                throw new CmsDataAccessException(Messages.get().container(Messages.ERR_RESET_PASSWORD_1, username));
            }

            getUserDriver(dbc).writePassword(dbc, username, oldPassword, newPassword);

            if (!dbc.getProjectId().isNullUUID()) {
                // user modified event is not needed
                return;
            }
            // fire user modified event
            Map<String, Object> eventData = new HashMap<String, Object>();
            eventData.put(I_CmsEventListener.KEY_USER_ID, user.getId().toString());
            eventData.put(I_CmsEventListener.KEY_USER_ACTION,
                    I_CmsEventListener.VALUE_USER_MODIFIED_ACTION_RESET_PASSWORD);
            OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_USER_MODIFIED, eventData));

        } else if (CmsStringUtil.isEmpty(oldPassword)) {
            throw new CmsDataAccessException(Messages.get().container(Messages.ERR_PWD_OLD_MISSING_0));
        } else if (CmsStringUtil.isEmpty(newPassword)) {
            throw new CmsDataAccessException(Messages.get().container(Messages.ERR_PWD_NEW_MISSING_0));
        }
    }

    /**
     * Restores a deleted resource identified by its structure id from the historical archive.<p>
     * 
     * @param dbc the current database context
     * @param structureId the structure id of the resource to restore
     * 
     * @throws CmsException if something goes wrong
     * 
     * @see CmsObject#restoreDeletedResource(CmsUUID)
     */
    public void restoreDeletedResource(CmsDbContext dbc, CmsUUID structureId) throws CmsException {

        // get the last version, which should be the deleted one
        int version = getHistoryDriver(dbc).readLastVersion(dbc, structureId);
        // get that version
        I_CmsHistoryResource histRes = getHistoryDriver(dbc).readResource(dbc, structureId, version);

        // check the parent path
        CmsResource parent;
        try {
            // try to read the parent resource by id
            parent = getVfsDriver(dbc).readResource(dbc, dbc.currentProject().getUuid(), histRes.getParentId(),
                    true);
        } catch (CmsVfsResourceNotFoundException e) {
            // if not found try to read the parent resource by name
            try {
                // try to read the parent resource by id
                parent = getVfsDriver(dbc).readResource(dbc, dbc.currentProject().getUuid(),
                        CmsResource.getParentFolder(histRes.getRootPath()), true);
            } catch (CmsVfsResourceNotFoundException e1) {
                // if not found try to restore the parent resource
                restoreDeletedResource(dbc, histRes.getParentId());
                parent = readResource(dbc, histRes.getParentId(), CmsResourceFilter.IGNORE_EXPIRATION);
            }
        }
        // check write permissions
        m_securityManager.checkPermissions(dbc, parent, CmsPermissionSet.ACCESS_WRITE, false,
                CmsResourceFilter.IGNORE_EXPIRATION);

        // check the name
        String path = CmsResource.getParentFolder(histRes.getRootPath()); // path
        String resName = CmsResource.getName(histRes.getRootPath()); // name
        String ext = "";
        if (resName.charAt(resName.length() - 1) == '/') {
            resName = resName.substring(0, resName.length() - 1);
        } else {
            ext = CmsFileUtil.getExtension(resName); // extension
        }
        String nameWOExt = resName.substring(0, resName.length() - ext.length()); // name without extension
        for (int i = 1; true; i++) {
            try {
                readResource(dbc, path + resName, CmsResourceFilter.ALL);
                resName = nameWOExt + "_" + i + ext;
                // try the next resource name with following schema: path/name_{i}.ext
            } catch (CmsVfsResourceNotFoundException e) {
                // ok, we found a not used resource name
                break;
            }
        }

        // check structure id
        CmsUUID id = structureId;
        if (getVfsDriver(dbc).validateStructureIdExists(dbc, dbc.currentProject().getUuid(), structureId)) {
            // should never happen, but if already exists create a new one
            id = new CmsUUID();
        }

        byte[] contents = null;
        boolean isFolder = true;

        // do we need the contents?
        if (histRes instanceof CmsFile) {
            contents = ((CmsFile) histRes).getContents();
            if ((contents == null) || (contents.length == 0)) {
                contents = getHistoryDriver(dbc).readContent(dbc, histRes.getResourceId(), histRes.getPublishTag());
            }
            isFolder = false;
        }

        // now read the historical properties
        List<CmsProperty> properties = getHistoryDriver(dbc).readProperties(dbc, histRes);

        // create the object to create
        CmsResource newResource = new CmsResource(id, histRes.getResourceId(), path + resName, histRes.getTypeId(),
                isFolder, histRes.getFlags(), dbc.currentProject().getUuid(), CmsResource.STATE_NEW,
                histRes.getDateCreated(), histRes.getUserCreated(), histRes.getDateLastModified(),
                dbc.currentUser().getId(), histRes.getDateReleased(), histRes.getDateExpired(),
                histRes.getSiblingCount(), histRes.getLength(), histRes.getDateContent(), histRes.getVersion());

        // log it
        log(dbc, new CmsLogEntry(dbc, newResource.getStructureId(), CmsLogEntryType.RESOURCE_RESTORE_DELETED,
                new String[] { newResource.getRootPath() }), false);

        // prevent the date last modified is set to the current time
        newResource.setDateLastModified(newResource.getDateLastModified());
        // restore the resource!
        CmsResource resource = createResource(dbc, path + resName, newResource, contents, properties, true);
        // set resource state to changed
        newResource.setState(CmsResource.STATE_CHANGED);
        getVfsDriver(dbc).writeResourceState(dbc, dbc.currentProject(), newResource, UPDATE_RESOURCE_STATE, false);
        newResource.setState(CmsResource.STATE_NEW);
        // fire the event
        Map<String, Object> data = new HashMap<String, Object>(2);
        data.put(I_CmsEventListener.KEY_RESOURCE, resource);
        data.put(I_CmsEventListener.KEY_CHANGE, new Integer(CHANGED_RESOURCE | CHANGED_CONTENT));
        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_MODIFIED, data));
    }

    /**
     * Restores a resource in the current project with a version from the historical archive.<p>
     * 
     * @param dbc the current database context
     * @param resource the resource to restore from the archive
     * @param version the version number to restore from the archive
     * 
     * @throws CmsException if something goes wrong
     * 
     * @see CmsObject#restoreResourceVersion(CmsUUID, int)
     * @see I_CmsResourceType#restoreResource(CmsObject, CmsSecurityManager, CmsResource, int)
     */
    public void restoreResource(CmsDbContext dbc, CmsResource resource, int version) throws CmsException {

        I_CmsHistoryResource historyResource = readResource(dbc, resource, version);
        CmsResourceState state = CmsResource.STATE_CHANGED;
        if (resource.getState().isNew()) {
            state = CmsResource.STATE_NEW;
        }
        int newVersion = resource.getVersion();
        if (resource.getState().isUnchanged()) {
            newVersion++;
        }
        CmsResource newResource = null;
        // is the resource a file?
        if (historyResource instanceof CmsFile) {
            // get the historical up flags 
            int flags = historyResource.getFlags();
            if (resource.isLabeled()) {
                // set the flag for labeled links on the restored file
                flags |= CmsResource.FLAG_LABELED;
            }
            CmsFile newFile = new CmsFile(resource.getStructureId(), resource.getResourceId(),
                    resource.getRootPath(), historyResource.getTypeId(), flags, dbc.currentProject().getUuid(),
                    state, resource.getDateCreated(), historyResource.getUserCreated(),
                    resource.getDateLastModified(), dbc.currentUser().getId(), historyResource.getDateReleased(),
                    historyResource.getDateExpired(), resource.getSiblingCount(), historyResource.getLength(),
                    historyResource.getDateContent(), newVersion,
                    readFile(dbc, (CmsHistoryFile) historyResource).getContents());

            // log it
            log(dbc, new CmsLogEntry(dbc, newFile.getStructureId(), CmsLogEntryType.RESOURCE_HISTORY,
                    new String[] { newFile.getRootPath() }), false);

            newResource = writeFile(dbc, newFile);
        } else {
            // it is a folder!
            newResource = new CmsFolder(resource.getStructureId(), resource.getResourceId(), resource.getRootPath(),
                    historyResource.getTypeId(), historyResource.getFlags(), dbc.currentProject().getUuid(), state,
                    resource.getDateCreated(), historyResource.getUserCreated(), resource.getDateLastModified(),
                    dbc.currentUser().getId(), historyResource.getDateReleased(), historyResource.getDateExpired(),
                    newVersion);

            // log it
            log(dbc, new CmsLogEntry(dbc, newResource.getStructureId(), CmsLogEntryType.RESOURCE_HISTORY,
                    new String[] { newResource.getRootPath() }), false);

            writeResource(dbc, newResource);
        }
        if (newResource != null) {
            // now read the historical properties
            List<CmsProperty> historyProperties = getHistoryDriver(dbc).readProperties(dbc, historyResource);
            // remove all properties
            deleteAllProperties(dbc, newResource.getRootPath());
            // write them to the restored resource
            writePropertyObjects(dbc, newResource, historyProperties, false);

            m_monitor.clearResourceCache();
        }

        Map<String, Object> data = new HashMap<String, Object>(2);
        data.put(I_CmsEventListener.KEY_RESOURCE, resource);
        data.put(I_CmsEventListener.KEY_CHANGE, new Integer(CHANGED_RESOURCE | CHANGED_CONTENT));
        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_MODIFIED, data));
    }

    /**
     * Searches for users which fit the given criteria.<p>
     * 
     * @param dbc the database context 
     * @param searchParams the search criteria
     *  
     * @return the users which fit the search criteria 
     * 
     * @throws CmsDataAccessException if something goes wrong 
     */
    public List<CmsUser> searchUsers(CmsDbContext dbc, CmsUserSearchParameters searchParams

    ) throws CmsDataAccessException {

        return getUserDriver(dbc).searchUsers(dbc, searchParams);
    }

    /**
     * Changes the "expire" date of a resource.<p>
     * 
     * @param dbc the current database context
     * @param resource the resource to touch
     * @param dateExpired the new expire date of the resource
     * 
     * @throws CmsDataAccessException if something goes wrong
     * 
     * @see CmsObject#setDateExpired(String, long, boolean)
     * @see I_CmsResourceType#setDateExpired(CmsObject, CmsSecurityManager, CmsResource, long, boolean)
     */
    public void setDateExpired(CmsDbContext dbc, CmsResource resource, long dateExpired)
            throws CmsDataAccessException {

        resource.setDateExpired(dateExpired);
        if (resource.getState().isUnchanged()) {
            resource.setState(CmsResource.STATE_CHANGED);
        }
        getVfsDriver(dbc).writeResourceState(dbc, dbc.currentProject(), resource, UPDATE_STRUCTURE, false);

        // modify the last modified project reference
        getVfsDriver(dbc).writeResourceState(dbc, dbc.currentProject(), resource, UPDATE_RESOURCE_PROJECT, false);
        // log
        log(dbc, new CmsLogEntry(dbc, resource.getStructureId(), CmsLogEntryType.RESOURCE_DATE_EXPIRED,
                new String[] { resource.getRootPath() }), false);

        // clear the cache
        m_monitor.clearResourceCache();

        // fire the event
        Map<String, Object> data = new HashMap<String, Object>(2);
        data.put(I_CmsEventListener.KEY_RESOURCE, resource);
        data.put(I_CmsEventListener.KEY_CHANGE, new Integer(CHANGED_TIMEFRAME));
        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_MODIFIED, data));
    }

    /**
     * Changes the "last modified" timestamp of a resource.<p>
     * 
     * @param dbc the current database context
     * @param resource the resource to touch
     * @param dateLastModified the new last modified date of the resource
     * 
     * @throws CmsDataAccessException if something goes wrong
     * 
     * @see CmsObject#setDateLastModified(String, long, boolean)
     * @see I_CmsResourceType#setDateLastModified(CmsObject, CmsSecurityManager, CmsResource, long, boolean)
     */
    public void setDateLastModified(CmsDbContext dbc, CmsResource resource, long dateLastModified)
            throws CmsDataAccessException {

        // modify the last modification date
        resource.setDateLastModified(dateLastModified);
        if (resource.getState().isUnchanged()) {
            resource.setState(CmsResource.STATE_CHANGED);
        } else if (resource.getState().isNew() && (resource.getSiblingCount() > 1)) {
            // in case of new resources with siblings make sure the state is correct
            resource.setState(CmsResource.STATE_CHANGED);
        }
        resource.setUserLastModified(dbc.currentUser().getId());
        getVfsDriver(dbc).writeResourceState(dbc, dbc.currentProject(), resource, UPDATE_RESOURCE, false);

        log(dbc, new CmsLogEntry(dbc, resource.getStructureId(), CmsLogEntryType.RESOURCE_TOUCHED,
                new String[] { resource.getRootPath() }), false);

        // clear the cache
        m_monitor.clearResourceCache();

        // fire the event
        Map<String, Object> data = new HashMap<String, Object>(2);
        data.put(I_CmsEventListener.KEY_RESOURCE, resource);
        data.put(I_CmsEventListener.KEY_CHANGE, new Integer(CHANGED_LASTMODIFIED));
        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_MODIFIED, data));
    }

    /**
     * Changes the "release" date of a resource.<p>
     * 
     * @param dbc the current database context
     * @param resource the resource to touch
     * @param dateReleased the new release date of the resource
     * 
     * @throws CmsDataAccessException if something goes wrong
     * 
     * @see CmsObject#setDateReleased(String, long, boolean)
     * @see I_CmsResourceType#setDateReleased(CmsObject, CmsSecurityManager, CmsResource, long, boolean)
     */
    public void setDateReleased(CmsDbContext dbc, CmsResource resource, long dateReleased)
            throws CmsDataAccessException {

        // modify the last modification date
        resource.setDateReleased(dateReleased);
        if (resource.getState().isUnchanged()) {
            resource.setState(CmsResource.STATE_CHANGED);
        }
        getVfsDriver(dbc).writeResourceState(dbc, dbc.currentProject(), resource, UPDATE_STRUCTURE, false);

        // modify the last modified project reference
        getVfsDriver(dbc).writeResourceState(dbc, dbc.currentProject(), resource, UPDATE_RESOURCE_PROJECT, false);
        // log it
        log(dbc, new CmsLogEntry(dbc, resource.getStructureId(), CmsLogEntryType.RESOURCE_DATE_RELEASED,
                new String[] { resource.getRootPath() }), false);

        // clear the cache
        m_monitor.clearResourceCache();

        // fire the event
        Map<String, Object> data = new HashMap<String, Object>(2);
        data.put(I_CmsEventListener.KEY_RESOURCE, resource);
        data.put(I_CmsEventListener.KEY_CHANGE, new Integer(CHANGED_TIMEFRAME));
        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_MODIFIED, data));
    }

    /**
     * Sets a new parent group for an already existing group.<p>
     *
     * @param dbc the current database context
     * @param groupName the name of the group that should be written
     * @param parentGroupName the name of the parent group to set, 
     *                      or <code>null</code> if the parent
     *                      group should be deleted.
     *
     * @throws CmsException if operation was not successful
     * @throws CmsDataAccessException if the group with <code>groupName</code> could not be read from VFS
     */
    public void setParentGroup(CmsDbContext dbc, String groupName, String parentGroupName)
            throws CmsException, CmsDataAccessException {

        CmsGroup group = readGroup(dbc, groupName);
        CmsUUID parentGroupId = CmsUUID.getNullUUID();

        // if the group exists, use its id, else set to unknown.
        if (parentGroupName != null) {
            parentGroupId = readGroup(dbc, parentGroupName).getId();
        }

        group.setParentId(parentGroupId);

        // write the changes to the cms
        writeGroup(dbc, group);
    }

    /**
     * Sets the password for a user.<p>
     *
     * @param dbc the current database context
     * @param username the name of the user
     * @param newPassword the new password
     * 
     * @throws CmsException if operation was not successful
     * @throws CmsIllegalArgumentException if the user with the <code>username</code> was not found
     */
    public void setPassword(CmsDbContext dbc, String username, String newPassword)
            throws CmsException, CmsIllegalArgumentException {

        validatePassword(newPassword);

        // read the user as a system user to verify that the specified old password is correct
        getUserDriver(dbc).readUser(dbc, username);
        // only continue if not found and read user from web might succeed
        getUserDriver(dbc).writePassword(dbc, username, null, newPassword);
    }

    /**
     * Marks a subscribed resource as deleted.<p>
     * 
     * @param dbc the database context
     * @param poolName the name of the database pool to use
     * @param resource the subscribed resource to mark as deleted
     * 
     * @throws CmsException if something goes wrong
     */
    public void setSubscribedResourceAsDeleted(CmsDbContext dbc, String poolName, CmsResource resource)
            throws CmsException {

        getSubscriptionDriver().setSubscribedResourceAsDeleted(dbc, poolName, resource);
    }

    /**
     * Moves an user to the given organizational unit.<p>
     * 
     * @param dbc the current db context
     * @param orgUnit the organizational unit to add the resource to
     * @param user the user that is to be moved to the organizational unit
     * 
     * @throws CmsException if something goes wrong
     * 
     * @see org.opencms.security.CmsOrgUnitManager#setUsersOrganizationalUnit(CmsObject, String, String)
     */
    public void setUsersOrganizationalUnit(CmsDbContext dbc, CmsOrganizationalUnit orgUnit, CmsUser user)
            throws CmsException {

        if (!getGroupsOfUser(dbc, user.getName(), false).isEmpty()) {
            throw new CmsDbConsistencyException(
                    Messages.get().container(Messages.ERR_ORGUNIT_MOVE_USER_2, orgUnit.getName(), user.getName()));
        }

        // move the principal
        getUserDriver(dbc).setUsersOrganizationalUnit(dbc, orgUnit, user);
        // remove the principal from cache
        m_monitor.clearUserCache(user);

        if (!dbc.getProjectId().isNullUUID()) {
            // user modified event is not needed
            return;
        }
        // fire user modified event
        Map<String, Object> eventData = new HashMap<String, Object>();
        eventData.put(I_CmsEventListener.KEY_USER_ID, user.getId().toString());
        eventData.put(I_CmsEventListener.KEY_OU_NAME, user.getOuFqn());
        eventData.put(I_CmsEventListener.KEY_USER_ACTION, I_CmsEventListener.VALUE_USER_MODIFIED_ACTION_SET_OU);
        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_USER_MODIFIED, eventData));
    }

    /**
     * Subscribes the user or group to the resource.<p>
     * 
     * @param dbc the database context
     * @param poolName the name of the database pool to use
     * @param principal the principal that subscribes to the resource
     * @param resource the resource to subscribe to
     * 
     * @throws CmsException if something goes wrong
     */
    public void subscribeResourceFor(CmsDbContext dbc, String poolName, CmsPrincipal principal,
            CmsResource resource) throws CmsException {

        getSubscriptionDriver().subscribeResourceFor(dbc, poolName, principal, resource);
    }

    /**
     * Undelete the resource.<p>
     * 
     * @param dbc the current database context
     * @param resource the name of the resource to apply this operation to
     * 
     * @throws CmsException if something goes wrong
     * 
     * @see CmsObject#undeleteResource(String, boolean)
     * @see I_CmsResourceType#undelete(CmsObject, CmsSecurityManager, CmsResource, boolean)
     */
    public void undelete(CmsDbContext dbc, CmsResource resource) throws CmsException {

        if (!resource.getState().isDeleted()) {
            throw new CmsVfsException(Messages.get().container(Messages.ERR_UNDELETE_FOR_RESOURCE_DELETED_1,
                    dbc.removeSiteRoot(resource.getRootPath())));
        }

        // set the state to changed
        resource.setState(CmsResourceState.STATE_CHANGED);
        // perform the changes
        updateState(dbc, resource, false);
        // log it
        log(dbc, new CmsLogEntry(dbc, resource.getStructureId(), CmsLogEntryType.RESOURCE_UNDELETED,
                new String[] { resource.getRootPath() }), false);
        // clear the cache
        m_monitor.clearResourceCache();

        // fire change event
        Map<String, Object> data = new HashMap<String, Object>(2);
        data.put(I_CmsEventListener.KEY_RESOURCE, resource);
        data.put(I_CmsEventListener.KEY_CHANGE, new Integer(CHANGED_RESOURCE));
        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_MODIFIED, data));
    }

    /**
     * Undos all changes in the resource by restoring the version from the 
     * online project to the current offline project.<p>
     * 
     * @param dbc the current database context
     * @param resource the name of the resource to apply this operation to
     * @param mode the undo mode, one of the <code>{@link org.opencms.file.CmsResource.CmsResourceUndoMode}#UNDO_XXX</code> constants 
     *      please note that the recursive flag is ignored at this level
     * 
     * @throws CmsException if something goes wrong
     * 
     * @see CmsObject#undoChanges(String, CmsResource.CmsResourceUndoMode)
     * @see I_CmsResourceType#undoChanges(CmsObject, CmsSecurityManager, CmsResource, CmsResource.CmsResourceUndoMode)
     */
    public void undoChanges(CmsDbContext dbc, CmsResource resource, CmsResource.CmsResourceUndoMode mode)
            throws CmsException {

        if (resource.getState().isNew()) {
            // undo changes is impossible on a new resource
            throw new CmsVfsException(Messages.get().container(Messages.ERR_UNDO_CHANGES_FOR_RESOURCE_NEW_0));
        }

        // we need this for later use
        CmsProject onlineProject = readProject(dbc, CmsProject.ONLINE_PROJECT_ID);
        // read the resource from the online project
        CmsResource onlineResource = getVfsDriver(dbc).readResource(dbc, CmsProject.ONLINE_PROJECT_ID,
                resource.getStructureId(), true);

        CmsResource onlineResourceByPath = null;
        try {
            // this is needed to figure out if a moved resource overwrote a deleted one
            onlineResourceByPath = getVfsDriver(dbc).readResource(dbc, CmsProject.ONLINE_PROJECT_ID,
                    resource.getRootPath(), true);

            // force undo move operation if needed
            if (!mode.isUndoMove() && !onlineResourceByPath.getRootPath().equals(onlineResource.getRootPath())) {
                mode = mode.includeMove();
            }
        } catch (Exception e) {
            // ok
        }

        boolean moved = !onlineResource.getRootPath().equals(resource.getRootPath());
        // undo move operation if required
        if (moved && mode.isUndoMove()) {
            moveResource(dbc, resource, onlineResource.getRootPath(), true);
            if ((onlineResourceByPath != null)
                    && !onlineResourceByPath.getRootPath().equals(onlineResource.getRootPath())) {
                // was moved over deleted, so the deleted file has to be undone
                undoContentChanges(dbc, onlineProject, null, onlineResourceByPath, CmsResource.STATE_UNCHANGED,
                        true);
            }
        }
        // undo content changes
        CmsResourceState newState = CmsResource.STATE_UNCHANGED;
        if (moved && !mode.isUndoMove()) {
            newState = CmsResource.STATE_CHANGED;
        }
        undoContentChanges(dbc, onlineProject, resource, onlineResource, newState, moved && mode.isUndoMove());
        // because undoContentChanges deletes the offline resource internally, we have
        // to write an entry to the log table to prevent the resource from appearing in the
        // user's publish list. 
        log(dbc, new CmsLogEntry(dbc, resource.getStructureId(), CmsLogEntryType.RESOURCE_CHANGES_UNDONE,
                new String[] { resource.getRootPath() }), true);

    }

    /**
     * Unlocks all resources in the given project.<p>
     * 
     * @param project the project to unlock the resources in
     */
    public void unlockProject(CmsProject project) {

        // unlock all resources in the project
        m_lockManager.removeResourcesInProject(project.getUuid(), false);
        m_monitor.clearResourceCache();
        m_monitor.flushCache(CmsMemoryMonitor.CacheType.PROJECT, CmsMemoryMonitor.CacheType.PERMISSION);
    }

    /**
     * Unlocks a resource.<p>
     * 
     * @param dbc the current database context
     * @param resource the resource to unlock
     * @param force <code>true</code>, if a resource is forced to get unlocked, 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
     * 
     * @throws CmsException if something goes wrong
     * 
     * @see CmsObject#unlockResource(String)
     * @see I_CmsResourceType#unlockResource(CmsObject, CmsSecurityManager, CmsResource)
     */
    public void unlockResource(CmsDbContext dbc, CmsResource resource, boolean force, boolean removeSystemLock)
            throws CmsException {

        // update the resource cache
        m_monitor.clearResourceCache();

        // now update lock status
        m_lockManager.removeResource(dbc, resource, force, removeSystemLock);

        // we must also clear the permission cache
        m_monitor.flushCache(CmsMemoryMonitor.CacheType.PERMISSION);

        // fire resource modification event
        Map<String, Object> data = new HashMap<String, Object>(2);
        data.put(I_CmsEventListener.KEY_RESOURCE, resource);
        data.put(I_CmsEventListener.KEY_CHANGE, new Integer(NOTHING_CHANGED));
        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_MODIFIED, data));
    }

    /**
     * Unsubscribes all deleted resources that were deleted before the specified time stamp.<p>
     * 
     * @param dbc the database context
     * @param poolName the name of the database pool to use
     * @param deletedTo the time stamp to which the resources have been deleted
     * 
     * @throws CmsException if something goes wrong
     */
    public void unsubscribeAllDeletedResources(CmsDbContext dbc, String poolName, long deletedTo)
            throws CmsException {

        getSubscriptionDriver().unsubscribeAllDeletedResources(dbc, poolName, deletedTo);
    }

    /**
     * Unsubscribes the principal from all resources.<p>
     * 
     * @param dbc the database context
     * @param poolName the name of the database pool to use
     * @param principal the principal that unsubscribes from all resources
     * 
     * @throws CmsException if something goes wrong
     */
    public void unsubscribeAllResourcesFor(CmsDbContext dbc, String poolName, CmsPrincipal principal)
            throws CmsException {

        getSubscriptionDriver().unsubscribeAllResourcesFor(dbc, poolName, principal);

    }

    /**
     * Unsubscribes the principal from the resource.<p>
     * 
     * @param dbc the database context
     * @param poolName the name of the database pool to use
     * @param principal the principal that unsubscribes from the resource
     * @param resource the resource to unsubscribe from
     * 
     * @throws CmsException if something goes wrong 
     */
    public void unsubscribeResourceFor(CmsDbContext dbc, String poolName, CmsPrincipal principal,
            CmsResource resource) throws CmsException {

        getSubscriptionDriver().unsubscribeResourceFor(dbc, poolName, principal, resource);
    }

    /**
     * Unsubscribes all groups and users from the resource.<p>
     * 
     * @param dbc the database context
     * @param poolName the name of the database pool to use
     * @param resource the resource to unsubscribe all groups and users from
     * 
     * @throws CmsException if something goes wrong
     */
    public void unsubscribeResourceForAll(CmsDbContext dbc, String poolName, CmsResource resource)
            throws CmsException {

        getSubscriptionDriver().unsubscribeResourceForAll(dbc, poolName, resource);
    }

    /**
     * Update the export points.<p>
     * 
     * All files and folders "inside" an export point are written.<p>
     * 
     * @param dbc the current database context
     */
    public void updateExportPoints(CmsDbContext dbc) {

        try {
            // read the export points and return immediately if there are no export points at all         
            Set<CmsExportPoint> exportPoints = new HashSet<CmsExportPoint>();
            exportPoints.addAll(OpenCms.getExportPoints());
            exportPoints.addAll(OpenCms.getModuleManager().getExportPoints());
            if (exportPoints.size() == 0) {
                if (LOG.isWarnEnabled()) {
                    LOG.warn(Messages.get().getBundle().key(Messages.LOG_NO_EXPORT_POINTS_CONFIGURED_0));
                }
                return;
            }

            // create the driver to write the export points
            CmsExportPointDriver exportPointDriver = new CmsExportPointDriver(exportPoints);

            // the export point hash table contains RFS export paths keyed by their internal VFS paths
            Iterator<String> i = exportPointDriver.getExportPointPaths().iterator();
            I_CmsVfsDriver vfsDriver = getVfsDriver(dbc);
            while (i.hasNext()) {
                String currentExportPoint = i.next();

                // print some report messages
                if (LOG.isInfoEnabled()) {
                    LOG.info(Messages.get().getBundle().key(Messages.LOG_WRITE_EXPORT_POINT_1, currentExportPoint));
                }

                try {
                    CmsResourceFilter filter = CmsResourceFilter.DEFAULT;
                    List<CmsResource> resources = vfsDriver.readResourceTree(dbc, CmsProject.ONLINE_PROJECT_ID,
                            currentExportPoint, filter.getType(), filter.getState(), filter.getModifiedAfter(),
                            filter.getModifiedBefore(), filter.getReleaseAfter(), filter.getReleaseBefore(),
                            filter.getExpireAfter(), filter.getExpireBefore(),
                            CmsDriverManager.READMODE_INCLUDE_TREE
                                    | (filter.excludeType() ? CmsDriverManager.READMODE_EXCLUDE_TYPE : 0)
                                    | (filter.excludeState() ? CmsDriverManager.READMODE_EXCLUDE_STATE : 0));

                    Iterator<CmsResource> j = resources.iterator();
                    while (j.hasNext()) {
                        CmsResource currentResource = j.next();

                        if (currentResource.isFolder()) {
                            // export the folder                        
                            exportPointDriver.createFolder(currentResource.getRootPath(), currentExportPoint);
                        } else {
                            // try to create the exportpoint folder
                            exportPointDriver.createFolder(currentExportPoint, currentExportPoint);
                            byte[] onlineContent = vfsDriver.readContent(dbc, CmsProject.ONLINE_PROJECT_ID,
                                    currentResource.getResourceId());
                            // export the file content online
                            exportPointDriver.writeFile(currentResource.getRootPath(), currentExportPoint,
                                    onlineContent);
                        }
                    }
                } catch (CmsException e) {
                    // there might exist export points without corresponding resources in the VFS
                    // -> ignore exceptions which are not "resource not found" exception quiet here
                    if (e instanceof CmsVfsResourceNotFoundException) {
                        if (LOG.isErrorEnabled()) {
                            LOG.error(Messages.get().getBundle().key(Messages.LOG_UPDATE_EXORT_POINTS_ERROR_0), e);
                        }
                    }
                }
            }
        } catch (Exception e) {
            if (LOG.isErrorEnabled()) {
                LOG.error(Messages.get().getBundle().key(Messages.LOG_UPDATE_EXORT_POINTS_ERROR_0), e);
            }
        }
    }

    /**
     * Logs everything that has not been written to DB jet.<p>
     * 
     * @param dbc the current db context
     * 
     * @throws CmsDataAccessException if something goes wrong
     */
    public void updateLog(CmsDbContext dbc) throws CmsDataAccessException {

        if (m_log.isEmpty()) {
            return;
        }
        List<CmsLogEntry> log = new ArrayList<CmsLogEntry>(m_log);
        m_log.clear();
        m_projectDriver.log(dbc, log);
    }

    /**
     * Updates/Creates the given relations for the given resource.<p>
     * 
     * @param dbc the db context
     * @param resource the resource to update the relations for
     * @param links the links to consider for updating
     * 
     * @throws CmsException if something goes wrong
     * 
     * @see CmsSecurityManager#updateRelationsForResource(CmsRequestContext, CmsResource, List)
     */
    public void updateRelationsForResource(CmsDbContext dbc, CmsResource resource, List<CmsLink> links)
            throws CmsException {

        deleteRelationsWithSiblings(dbc, resource);

        // build the links again only if needed
        if ((links == null) || links.isEmpty()) {
            return;
        }
        // the set of written relations
        Set<CmsRelation> writtenRelations = new HashSet<CmsRelation>();

        // create new relation information
        I_CmsVfsDriver vfsDriver = getVfsDriver(dbc);
        Iterator<CmsLink> itLinks = links.iterator();
        while (itLinks.hasNext()) {
            CmsLink link = itLinks.next();
            if (link.isInternal()) { // only update internal links
                if (CmsStringUtil.isEmptyOrWhitespaceOnly(link.getTarget())) {
                    // only an anchor
                    continue;
                }
                CmsUUID targetId = link.getStructureId();
                String destPath = link.getTarget();

                if (targetId != null) {
                    // the link target may not be a VFS path even if the link id is a structure id,
                    // so if possible, we read the resource for the id and set the relation target to its
                    // real root path. 
                    try {
                        CmsResource destRes = readResource(dbc, targetId, CmsResourceFilter.ALL);
                        destPath = destRes.getRootPath();
                    } catch (CmsVfsResourceNotFoundException e) {
                        // ignore
                    }
                }

                CmsRelation originalRelation = new CmsRelation(resource.getStructureId(), resource.getRootPath(),
                        link.getStructureId(), destPath, link.getType());

                // do not write twice the same relation
                if (writtenRelations.contains(originalRelation)) {
                    continue;
                }
                writtenRelations.add(originalRelation);

                // TODO: it would be good to have the link locale to make the relation just to the right sibling
                // create the relations in content for all siblings 
                Iterator<CmsResource> itSiblings = readSiblings(dbc, resource, CmsResourceFilter.ALL).iterator();
                while (itSiblings.hasNext()) {
                    CmsResource sibling = itSiblings.next();
                    CmsRelation relation = new CmsRelation(sibling.getStructureId(), sibling.getRootPath(),
                            originalRelation.getTargetId(), originalRelation.getTargetPath(), link.getType());
                    vfsDriver.createRelation(dbc, dbc.currentProject().getUuid(), relation);
                }
            }
        }
    }

    /**
     * Returns <code>true</code> if a user is member of the given group.<p>
     * 
     * @param dbc the current database context
     * @param username the name of the user to check
     * @param groupname the name of the group to check
     * @param readRoles if to read roles or groups
     *
     * @return <code>true</code>, if the user is in the group, <code>false</code> otherwise
     * 
     * @throws CmsException if something goes wrong
     */
    public boolean userInGroup(CmsDbContext dbc, String username, String groupname, boolean readRoles)
            throws CmsException {

        List<CmsGroup> groups = getGroupsOfUser(dbc, username, readRoles);
        for (int i = 0; i < groups.size(); i++) {
            CmsGroup group = groups.get(i);
            if (groupname.equals(group.getName()) || groupname.substring(1).equals(group.getName())) {
                return true;
            }
        }
        return false;
    }

    /**
     * This method checks if a new password follows the rules for
     * new passwords, which are defined by a Class implementing the 
     * <code>{@link org.opencms.security.I_CmsPasswordHandler}</code> 
     * interface and configured in the opencms.properties file.<p>
     * 
     * If this method throws no exception the password is valid.<p>
     *
     * @param password the new password that has to be checked
     * 
     * @throws CmsSecurityException if the password is not valid
     */
    public void validatePassword(String password) throws CmsSecurityException {

        OpenCms.getPasswordHandler().validatePassword(password);
    }

    /**
     * Validates the relations for the given resources.<p>
     * 
     * @param dbc the database context
     * @param publishList the resources to validate during publishing 
     * @param report a report to write the messages to
     * 
     * @return a map with lists of invalid links 
     *          (<code>{@link org.opencms.relations.CmsRelation}}</code> objects) 
     *          keyed by root paths
     * 
     * @throws Exception if something goes wrong
     */
    public Map<String, List<CmsRelation>> validateRelations(CmsDbContext dbc, CmsPublishList publishList,
            I_CmsReport report) throws Exception {

        return m_htmlLinkValidator.validateResources(dbc, publishList, report);
    }

    /**
     * Writes an access control entries to a given resource.<p>
     * 
     * @param dbc the current database context
     * @param resource the resource
     * @param ace the entry to write
     * 
     * @throws CmsException if something goes wrong
     */
    public void writeAccessControlEntry(CmsDbContext dbc, CmsResource resource, CmsAccessControlEntry ace)
            throws CmsException {

        // write the new ace
        getUserDriver(dbc).writeAccessControlEntry(dbc, dbc.currentProject(), ace);

        // log it
        log(dbc, new CmsLogEntry(dbc, resource.getStructureId(), CmsLogEntryType.RESOURCE_PERMISSIONS,
                new String[] { resource.getRootPath() }), false);

        // update the "last modified" information
        setDateLastModified(dbc, resource, resource.getDateLastModified());

        // clear the cache
        m_monitor.clearAccessControlListCache();

        // fire a resource modification event
        Map<String, Object> data = new HashMap<String, Object>(2);
        data.put(I_CmsEventListener.KEY_RESOURCE, resource);
        data.put(I_CmsEventListener.KEY_CHANGE, new Integer(CHANGED_ACCESSCONTROL));
        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_MODIFIED, data));
    }

    /**
     * Writes all export points into the file system for the publish task 
     * specified by trhe given publish history ID.<p>
     * 
     * @param dbc the current database context
     * @param report an I_CmsReport instance to print output message, or null to write messages to the log file
     * @param publishHistoryId ID to identify the publish task in the publish history
     */
    public void writeExportPoints(CmsDbContext dbc, I_CmsReport report, CmsUUID publishHistoryId) {

        boolean printReportHeaders = false;
        List<CmsPublishedResource> publishedResources = null;
        try {
            // read the "published resources" for the specified publish history ID
            publishedResources = getProjectDriver(dbc).readPublishedResources(dbc, publishHistoryId);
        } catch (CmsException e) {
            if (LOG.isErrorEnabled()) {
                LOG.error(Messages.get().getBundle().key(Messages.ERR_READ_PUBLISHED_RESOURCES_FOR_ID_1,
                        publishHistoryId), e);
            }
        }
        if ((publishedResources == null) || publishedResources.isEmpty()) {
            if (LOG.isWarnEnabled()) {
                LOG.warn(Messages.get().getBundle().key(Messages.LOG_EMPTY_PUBLISH_HISTORY_1, publishHistoryId));
            }
            return;
        }

        // read the export points and return immediately if there are no export points at all         
        Set<CmsExportPoint> exportPoints = new HashSet<CmsExportPoint>();
        exportPoints.addAll(OpenCms.getExportPoints());
        exportPoints.addAll(OpenCms.getModuleManager().getExportPoints());
        if (exportPoints.size() == 0) {
            if (LOG.isWarnEnabled()) {
                LOG.warn(Messages.get().getBundle().key(Messages.LOG_NO_EXPORT_POINTS_CONFIGURED_0));
            }
            return;
        }

        // create the driver to write the export points
        CmsExportPointDriver exportPointDriver = new CmsExportPointDriver(exportPoints);

        // the report may be null if the export point write was started by an event
        if (report == null) {
            if (dbc.getRequestContext() != null) {
                report = new CmsLogReport(dbc.getRequestContext().getLocale(), getClass());
            } else {
                report = new CmsLogReport(CmsLocaleManager.getDefaultLocale(), getClass());
            }
        }

        // iterate over all published resources to export them
        I_CmsVfsDriver vfsDriver = getVfsDriver(dbc);
        Iterator<CmsPublishedResource> i = publishedResources.iterator();
        while (i.hasNext()) {
            CmsPublishedResource currentPublishedResource = i.next();
            String currentExportPoint = exportPointDriver.getExportPoint(currentPublishedResource.getRootPath());

            if (currentExportPoint != null) {
                if (!printReportHeaders) {
                    report.println(Messages.get().container(Messages.RPT_EXPORT_POINTS_WRITE_BEGIN_0),
                            I_CmsReport.FORMAT_HEADLINE);
                    printReportHeaders = true;
                }

                // print report message
                if (currentPublishedResource.getState().isDeleted()) {
                    report.print(Messages.get().container(Messages.RPT_EXPORT_POINTS_DELETE_0),
                            I_CmsReport.FORMAT_NOTE);
                } else {
                    report.print(Messages.get().container(Messages.RPT_EXPORT_POINTS_WRITE_0),
                            I_CmsReport.FORMAT_NOTE);
                }
                report.print(org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_ARGUMENT_1,
                        currentPublishedResource.getRootPath()));
                report.print(org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_DOTS_0));

                if (currentPublishedResource.isFolder()) {
                    // export the folder                        
                    if (currentPublishedResource.getState().isDeleted()) {
                        exportPointDriver.deleteResource(currentPublishedResource.getRootPath(),
                                currentExportPoint);
                    } else {
                        exportPointDriver.createFolder(currentPublishedResource.getRootPath(), currentExportPoint);
                    }
                    report.println(
                            org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_OK_0),
                            I_CmsReport.FORMAT_OK);
                } else {
                    // export the file            
                    try {
                        if (currentPublishedResource.getState().isDeleted()) {
                            exportPointDriver.deleteResource(currentPublishedResource.getRootPath(),
                                    currentExportPoint);
                        } else {
                            // read the file content online
                            byte[] onlineContent = vfsDriver.readContent(dbc, CmsProject.ONLINE_PROJECT_ID,
                                    currentPublishedResource.getResourceId());
                            exportPointDriver.writeFile(currentPublishedResource.getRootPath(), currentExportPoint,
                                    onlineContent);
                        }
                        report.println(
                                org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_OK_0),
                                I_CmsReport.FORMAT_OK);
                    } catch (CmsException e) {
                        if (LOG.isErrorEnabled()) {
                            LOG.error(Messages.get().getBundle().key(Messages.LOG_WRITE_EXPORT_POINT_ERROR_1,
                                    currentPublishedResource.getRootPath()), e);
                        }
                        report.println(org.opencms.report.Messages.get()
                                .container(org.opencms.report.Messages.RPT_FAILED_0), I_CmsReport.FORMAT_ERROR);
                    }
                }
            }
        }
        if (printReportHeaders) {
            report.println(Messages.get().container(Messages.RPT_EXPORT_POINTS_WRITE_END_0),
                    I_CmsReport.FORMAT_HEADLINE);
        }
    }

    /**
     * Writes a resource to the OpenCms VFS, including it's content.<p>
     * 
     * Applies only to resources of type <code>{@link CmsFile}</code>
     * i.e. resources that have a binary content attached.<p>
     * 
     * Certain resource types might apply content validation or transformation rules 
     * before the resource is actually written to the VFS. The returned result
     * might therefore be a modified version from the provided original.<p>
     * 
     * @param dbc the current database context
     * @param resource the resource to apply this operation to
     * 
     * @return the written resource (may have been modified)
     *
     * @throws CmsException if something goes wrong
     * 
     * @see CmsObject#writeFile(CmsFile)
     * @see I_CmsResourceType#writeFile(CmsObject, CmsSecurityManager, CmsFile)
     */
    public CmsFile writeFile(CmsDbContext dbc, CmsFile resource) throws CmsException {

        resource.setUserLastModified(dbc.currentUser().getId());
        resource.setContents(resource.getContents()); // to be sure the content date is updated

        getVfsDriver(dbc).writeResource(dbc, dbc.currentProject().getUuid(), resource, UPDATE_RESOURCE_STATE);

        byte[] contents = resource.getContents();
        getVfsDriver(dbc).writeContent(dbc, resource.getResourceId(), contents);
        // log it
        log(dbc, new CmsLogEntry(dbc, resource.getStructureId(), CmsLogEntryType.RESOURCE_CONTENT_MODIFIED,
                new String[] { resource.getRootPath() }), false);

        // read the file back from db
        resource = new CmsFile(readResource(dbc, resource.getStructureId(), CmsResourceFilter.ALL));
        resource.setContents(contents);

        deleteRelationsWithSiblings(dbc, resource);

        // update the cache
        m_monitor.clearResourceCache();

        Map<String, Object> data = new HashMap<String, Object>(2);
        data.put(I_CmsEventListener.KEY_RESOURCE, resource);
        data.put(I_CmsEventListener.KEY_CHANGE, new Integer(CHANGED_CONTENT));
        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_MODIFIED, data));

        return resource;
    }

    /**
     * Writes an already existing group.<p>
     *
     * The group id has to be a valid OpenCms group id.<br>
     * 
     * The group with the given id will be completely overridden
     * by the given data.<p>
     * 
     * @param dbc the current database context
     * @param group the group that should be written
     * 
     * @throws CmsException if operation was not successful
     */
    public void writeGroup(CmsDbContext dbc, CmsGroup group) throws CmsException {

        CmsGroup oldGroup = readGroup(dbc, group.getName());
        m_monitor.uncacheGroup(oldGroup);
        getUserDriver(dbc).writeGroup(dbc, group);
        m_monitor.cacheGroup(group);

        if (!dbc.getProjectId().isNullUUID()) {
            // group modified event is not needed
            return;
        }
        // fire group modified event
        Map<String, Object> eventData = new HashMap<String, Object>();
        eventData.put(I_CmsEventListener.KEY_GROUP_ID, group.getId().toString());
        eventData.put(I_CmsEventListener.KEY_GROUP_NAME, oldGroup.getName());
        eventData.put(I_CmsEventListener.KEY_USER_ACTION, I_CmsEventListener.VALUE_GROUP_MODIFIED_ACTION_WRITE);
        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_GROUP_MODIFIED, eventData));
    }

    /**
     * Creates an historical entry of the current project.<p>
     * 
     * @param dbc the current database context
     * @param publishTag the version
     * @param publishDate the date of publishing
     *
     * @throws CmsDataAccessException if operation was not successful
     */
    public void writeHistoryProject(CmsDbContext dbc, int publishTag, long publishDate)
            throws CmsDataAccessException {

        getHistoryDriver(dbc).writeProject(dbc, publishTag, publishDate);
    }

    /**
     * Writes the locks that are currently stored in-memory to the database to allow restoring them  
     * in future server 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 {

        m_lockManager.writeLocks(dbc);
    }

    /**
     * Writes an already existing organizational unit.<p>
     *
     * The organizational unit id has to be a valid OpenCms organizational unit id.<br>
     * 
     * The organizational unit with the given id will be completely overridden
     * by the given data.<p>
     *
     * @param dbc the current db context
     * @param organizationalUnit the organizational unit that should be written
     * 
     * @throws CmsException if operation was not successful
     * 
     * @see org.opencms.security.CmsOrgUnitManager#writeOrganizationalUnit(CmsObject, CmsOrganizationalUnit)
     */
    public void writeOrganizationalUnit(CmsDbContext dbc, CmsOrganizationalUnit organizationalUnit)
            throws CmsException {

        m_monitor.uncacheOrgUnit(organizationalUnit);
        getUserDriver(dbc).writeOrganizationalUnit(dbc, organizationalUnit);

        // create a publish list for the 'virtual' publish event
        CmsResource ouRes = readResource(dbc, organizationalUnit.getId(), CmsResourceFilter.DEFAULT);
        CmsPublishList pl = new CmsPublishList(ouRes, false);
        pl.add(ouRes, false);

        getProjectDriver(dbc).writePublishHistory(dbc, pl.getPublishHistoryId(),
                new CmsPublishedResource(ouRes, -1, CmsResourceState.STATE_NEW));

        // fire the 'virtual' publish event
        Map<String, Object> eventData = new HashMap<String, Object>();
        eventData.put(I_CmsEventListener.KEY_PUBLISHID, pl.getPublishHistoryId().toString());
        eventData.put(I_CmsEventListener.KEY_PROJECTID, dbc.currentProject().getUuid());
        eventData.put(I_CmsEventListener.KEY_DBCONTEXT, dbc);
        CmsEvent afterPublishEvent = new CmsEvent(I_CmsEventListener.EVENT_PUBLISH_PROJECT, eventData);
        OpenCms.fireCmsEvent(afterPublishEvent);

        m_monitor.cacheOrgUnit(organizationalUnit);
    }

    /**
     * Writes an already existing project.<p>
     *
     * The project id has to be a valid OpenCms project id.<br>
     * 
     * The project with the given id will be completely overridden
     * by the given data.<p>
     *
     * @param dbc the current database context
     * @param project the project that should be written
     * 
     * @throws CmsException if operation was not successful
     */
    public void writeProject(CmsDbContext dbc, CmsProject project) throws CmsException {

        m_monitor.uncacheProject(project);
        getProjectDriver(dbc).writeProject(dbc, project);
        m_monitor.cacheProject(project);
    }

    /**
     * Writes a property for a specified resource.<p>
     * 
     * @param dbc the current database context
     * @param resource the resource to write the property for
     * @param property the property to write
     * 
     * @throws CmsException if something goes wrong
     * 
     * @see CmsObject#writePropertyObject(String, CmsProperty)
     * @see I_CmsResourceType#writePropertyObject(CmsObject, CmsSecurityManager, CmsResource, CmsProperty)
     */
    public void writePropertyObject(CmsDbContext dbc, CmsResource resource, CmsProperty property)
            throws CmsException {

        try {
            if (property == CmsProperty.getNullProperty()) {
                // skip empty or null properties
                return;
            }

            // test if and what state should be updated
            // 0: none, 1: structure, 2: resource
            int updateState = getUpdateState(dbc, resource, Collections.singletonList(property));

            // write the property
            getVfsDriver(dbc).writePropertyObject(dbc, dbc.currentProject(), resource, property);

            if (updateState > 0) {
                updateState(dbc, resource, updateState == 2);
            }
            // log it
            log(dbc, new CmsLogEntry(dbc, resource.getStructureId(), CmsLogEntryType.RESOURCE_PROPERTIES,
                    new String[] { resource.getRootPath() }), false);

        } finally {
            // update the driver manager cache
            m_monitor.clearResourceCache();
            m_monitor.flushCache(CmsMemoryMonitor.CacheType.PROPERTY, CmsMemoryMonitor.CacheType.PROPERTY_LIST);

            // fire an event that a property of a resource has been modified
            Map<String, Object> data = new HashMap<String, Object>();
            data.put(I_CmsEventListener.KEY_RESOURCE, resource);
            data.put("property", property);
            OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_PROPERTY_MODIFIED, data));
        }
    }

    /**
     * Writes a list of properties for a specified resource.<p>
     * 
     * Code calling this method has to ensure that the no properties 
     * <code>a, b</code> are contained in the specified list so that <code>a.equals(b)</code>, 
     * otherwise an exception is thrown.<p>
     * 
     * @param dbc the current database context
     * @param resource the resource to write the properties for
     * @param properties the list of properties to write
     * @param updateState if <code>true</code> the state of the resource will be updated
     * 
     * @throws CmsException if something goes wrong
     * 
     * @see CmsObject#writePropertyObjects(String, List)
     * @see I_CmsResourceType#writePropertyObjects(CmsObject, CmsSecurityManager, CmsResource, List)
     */
    public void writePropertyObjects(CmsDbContext dbc, CmsResource resource, List<CmsProperty> properties,
            boolean updateState) throws CmsException {

        if ((properties == null) || (properties.size() == 0)) {
            // skip empty or null lists
            return;
        }

        try {
            // the specified list must not contain two or more equal property objects
            for (int i = 0, n = properties.size(); i < n; i++) {
                Set<String> keyValidationSet = new HashSet<String>();
                CmsProperty property = properties.get(i);
                if (!keyValidationSet.contains(property.getName())) {
                    keyValidationSet.add(property.getName());
                } else {
                    throw new CmsVfsException(
                            Messages.get().container(Messages.ERR_VFS_INVALID_PROPERTY_LIST_1, property.getName()));
                }
            }

            // test if and what state should be updated
            // 0: none, 1: structure, 2: resource
            int updateStateValue = 0;
            if (updateState) {
                updateStateValue = getUpdateState(dbc, resource, properties);
            }
            I_CmsVfsDriver vfsDriver = getVfsDriver(dbc);
            for (int i = 0; i < properties.size(); i++) {
                // write the property
                CmsProperty property = properties.get(i);
                vfsDriver.writePropertyObject(dbc, dbc.currentProject(), resource, property);
            }

            if (updateStateValue > 0) {
                // update state
                updateState(dbc, resource, (updateStateValue == 2));
            }

            if (updateState) {
                // log it
                log(dbc, new CmsLogEntry(dbc, resource.getStructureId(), CmsLogEntryType.RESOURCE_PROPERTIES,
                        new String[] { resource.getRootPath() }), false);
            }
        } finally {
            // update the driver manager cache
            m_monitor.clearResourceCache();
            m_monitor.flushCache(CmsMemoryMonitor.CacheType.PROPERTY, CmsMemoryMonitor.CacheType.PROPERTY_LIST);

            // fire an event that the properties of a resource have been modified
            OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_AND_PROPERTIES_MODIFIED,
                    Collections.<String, Object>singletonMap(I_CmsEventListener.KEY_RESOURCE, resource)));
        }
    }

    /**
     * Updates a publish job.<p>
     * 
     * @param dbc the current database context
     * @param publishJob the publish job to update
     * 
     * @throws CmsException if something goes wrong
     */
    public void writePublishJob(CmsDbContext dbc, CmsPublishJobInfoBean publishJob) throws CmsException {

        getProjectDriver(dbc).writePublishJob(dbc, publishJob);
    }

    /**
     * Writes the publish report for a publish job.<p>
     * 
     * @param dbc the current database context
     * @param publishJob the publish job 
     * @throws CmsException if something goes wrong
     */
    public void writePublishReport(CmsDbContext dbc, CmsPublishJobInfoBean publishJob) throws CmsException {

        CmsPublishReport report = (CmsPublishReport) publishJob.removePublishReport();

        if (report != null) {
            getProjectDriver(dbc).writePublishReport(dbc, publishJob.getPublishHistoryId(), report.getContents());
        }
    }

    /**
     * Writes a resource to the OpenCms VFS.<p>
     * 
     * @param dbc the current database context
     * @param resource the resource to write
     *
     * @throws CmsException if something goes wrong
     */
    public void writeResource(CmsDbContext dbc, CmsResource resource) throws CmsException {

        // access was granted - write the resource
        resource.setUserLastModified(dbc.currentUser().getId());
        CmsUUID projectId = ((dbc.getProjectId() == null) || dbc.getProjectId().isNullUUID())
                ? dbc.currentProject().getUuid()
                : dbc.getProjectId();

        getVfsDriver(dbc).writeResource(dbc, projectId, resource, UPDATE_RESOURCE_STATE);

        // make sure the written resource has the state correctly set
        if (resource.getState().isUnchanged()) {
            resource.setState(CmsResource.STATE_CHANGED);
        }

        // delete in content relations if the new type is not parseable
        if (!(OpenCms.getResourceManager().getResourceType(resource.getTypeId()) instanceof I_CmsLinkParseable)) {
            deleteRelationsWithSiblings(dbc, resource);
        }

        // update the cache
        m_monitor.clearResourceCache();
        Map<String, Object> data = new HashMap<String, Object>(2);
        data.put(I_CmsEventListener.KEY_RESOURCE, resource);
        data.put(I_CmsEventListener.KEY_CHANGE, new Integer(CHANGED_RESOURCE));
        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_MODIFIED, data));
    }

    /**
     * Inserts an entry in the published resource table.<p>
     * 
     * This is done during static export.<p>
     * 
     * @param dbc the current database context
     * @param resourceName The name of the resource to be added to the static export
     * @param linkType the type of resource exported (0= non-parameter, 1=parameter)
     * @param linkParameter the parameters added to the resource
     * @param timestamp a time stamp for writing the data into the db
     * 
     * @throws CmsException if something goes wrong
     */
    public void writeStaticExportPublishedResource(CmsDbContext dbc, String resourceName, int linkType,
            String linkParameter, long timestamp) throws CmsException {

        getProjectDriver(dbc).writeStaticExportPublishedResource(dbc, resourceName, linkType, linkParameter,
                timestamp);
    }

    /**
     * Adds a new url name mapping for a structure id.<p>
     * 
     * Instead of taking the name directly, this method takes an iterator of strings 
     * which generates candidate URL names on-the-fly. The first generated name which is
     * not already mapped to another structure id will be chosen for the new URL name mapping.
     * 
     * @param dbc the current database context
     * @param nameSeq the sequence of URL name candidates  
     * @param structureId the structure id to which the url name should be mapped 
     * @param locale the locale for which the mapping should be written 
     * 
     * @return the actual name which was mapped to the structure id 
     * 
     * @throws CmsDataAccessException if something goes wrong 
     */
    public String writeUrlNameMapping(CmsDbContext dbc, Iterator<String> nameSeq, CmsUUID structureId,
            String locale) throws CmsDataAccessException {

        String bestName = findBestNameForUrlNameMapping(dbc, nameSeq, structureId, locale);
        addOrReplaceUrlNameMapping(dbc, bestName, structureId, locale);
        return bestName;
    }

    /**
     * Updates the user information. <p>
     * 
     * The user id has to be a valid OpenCms user id.<br>
     * 
     * The user with the given id will be completely overridden
     * by the given data.<p>
     *
     * @param dbc the current database context
     * @param user the user to be updated
     *
     * @throws CmsException if operation was not successful
     */
    public void writeUser(CmsDbContext dbc, CmsUser user) throws CmsException {

        CmsUser oldUser = readUser(dbc, user.getId());
        m_monitor.clearUserCache(oldUser);
        getUserDriver(dbc).writeUser(dbc, user);
        m_monitor.flushCache(CmsMemoryMonitor.CacheType.USERGROUPS, CmsMemoryMonitor.CacheType.USER_LIST);

        if (!dbc.getProjectId().isNullUUID()) {
            // user modified event is not needed
            return;
        }
        // fire user modified event
        Map<String, Object> eventData = new HashMap<String, Object>();
        eventData.put(I_CmsEventListener.KEY_USER_ID, user.getId().toString());
        eventData.put(I_CmsEventListener.KEY_USER_NAME, oldUser.getName());
        eventData.put(I_CmsEventListener.KEY_USER_ACTION, I_CmsEventListener.VALUE_USER_MODIFIED_ACTION_WRITE_USER);
        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_USER_MODIFIED, eventData));
    }

    /**
     * Adds or replaces a new url name mapping in the offline project.<p>
     * 
     * @param dbc the current database context 
     * @param name the URL name of the mapping 
     * @param structureId the structure id of the mapping 
     * @param locale the locale of the mapping 
     * 
     * @throws CmsDataAccessException if something goes wrong 
     */
    protected void addOrReplaceUrlNameMapping(CmsDbContext dbc, String name, CmsUUID structureId, String locale)
            throws CmsDataAccessException {

        getVfsDriver(dbc).deleteUrlNameMappingEntries(dbc, false,
                CmsUrlNameMappingFilter.ALL.filterStructureId(structureId).filterLocale(locale)
                        .filterState(CmsUrlNameMappingEntry.MAPPING_STATUS_NEW));
        CmsUrlNameMappingEntry newEntry = new CmsUrlNameMappingEntry(name, structureId,
                CmsUrlNameMappingEntry.MAPPING_STATUS_NEW, System.currentTimeMillis(), locale);
        getVfsDriver(dbc).addUrlNameMappingEntry(dbc, false, newEntry);
    }

    /**
     * Converts a resource to a folder (if possible).<p>
     * 
     * @param resource the resource to convert
     * @return the converted resource 
     * 
     * @throws CmsVfsResourceNotFoundException if the resource is not a folder
     */
    protected CmsFolder convertResourceToFolder(CmsResource resource) throws CmsVfsResourceNotFoundException {

        if (resource.isFolder()) {
            return new CmsFolder(resource);
        }

        throw new CmsVfsResourceNotFoundException(
                Messages.get().container(Messages.ERR_ACCESS_FILE_AS_FOLDER_1, resource.getRootPath()));
    }

    /**
     * Helper method for creating a driver from configuration data.<p>
     * 
     * @param dbc the db context
     * @param configManager the configuration manager 
     * @param config the configuration
     * @param driverChainKey the configuration key under which the driver chain is stored  
     * @param suffix the suffix to append to a driver chain entry to get the key for the driver class
     *  
     * @return the newly created driver 
     */
    protected Object createDriver(CmsDbContext dbc, CmsConfigurationManager configManager,
            CmsParameterConfiguration config, String driverChainKey, String suffix) {

        // read the vfs driver class properties and initialize a new instance 
        List<String> drivers = config.getList(driverChainKey);
        String driverKey = drivers.get(0) + suffix;
        String driverName = config.get(driverKey);
        drivers = (drivers.size() > 1) ? drivers.subList(1, drivers.size()) : null;
        if (driverName == null) {
            CmsLog.INIT.error(Messages.get().getBundle().key(Messages.INIT_DRIVER_FAILED_1, driverKey));
        }
        return newDriverInstance(dbc, configManager, driverName, drivers);
    }

    /**
     * Deletes all relations for the given resource and all its siblings.<p>
     * 
     * @param dbc the current database context
     * @param resource the resource to delete the resource for
     * 
     * @throws CmsException if something goes wrong 
     */
    protected void deleteRelationsWithSiblings(CmsDbContext dbc, CmsResource resource) throws CmsException {

        // get all siblings
        List<CmsResource> siblings;
        if (resource.getSiblingCount() > 1) {
            siblings = readSiblings(dbc, resource, CmsResourceFilter.ALL);
        } else {
            siblings = new ArrayList<CmsResource>();
            siblings.add(resource);
        }
        // clean the relations in content for all siblings 
        I_CmsVfsDriver vfsDriver = getVfsDriver(dbc);
        Iterator<CmsResource> it = siblings.iterator();
        while (it.hasNext()) {
            CmsResource sibling = it.next();
            // clean the relation information for this sibling
            vfsDriver.deleteRelations(dbc, dbc.currentProject().getUuid(), sibling,
                    CmsRelationFilter.TARGETS.filterDefinedInContent());
        }
    }

    /**
     * Tries to add sub-resources of moved folders to the publish list and throws an exception if the publish list still does 
     * not contain some  sub-resources of the moved folders.<p>
     *  
     * @param cms the current CMS context 
     * @param dbc the current database context 
     * @param pubList the publish list 
     * @throws CmsException if something goes wrong 
     */
    protected void ensureSubResourcesOfMovedFoldersPublished(CmsObject cms, CmsDbContext dbc,
            CmsPublishList pubList) throws CmsException {

        List<CmsResource> topMovedFolders = pubList.getTopMovedFolders(cms);
        Iterator<CmsResource> folderIt = topMovedFolders.iterator();
        while (folderIt.hasNext()) {
            CmsResource folder = folderIt.next();
            addSubResources(dbc, pubList, folder);
        }
        CmsResource checkRes = pubList.checkContainsSubResources(cms, topMovedFolders);
        if (checkRes != null) {
            throw new CmsVfsException(Messages.get()
                    .container(Messages.RPT_CHILDREN_OF_MOVED_FOLDER_NOT_PUBLISHED_1, checkRes.getRootPath()));
        }

    }

    /**
     * Tries to find the best name for an URL name mapping for the given structure id.<p>
     * 
     * @param dbc the database context 
     * @param nameSeq the sequence of name candidates 
     * @param structureId the structure id to which an URL name should be mapped
     * @param locale the locale for which the URL name should be mapped 
     *  
     * @return the selected URL name candidate 
     * 
     * @throws CmsDataAccessException if something goes wrong 
     */
    protected String findBestNameForUrlNameMapping(CmsDbContext dbc, Iterator<String> nameSeq, CmsUUID structureId,
            String locale) throws CmsDataAccessException {

        String newName;
        boolean alreadyInUse;
        do {
            newName = nameSeq.next();
            alreadyInUse = false;
            CmsUrlNameMappingFilter filter = CmsUrlNameMappingFilter.ALL.filterName(newName);
            List<CmsUrlNameMappingEntry> entriesWithSameName = getVfsDriver(dbc).readUrlNameMappingEntries(dbc,
                    false, filter);
            for (CmsUrlNameMappingEntry entry : entriesWithSameName) {
                boolean sameId = entry.getStructureId().equals(structureId);
                boolean sameLocale = Objects.equal(entry.getLocale(), locale);
                if (!sameId || !sameLocale) {
                    // name already used for other resource, or for different locale of the same resource
                    alreadyInUse = true;
                    break;
                }
            }
        } while (alreadyInUse);
        return newName;
    }

    /**
     * Helper method for finding the 'best' URL name to use for a new URL name mapping.<p>  
     * 
     * Since the name given as a parameter may be already used, this method will try to append numeric suffixes
     * to the name to find a mapping name which is not used.<p>
     * 
     * @param dbc the current database context 
     * @param name the name of the mapping 
     * @param structureId the structure id to which the name is mapped
     *  
     * @return the best name which was found for the new mapping 
     * 
     * @throws CmsDataAccessException if something goes wrong 
     */
    protected String findBestNameForUrlNameMapping(CmsDbContext dbc, String name, CmsUUID structureId)
            throws CmsDataAccessException {

        List<CmsUrlNameMappingEntry> entriesStartingWithName = getVfsDriver(dbc).readUrlNameMappingEntries(dbc,
                false,
                CmsUrlNameMappingFilter.ALL.filterNamePattern(name + "%").filterRejectStructureId(structureId));
        Set<String> usedNames = new HashSet<String>();
        for (CmsUrlNameMappingEntry entry : entriesStartingWithName) {
            usedNames.add(entry.getName());
        }
        int counter = 0;
        String numberedName;
        do {
            numberedName = getNumberedName(name, counter);
            counter += 1;
        } while (usedNames.contains(numberedName));
        return numberedName;
    }

    /**
     * Returns the lock manager instance.<p>
     * 
     * @return the lock manager instance
     */
    protected CmsLockManager getLockManager() {

        return m_lockManager;
    }

    /**
     * Adds a numeric suffix to the end of a string, unless the number passed as a parameter is 0.<p>
     *  
     * @param name the base name 
     * @param number the number from which to form the suffix 
     * 
     * @return the concatenation of the base name and possibly the numeric suffix 
     */
    protected String getNumberedName(String name, int number) {

        if (number == 0) {
            return name;
        }
        PrintfFormat fmt = new PrintfFormat("%0.6d");
        return name + "_" + fmt.sprintf(number);
    }

    /**
     * Counts the total number of users which fit the given criteria.<p>
     * 
     * @param dbc the database context 
     * @param searchParams the user search criteria 
     * 
     * @return the total number of users matching the criteria 
     * 
     * @throws CmsDataAccessException if something goes wrong 
     */
    long countUsers(CmsDbContext dbc, CmsUserSearchParameters searchParams) throws CmsDataAccessException {

        return getUserDriver(dbc).countUsers(dbc, searchParams);
    }

    private void addSubResources(CmsDbContext dbc, CmsPublishList publishList, CmsResource directPublishResource)
            throws CmsDataAccessException {

        int flags = CmsDriverManager.READMODE_INCLUDE_TREE | CmsDriverManager.READMODE_EXCLUDE_STATE;
        if (!directPublishResource.getState().isDeleted()) {
            // fix for org.opencms.file.TestPublishIssues#testPublishFolderWithDeletedFileFromOtherProject
            flags = flags | CmsDriverManager.READMODE_INCLUDE_PROJECT;
        }

        // add all sub resources of the folder
        List<CmsResource> folderList = getVfsDriver(dbc).readResourceTree(dbc, dbc.currentProject().getUuid(),
                directPublishResource.getRootPath(), CmsDriverManager.READ_IGNORE_TYPE, CmsResource.STATE_UNCHANGED,
                CmsDriverManager.READ_IGNORE_TIME, CmsDriverManager.READ_IGNORE_TIME,
                CmsDriverManager.READ_IGNORE_TIME, CmsDriverManager.READ_IGNORE_TIME,
                CmsDriverManager.READ_IGNORE_TIME, CmsDriverManager.READ_IGNORE_TIME,
                flags | CmsDriverManager.READMODE_ONLY_FOLDERS);

        publishList.addAll(filterResources(dbc, publishList, folderList), true);

        List<CmsResource> fileList = getVfsDriver(dbc).readResourceTree(dbc, dbc.currentProject().getUuid(),
                directPublishResource.getRootPath(), CmsDriverManager.READ_IGNORE_TYPE, CmsResource.STATE_UNCHANGED,
                CmsDriverManager.READ_IGNORE_TIME, CmsDriverManager.READ_IGNORE_TIME,
                CmsDriverManager.READ_IGNORE_TIME, CmsDriverManager.READ_IGNORE_TIME,
                CmsDriverManager.READ_IGNORE_TIME, CmsDriverManager.READ_IGNORE_TIME,
                flags | CmsDriverManager.READMODE_ONLY_FILES);

        publishList.addAll(filterResources(dbc, publishList, fileList), true);
    }

    /**
     * Checks the parent of a resource during publishing.<p> 
     * 
     * @param dbc the current database context
     * @param deletedFolders a list of deleted folders
     * @param res a resource to check the parent for
     * 
     * @return <code>true</code> if the parent resource will be deleted during publishing
     */
    private boolean checkDeletedParentFolder(CmsDbContext dbc, List<CmsResource> deletedFolders, CmsResource res) {

        String parentPath = CmsResource.getParentFolder(res.getRootPath());

        if (parentPath == null) {
            // resource has no parent
            return false;
        }

        CmsResource parent;
        try {
            parent = readResource(dbc, parentPath, CmsResourceFilter.ALL);
        } catch (Exception e) {
            // failure: if we cannot read the parent, we should not publish the resource
            return false;
        }

        if (!parent.getState().isDeleted()) {
            // parent is not deleted
            return false;
        }

        for (int j = 0; j < deletedFolders.size(); j++) {
            if ((deletedFolders.get(j)).getStructureId().equals(parent.getStructureId())) {
                // parent is deleted, and it will get published
                return true;
            }
        }

        // parent is new, but it will not get published
        return false;
    }

    /**
     * Checks that no one of the resources to be published has a 'new' parent (that has not been published yet).<p> 
     * 
     * @param dbc the db context
     * @param publishList the publish list to check
     * 
     * @throws CmsVfsException if there is a resource to be published with a 'new' parent
     */
    private void checkParentFolders(CmsDbContext dbc, CmsPublishList publishList) throws CmsVfsException {

        boolean directPublish = publishList.isDirectPublish();
        // if we direct publish a file, check if all parent folders are already published
        if (directPublish) {
            // first get the names of all parent folders
            Iterator<CmsResource> it = publishList.getDirectPublishResources().iterator();
            List<String> parentFolderNames = new ArrayList<String>();
            while (it.hasNext()) {
                CmsResource res = it.next();
                String parentFolderName = CmsResource.getParentFolder(res.getRootPath());
                if (parentFolderName != null) {
                    parentFolderNames.add(parentFolderName);
                }
            }
            // remove duplicate parent folder names
            parentFolderNames = CmsFileUtil.removeRedundancies(parentFolderNames);
            String parentFolderName = null;
            try {
                I_CmsVfsDriver vfsDriver = getVfsDriver(dbc);
                // now check all folders if they exist in the online project
                Iterator<String> parentIt = parentFolderNames.iterator();
                while (parentIt.hasNext()) {
                    parentFolderName = parentIt.next();
                    vfsDriver.readFolder(dbc, CmsProject.ONLINE_PROJECT_ID, parentFolderName);
                }
            } catch (CmsException e) {
                throw new CmsVfsException(
                        Messages.get().container(Messages.RPT_PARENT_FOLDER_NOT_PUBLISHED_1, parentFolderName));
            }
        }
    }

    /**
     * Checks the parent of a resource during publishing.<p> 
     * 
     * @param dbc the current database context
     * @param folderList a list of folders
     * @param res a resource to check the parent for
     * 
     * @return true if the resource should be published
     */
    private boolean checkParentResource(CmsDbContext dbc, List<CmsResource> folderList, CmsResource res) {

        String parentPath = CmsResource.getParentFolder(res.getRootPath());

        if (parentPath == null) {
            // resource has no parent
            return true;
        }

        CmsResource parent;
        try {
            parent = readResource(dbc, parentPath, CmsResourceFilter.ALL);
        } catch (Exception e) {
            // failure: if we cannot read the parent, we should not publish the resource
            return false;
        }

        if (!parent.getState().isNew()) {
            // parent is already published
            return true;
        }

        for (int j = 0; j < folderList.size(); j++) {
            if (folderList.get(j).getStructureId().equals(parent.getStructureId())) {
                // parent is new, but it will get published
                return true;
            }
        }

        // parent is new, but it will not get published
        return false;
    }

    /**
     * Copies all relations from the source resource to the target resource.<p>
     * 
     * @param dbc the database context
     * @param source the source
     * @param target the target
     * 
     * @throws CmsException if something goes wrong
     */
    private void copyRelations(CmsDbContext dbc, CmsResource source, CmsResource target) throws CmsException {

        // copy relations all relations
        CmsObject cms = new CmsObject(getSecurityManager(), dbc.getRequestContext());
        Iterator<CmsRelation> itRelations = getRelationsForResource(dbc, source,
                CmsRelationFilter.TARGETS.filterNotDefinedInContent()).iterator();
        while (itRelations.hasNext()) {
            CmsRelation relation = itRelations.next();
            try {
                CmsResource relTarget = relation.getTarget(cms, CmsResourceFilter.ALL);
                addRelationToResource(dbc, target, relTarget, relation.getType(), true);
            } catch (CmsVfsResourceNotFoundException e) {
                // ignore this broken relation
                if (LOG.isWarnEnabled()) {
                    LOG.warn(e.getLocalizedMessage(), e);
                }
            }
        }
        // repair categories
        repairCategories(dbc, getProjectIdForContext(dbc), target);
    }

    /**
     * Filters the given list of resources, removes all resources where the current user
     * does not have READ permissions, plus the filter is applied.<p>
     * 
     * @param dbc the current database context
     * @param resourceList a list of CmsResources
     * @param filter the resource filter to use
     * 
     * @return the filtered list of resources
     * 
     * @throws CmsException in case errors testing the permissions
     */
    private List<CmsResource> filterPermissions(CmsDbContext dbc, List<CmsResource> resourceList,
            CmsResourceFilter filter) throws CmsException {

        if (filter.requireTimerange()) {
            // never check time range here - this must be done later in #updateContextDates(...)
            filter = filter.addExcludeTimerange();
        }
        ArrayList<CmsResource> result = new ArrayList<CmsResource>(resourceList.size());
        for (int i = 0; i < resourceList.size(); i++) {
            // check the permission of all resources
            CmsResource currentResource = resourceList.get(i);
            if (m_securityManager.hasPermissions(dbc, currentResource, CmsPermissionSet.ACCESS_READ, true, filter)
                    .isAllowed()) {
                // only return resources where permission was granted
                result.add(currentResource);
            }
        }
        // return the result
        return result;
    }

    /**
     * Returns a filtered list of resources for publishing.<p>
     * Contains all resources, which are not locked 
     * and which have a parent folder that is already published or will be published, too.<p>
     * 
     * @param dbc the current database context
     * @param publishList the filling publish list
     * @param resourceList the list of resources to filter
     * 
     * @return a filtered list of resources
     */
    private List<CmsResource> filterResources(CmsDbContext dbc, CmsPublishList publishList,
            List<CmsResource> resourceList) {

        List<CmsResource> result = new ArrayList<CmsResource>();

        // local folder list for adding new publishing subfolders
        // this solves the {@link org.opencms.file.TestPublishIssues#testPublishScenarioD} problem.
        List<CmsResource> newFolderList = new ArrayList<CmsResource>(
                publishList == null ? resourceList : publishList.getFolderList());

        for (int i = 0; i < resourceList.size(); i++) {
            CmsResource res = resourceList.get(i);
            try {
                CmsLock lock = getLock(dbc, res);
                if (lock.isPublish()) {
                    // if already enqueued
                    continue;
                }
                if (!lock.isLockableBy(dbc.currentUser())) {
                    // checks if there is a shared lock and if the resource is deleted
                    // this solves the {@link org.opencms.file.TestPublishIssues#testPublishScenarioE} problem.
                    if (lock.isShared() && (publishList != null)) {
                        if (!res.getState().isDeleted()
                                || !checkDeletedParentFolder(dbc, publishList.getDeletedFolderList(), res)) {
                            continue;
                        }
                    } else {
                        // don't add locked resources
                        continue;
                    }
                }
                if (!"/".equals(res.getRootPath()) && !checkParentResource(dbc, newFolderList, res)) {
                    continue;
                }
                // check permissions
                try {
                    m_securityManager.checkPermissions(dbc, res, CmsPermissionSet.ACCESS_DIRECT_PUBLISH, false,
                            CmsResourceFilter.ALL);
                } catch (CmsException e) {
                    // skip if not enough permissions
                    continue;
                }
                if (res.isFolder()) {
                    newFolderList.add(res);
                }
                result.add(res);
            } catch (Exception e) {
                // should never happen
                LOG.error(e.getLocalizedMessage(), e);
            }
        }
        return result;
    }

    /**
     * Returns a filtered list of sibling resources for publishing.<p>
     * 
     * Contains all siblings of the given resources, which are not locked
     * and which have a parent folder that is already published or will be published, too.<p>
     * 
     * @param dbc the current database context 
     * @param publishList the unfinished publish list
     * @param resourceList the list of siblings to filter
     * 
     * @return a filtered list of sibling resources for publishing
     */
    private List<CmsResource> filterSiblings(CmsDbContext dbc, CmsPublishList publishList,
            Collection<CmsResource> resourceList) {

        List<CmsResource> result = new ArrayList<CmsResource>();

        // removed internal extendible folder list, since iterated (sibling) resources are files in any case, never folders

        for (Iterator<CmsResource> i = resourceList.iterator(); i.hasNext();) {
            CmsResource res = i.next();
            try {
                CmsLock lock = getLock(dbc, res);
                if (lock.isPublish()) {
                    // if already enqueued
                    continue;
                }
                if (!lock.isLockableBy(dbc.currentUser())) {
                    // checks if there is a shared lock and if the resource is deleted
                    // this solves the {@link org.opencms.file.TestPublishIssues#testPublishScenarioE} problem.
                    if (lock.isShared() && (publishList != null)) {
                        if (!res.getState().isDeleted()
                                || !checkDeletedParentFolder(dbc, publishList.getDeletedFolderList(), res)) {
                            continue;
                        }
                    } else {
                        // don't add locked resources
                        continue;
                    }
                }
                if (!"/".equals(res.getRootPath()) && !checkParentResource(dbc, publishList.getFolderList(), res)) {
                    // don't add resources that have no parent in the online project
                    continue;
                }
                // check permissions
                try {
                    m_securityManager.checkPermissions(dbc, res, CmsPermissionSet.ACCESS_DIRECT_PUBLISH, false,
                            CmsResourceFilter.ALL);
                } catch (CmsException e) {
                    // skip if not enough permissions
                    continue;
                }
                result.add(res);
            } catch (Exception e) {
                // should never happen
                LOG.error(e.getLocalizedMessage(), e);
            }
        }
        return result;
    }

    /**
     * Returns the access control list of a given resource.<p>
     * 
     * @param dbc the current database context
     * @param resource the resource
     * @param forFolder should be true if resource is a folder
     * @param depth the depth to include non-inherited access entries, also
     * @param inheritedOnly flag indicates to collect inherited permissions only
     * 
     * @return the access control list of the resource
     * 
     * @throws CmsException if something goes wrong
     */
    private CmsAccessControlList getAccessControlList(CmsDbContext dbc, CmsResource resource, boolean inheritedOnly,
            boolean forFolder, int depth) throws CmsException {

        String cacheKey = getCacheKey(new String[] { inheritedOnly ? "+" : "-", forFolder ? "+" : "-",
                Integer.toString(depth), resource.getStructureId().toString() }, dbc);

        CmsAccessControlList acl = m_monitor.getCachedACL(cacheKey);

        // return the cached acl if already available
        if ((acl != null) && dbc.getProjectId().isNullUUID()) {
            return acl;
        }

        List<CmsAccessControlEntry> aces = getUserDriver(dbc).readAccessControlEntries(dbc, dbc.currentProject(),
                resource.getResourceId(), (depth > 1) || ((depth > 0) && forFolder));

        // sort the list of aces
        boolean overwriteAll = sortAceList(aces);

        // if no 'overwrite all' ace was found
        if (!overwriteAll) {
            // get the acl of the parent
            CmsResource parentResource = null;
            try {
                // try to recurse over the id
                parentResource = getVfsDriver(dbc).readParentFolder(dbc, dbc.currentProject().getUuid(),
                        resource.getStructureId());
            } catch (CmsVfsResourceNotFoundException e) {
                // should never happen, but try with the path
                String parentPath = CmsResource.getParentFolder(resource.getRootPath());
                if (parentPath != null) {
                    parentResource = getVfsDriver(dbc).readFolder(dbc, dbc.currentProject().getUuid(), parentPath);
                }
            }
            if (parentResource != null) {
                acl = (CmsAccessControlList) getAccessControlList(dbc, parentResource, inheritedOnly, forFolder,
                        depth + 1).clone();
            }
        }
        if (acl == null) {
            acl = new CmsAccessControlList();
        }

        if (!((depth == 0) && inheritedOnly)) {
            Iterator<CmsAccessControlEntry> itAces = aces.iterator();
            while (itAces.hasNext()) {
                CmsAccessControlEntry acEntry = itAces.next();
                if (depth > 0) {
                    acEntry.setFlags(CmsAccessControlEntry.ACCESS_FLAGS_INHERITED);
                }

                acl.add(acEntry);

                // if the overwrite flag is set, reset the allowed permissions to the permissions of this entry
                // denied permissions are kept or extended
                if ((acEntry.getFlags() & CmsAccessControlEntry.ACCESS_FLAGS_OVERWRITE) > 0) {
                    acl.setAllowedPermissions(acEntry);
                }
            }
        }
        if (dbc.getProjectId().isNullUUID()) {
            m_monitor.cacheACL(cacheKey, acl);
        }
        return acl;
    }

    /**
     * Return a cache key build from the provided information.<p>
     * 
     * @param prefix a prefix for the key
     * @param flag a boolean flag for the key (only used if prefix is not null)
     * @param projectId the project for which to generate the key
     * @param resource the resource for which to generate the key
     * 
     * @return String a cache key build from the provided information
     */
    private String getCacheKey(String prefix, boolean flag, CmsUUID projectId, String resource) {

        StringBuffer b = new StringBuffer(64);
        if (prefix != null) {
            b.append(prefix);
            b.append(flag ? '+' : '-');
        }
        b.append(CmsProject.isOnlineProject(projectId) ? '+' : '-');
        return b.append(resource).toString();
    }

    /**
     * Return a cache key build from the provided information.<p>
     * 
     * @param keys an array of keys to generate the cache key from
     * @param dbc the database context for which to generate the key
     *
     * @return String a cache key build from the provided information
     */
    private String getCacheKey(String[] keys, CmsDbContext dbc) {

        if (!dbc.getProjectId().isNullUUID()) {
            return "";
        }
        StringBuffer b = new StringBuffer(64);
        int len = keys.length;
        if (len > 0) {
            for (int i = 0; i < len; i++) {
                b.append(keys[i]);
                b.append('_');
            }
        }
        if (dbc.currentProject().isOnlineProject()) {
            b.append("+");
        } else {
            b.append("-");
        }
        return b.toString();
    }

    /**
     * Returns the correct project id.<p>
     * 
     * @param dbc the database context
     * 
     * @return the correct project id
     */
    private CmsUUID getProjectIdForContext(CmsDbContext dbc) {

        CmsUUID projectId = dbc.getProjectId();
        if (projectId.isNullUUID()) {
            projectId = dbc.currentProject().getUuid();
        }
        return projectId;
    }

    /**
     * Returns if and what state needs to be updated.<p>
     * 
     * @param dbc the db context
     * @param resource the resource
     * @param properties the properties to check
     * 
     * @return 0: none, 1: structure, 2: resource
     * 
     * @throws CmsDataAccessException if something goes wrong
     */
    private int getUpdateState(CmsDbContext dbc, CmsResource resource, List<CmsProperty> properties)
            throws CmsDataAccessException {

        int updateState = 0;
        I_CmsVfsDriver vfsDriver = getVfsDriver(dbc);
        Iterator<CmsProperty> it = properties.iterator();
        while (it.hasNext() && (updateState < 2)) {
            CmsProperty property = it.next();

            // read existing property
            CmsProperty existingProperty = vfsDriver.readPropertyObject(dbc, property.getName(),
                    dbc.currentProject(), resource);

            // check the shared property
            if (property.getResourceValue() != null) {
                if (property.isDeleteResourceValue()) {
                    if (existingProperty.getResourceValue() != null) {
                        updateState = 2; // deleted
                    }
                } else {
                    if (existingProperty.getResourceValue() == null) {
                        updateState = 2; // created
                    } else {
                        if (!property.getResourceValue().equals(existingProperty.getResourceValue())) {
                            updateState = 2; // updated
                        }
                    }
                }
            }
            if (updateState == 0) {
                // check the individual property only if needed
                if (property.getStructureValue() != null) {
                    if (property.isDeleteStructureValue()) {
                        if (existingProperty.getStructureValue() != null) {
                            updateState = 1; // deleted
                        }
                    } else {
                        if (existingProperty.getStructureValue() == null) {
                            updateState = 1; // created
                        } else {
                            if (!property.getStructureValue().equals(existingProperty.getStructureValue())) {
                                updateState = 1; // updated
                            }
                        }
                    }
                }
            }
        }
        return updateState;
    }

    /**
     * Returns all groups that are virtualizing the given role in the given ou.<p>
     * 
     * @param dbc the database context
     * @param role the role
     * 
     * @return all groups that are virtualizing the given role (or a child of it)
     * 
     * @throws CmsException if something goes wrong 
     */
    private List<CmsGroup> getVirtualGroupsForRole(CmsDbContext dbc, CmsRole role) throws CmsException {

        Set<Integer> roleFlags = new HashSet<Integer>();
        // add role flag
        Integer flags = new Integer(role.getVirtualGroupFlags());
        roleFlags.add(flags);
        // collect all child role flags
        Iterator<CmsRole> itChildRoles = role.getChildren(true).iterator();
        while (itChildRoles.hasNext()) {
            CmsRole child = itChildRoles.next();
            flags = new Integer(child.getVirtualGroupFlags());
            roleFlags.add(flags);
        }
        // iterate all groups matching the flags
        List<CmsGroup> groups = new ArrayList<CmsGroup>();
        Iterator<CmsGroup> it = getGroups(dbc, readOrganizationalUnit(dbc, role.getOuFqn()), false, false)
                .iterator();
        while (it.hasNext()) {
            CmsGroup group = it.next();
            if (group.isVirtual()) {
                CmsRole r = CmsRole.valueOf(group);
                if (roleFlags.contains(new Integer(r.getVirtualGroupFlags()))) {
                    groups.add(group);
                }
            }
        }
        return groups;
    }

    /**
     * Returns a list of users in a group.<p>
     *
     * @param dbc the current database context
     * @param ouFqn the organizational unit to get the users from 
     * @param groupname the name of the group to list users from
     * @param includeOtherOuUsers include users of other organizational units
     * @param directUsersOnly if set only the direct assigned users will be returned, 
     *                        if not also indirect users, ie. members of parent roles, 
     *                        this parameter only works with roles
     * @param readRoles if to read roles or groups
     * 
     * @return all <code>{@link CmsUser}</code> objects in the group
     * 
     * @throws CmsException if operation was not successful
     */
    private List<CmsUser> internalUsersOfGroup(CmsDbContext dbc, String ouFqn, String groupname,
            boolean includeOtherOuUsers, boolean directUsersOnly, boolean readRoles) throws CmsException {

        CmsGroup group = readGroup(dbc, groupname); // check that the group really exists
        if ((group == null) || (!((!readRoles && !group.isRole()) || (readRoles && group.isRole())))) {
            throw new CmsDbEntryNotFoundException(
                    Messages.get().container(Messages.ERR_UNKNOWN_GROUP_1, groupname));
        }

        String prefix = "_" + includeOtherOuUsers + "_" + directUsersOnly + "_" + ouFqn;
        String cacheKey = m_keyGenerator.getCacheKeyForGroupUsers(prefix, dbc, group);
        List<CmsUser> allUsers = m_monitor.getCachedUserList(cacheKey);
        if (allUsers == null) {
            Set<CmsUser> users = new HashSet<CmsUser>(
                    getUserDriver(dbc).readUsersOfGroup(dbc, groupname, includeOtherOuUsers));
            if (readRoles && !directUsersOnly) {
                CmsRole role = CmsRole.valueOf(group);
                if (role.getParentRole() != null) {
                    try {
                        String parentGroup = role.getParentRole().getGroupName();
                        readGroup(dbc, parentGroup);
                        // iterate the parent roles
                        users.addAll(internalUsersOfGroup(dbc, ouFqn, parentGroup, includeOtherOuUsers,
                                directUsersOnly, readRoles));
                    } catch (CmsDbEntryNotFoundException e) {
                        // ignore, this may happen while deleting an orgunit
                        if (LOG.isDebugEnabled()) {
                            LOG.debug(e.getLocalizedMessage(), e);
                        }
                    }
                }
                String parentOu = CmsOrganizationalUnit.getParentFqn(group.getOuFqn());
                if (parentOu != null) {
                    // iterate the parent ou's
                    users.addAll(internalUsersOfGroup(dbc, ouFqn, parentOu + group.getSimpleName(),
                            includeOtherOuUsers, directUsersOnly, readRoles));
                }
            } else if (!readRoles && !directUsersOnly) {
                List<CmsGroup> groups = getChildren(dbc, group, false);
                for (CmsGroup parentGroup : groups) {
                    try {
                        // iterate the parent groups
                        users.addAll(internalUsersOfGroup(dbc, ouFqn, parentGroup.getName(), includeOtherOuUsers,
                                directUsersOnly, readRoles));
                    } catch (CmsDbEntryNotFoundException e) {
                        // ignore, this may happen while deleting an orgunit
                        if (LOG.isDebugEnabled()) {
                            LOG.debug(e.getLocalizedMessage(), e);
                        }
                    }
                }
            }
            // filter users from other ous
            if (!includeOtherOuUsers) {
                Iterator<CmsUser> itUsers = users.iterator();
                while (itUsers.hasNext()) {
                    CmsUser user = itUsers.next();
                    if (!user.getOuFqn().equals(ouFqn)) {
                        itUsers.remove();
                    }
                }
            }

            // make user list unmodifiable for caching
            allUsers = Collections.unmodifiableList(new ArrayList<CmsUser>(users));
            if (dbc.getProjectId().isNullUUID()) {
                m_monitor.cacheUserList(cacheKey, allUsers);
            }
        }
        return allUsers;
    }

    /**
     * Reads all resources that are inside and changed in a specified project.<p>
     * 
     * @param dbc the current database context
     * @param projectId the ID of the project
     * @param mode one of the {@link CmsReadChangedProjectResourceMode} constants
     * 
     * @return a List with all resources inside the specified project
     * 
     * @throws CmsException if something goes wrong
     */
    private List<CmsResource> readChangedResourcesInsideProject(CmsDbContext dbc, CmsUUID projectId,
            CmsReadChangedProjectResourceMode mode) throws CmsException {

        String cacheKey = projectId + "_" + mode.toString();
        List<CmsResource> result = m_monitor.getCachedProjectResources(cacheKey);
        if (result != null) {
            return result;
        }
        List<String> projectResources = readProjectResources(dbc, readProject(dbc, projectId));
        result = new ArrayList<CmsResource>();
        String currentProjectResource = null;
        List<CmsResource> resources = new ArrayList<CmsResource>();
        CmsResource currentResource = null;
        CmsLock currentLock = null;

        for (int i = 0; i < projectResources.size(); i++) {
            // read all resources that are inside the project by visiting each project resource
            currentProjectResource = projectResources.get(i);

            try {
                currentResource = readResource(dbc, currentProjectResource, CmsResourceFilter.ALL);

                if (currentResource.isFolder()) {
                    resources.addAll(readResources(dbc, currentResource, CmsResourceFilter.ALL, true));
                } else {
                    resources.add(currentResource);
                }
            } catch (CmsException e) {
                // the project resource probably doesn't exist (anymore)...
                if (!(e instanceof CmsVfsResourceNotFoundException)) {
                    throw e;
                }
            }
        }

        for (int j = 0; j < resources.size(); j++) {
            currentResource = resources.get(j);
            currentLock = getLock(dbc, currentResource).getEditionLock();

            if (!currentResource.getState().isUnchanged()) {
                if ((currentLock.isNullLock() && (currentResource.getProjectLastModified().equals(projectId)))
                        || (currentLock.isOwnedBy(dbc.currentUser())
                                && (currentLock.getProjectId().equals(projectId)))) {
                    // add only resources that are 
                    // - inside the project,
                    // - changed in the project,
                    // - either unlocked, or locked for the current user in the project
                    if ((mode == RCPRM_FILES_AND_FOLDERS_MODE)
                            || (currentResource.isFolder() && (mode == RCPRM_FOLDERS_ONLY_MODE))
                            || (currentResource.isFile() && (mode == RCPRM_FILES_ONLY_MODE))) {
                        result.add(currentResource);
                    }
                }
            }
        }

        resources.clear();
        resources = null;

        m_monitor.cacheProjectResources(cacheKey, result);
        return result;
    }

    /**
     * Sorts the given list of {@link CmsAccessControlEntry} objects.<p>
     * 
     * The the 'all others' ace in first place, the 'overwrite all' ace in second.<p>
     * 
     * @param aces the list of ACEs to sort
     * 
     * @return <code>true</code> if the list contains the 'overwrite all' ace
     */
    private boolean sortAceList(List<CmsAccessControlEntry> aces) {

        // sort the list of entries 
        Collections.sort(aces, CmsAccessControlEntry.COMPARATOR_ACE);
        // after sorting just the first 2 positions come in question
        for (int i = 0; i < Math.min(aces.size(), 2); i++) {
            CmsAccessControlEntry acEntry = aces.get(i);
            if (acEntry.getPrincipal().equals(CmsAccessControlEntry.PRINCIPAL_OVERWRITE_ALL_ID)) {
                return true;
            }
        }
        return false;
    }

    /**
     * All permissions and resources attributes of the principal
     * are transfered to a replacement principal.<p>
     *
     * @param dbc the current database context
     * @param project the current project
     * @param principalId the id of the principal to be replaced
     * @param replacementId the user to be transfered
     * @param withACEs flag to signal if the ACEs should also be transfered or just deleted
     * 
     * @throws CmsException if operation was not successful
     */
    private void transferPrincipalResources(CmsDbContext dbc, CmsProject project, CmsUUID principalId,
            CmsUUID replacementId, boolean withACEs) throws CmsException {

        // get all resources for the given user including resources associated by ACEs or attributes
        I_CmsUserDriver userDriver = getUserDriver(dbc);
        I_CmsVfsDriver vfsDriver = getVfsDriver(dbc);
        Set<CmsResource> resources = getResourcesForPrincipal(dbc, project, principalId, null, true);
        Iterator<CmsResource> it = resources.iterator();
        while (it.hasNext()) {
            CmsResource resource = it.next();
            // check resource attributes
            boolean attrModified = false;
            CmsUUID createdUser = null;
            if (resource.getUserCreated().equals(principalId)) {
                createdUser = replacementId;
                attrModified = true;
            }
            CmsUUID lastModUser = null;
            if (resource.getUserLastModified().equals(principalId)) {
                lastModUser = replacementId;
                attrModified = true;
            }
            if (attrModified) {
                vfsDriver.transferResource(dbc, project, resource, createdUser, lastModUser);
                // clear the cache
                m_monitor.clearResourceCache();
            }
            boolean aceModified = false;
            // check aces
            if (withACEs) {
                Iterator<CmsAccessControlEntry> itAces = userDriver
                        .readAccessControlEntries(dbc, project, resource.getResourceId(), false).iterator();
                while (itAces.hasNext()) {
                    CmsAccessControlEntry ace = itAces.next();
                    if (ace.getPrincipal().equals(principalId)) {
                        CmsAccessControlEntry newAce = new CmsAccessControlEntry(ace.getResource(), replacementId,
                                ace.getAllowedPermissions(), ace.getDeniedPermissions(), ace.getFlags());
                        // write the new ace
                        userDriver.writeAccessControlEntry(dbc, project, newAce);
                        aceModified = true;
                    }
                }
                if (aceModified) {
                    // clear the cache
                    m_monitor.clearAccessControlListCache();
                }
            }
            if (attrModified || aceModified) {
                // fire the event
                Map<String, Object> data = new HashMap<String, Object>(2);
                data.put(I_CmsEventListener.KEY_RESOURCE, resource);
                data.put(I_CmsEventListener.KEY_CHANGE, new Integer(
                        ((attrModified) ? CHANGED_RESOURCE : 0) | ((aceModified) ? CHANGED_ACCESSCONTROL : 0)));
                OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_MODIFIED, data));
            }
        }
    }

    /**
     * Undoes all content changes of a resource.<p>
     * 
     * @param dbc the database context
     * @param onlineProject the online project
     * @param offlineResource the offline resource, or <code>null</code> if deleted
     * @param onlineResource the online resource
     * @param newState the new resource state
     * @param moveUndone is a move operation on the same resource has been made
     * 
     * @throws CmsException if something goes wrong
     */
    private void undoContentChanges(CmsDbContext dbc, CmsProject onlineProject, CmsResource offlineResource,
            CmsResource onlineResource, CmsResourceState newState, boolean moveUndone) throws CmsException {

        String path = ((moveUndone || (offlineResource == null)) ? onlineResource.getRootPath()
                : offlineResource.getRootPath());

        // change folder or file?
        I_CmsUserDriver userDriver = getUserDriver(dbc);
        I_CmsVfsDriver vfsDriver = getVfsDriver(dbc);
        if (onlineResource.isFolder()) {
            CmsFolder restoredFolder = new CmsFolder(onlineResource.getStructureId(),
                    onlineResource.getResourceId(), path, onlineResource.getTypeId(), onlineResource.getFlags(),
                    dbc.currentProject().getUuid(), newState, onlineResource.getDateCreated(),
                    onlineResource.getUserCreated(), onlineResource.getDateLastModified(),
                    onlineResource.getUserLastModified(), onlineResource.getDateReleased(),
                    onlineResource.getDateExpired(), onlineResource.getVersion()); // version number does not matter since it will be computed later

            // write the folder in the offline project
            // this sets a flag so that the folder date is not set to the current time
            restoredFolder.setDateLastModified(onlineResource.getDateLastModified());

            // write the folder
            vfsDriver.writeResource(dbc, dbc.currentProject().getUuid(), restoredFolder, NOTHING_CHANGED);

            // restore the properties from the online project
            vfsDriver.deletePropertyObjects(dbc, dbc.currentProject().getUuid(), restoredFolder,
                    CmsProperty.DELETE_OPTION_DELETE_STRUCTURE_AND_RESOURCE_VALUES);

            List<CmsProperty> propertyInfos = vfsDriver.readPropertyObjects(dbc, onlineProject, onlineResource);
            vfsDriver.writePropertyObjects(dbc, dbc.currentProject(), restoredFolder, propertyInfos);

            // restore the access control entries from the online project
            userDriver.removeAccessControlEntries(dbc, dbc.currentProject(), onlineResource.getResourceId());
            ListIterator<CmsAccessControlEntry> aceList = userDriver
                    .readAccessControlEntries(dbc, onlineProject, onlineResource.getResourceId(), false)
                    .listIterator();

            while (aceList.hasNext()) {
                CmsAccessControlEntry ace = aceList.next();
                userDriver.createAccessControlEntry(dbc, dbc.currentProject(), onlineResource.getResourceId(),
                        ace.getPrincipal(), ace.getPermissions().getAllowedPermissions(),
                        ace.getPermissions().getDeniedPermissions(), ace.getFlags());
            }
        } else {
            byte[] onlineContent = vfsDriver.readContent(dbc, CmsProject.ONLINE_PROJECT_ID,
                    onlineResource.getResourceId());

            CmsFile restoredFile = new CmsFile(onlineResource.getStructureId(), onlineResource.getResourceId(),
                    path, onlineResource.getTypeId(), onlineResource.getFlags(), dbc.currentProject().getUuid(),
                    newState, onlineResource.getDateCreated(), onlineResource.getUserCreated(),
                    onlineResource.getDateLastModified(), onlineResource.getUserLastModified(),
                    onlineResource.getDateReleased(), onlineResource.getDateExpired(), 0,
                    onlineResource.getLength(), onlineResource.getDateContent(), onlineResource.getVersion(), // version number does not matter since it will be computed later
                    onlineContent);

            // write the file in the offline project
            // this sets a flag so that the file date is not set to the current time
            restoredFile.setDateLastModified(onlineResource.getDateLastModified());

            // collect the old properties
            List<CmsProperty> properties = vfsDriver.readPropertyObjects(dbc, onlineProject, onlineResource);

            if (offlineResource != null) {
                // bug fix 1020: delete all properties (included shared), 
                // shared properties will be recreated by the next call of #createResource(...)
                vfsDriver.deletePropertyObjects(dbc, dbc.currentProject().getUuid(), onlineResource,
                        CmsProperty.DELETE_OPTION_DELETE_STRUCTURE_AND_RESOURCE_VALUES);

                // implementation notes: 
                // undo changes can become complex e.g. if a resource was deleted, and then 
                // another resource was copied over the deleted file as a sibling
                // therefore we must "clean" delete the offline resource, and then create 
                // an new resource with the create method
                // note that this does NOT apply to folders, since a folder cannot be replaced
                // like a resource anyway
                deleteResource(dbc, offlineResource, CmsResource.DELETE_PRESERVE_SIBLINGS);
            }
            CmsResource res = createResource(dbc, restoredFile.getRootPath(), restoredFile,
                    restoredFile.getContents(), properties, false);

            // copy the access control entries from the online project
            if (offlineResource != null) {
                userDriver.removeAccessControlEntries(dbc, dbc.currentProject(), onlineResource.getResourceId());
            }
            ListIterator<CmsAccessControlEntry> aceList = userDriver
                    .readAccessControlEntries(dbc, onlineProject, onlineResource.getResourceId(), false)
                    .listIterator();

            while (aceList.hasNext()) {
                CmsAccessControlEntry ace = aceList.next();
                userDriver.createAccessControlEntry(dbc, dbc.currentProject(), res.getResourceId(),
                        ace.getPrincipal(), ace.getPermissions().getAllowedPermissions(),
                        ace.getPermissions().getDeniedPermissions(), ace.getFlags());
            }

            vfsDriver.deleteUrlNameMappingEntries(dbc, false,
                    CmsUrlNameMappingFilter.ALL.filterStructureId(res.getStructureId())
                            .filterState(CmsUrlNameMappingEntry.MAPPING_STATUS_NEW));
            // restore the state to unchanged 
            res.setState(newState);
            m_vfsDriver.writeResourceState(dbc, dbc.currentProject(), res, UPDATE_ALL, false);
        }

        // delete all offline relations
        if (offlineResource != null) {
            vfsDriver.deleteRelations(dbc, dbc.currentProject().getUuid(), offlineResource,
                    CmsRelationFilter.TARGETS);
        }
        // get online relations
        List<CmsRelation> relations = vfsDriver.readRelations(dbc, CmsProject.ONLINE_PROJECT_ID, onlineResource,
                CmsRelationFilter.TARGETS);
        // write offline relations
        Iterator<CmsRelation> itRelations = relations.iterator();
        while (itRelations.hasNext()) {
            CmsRelation relation = itRelations.next();
            vfsDriver.createRelation(dbc, dbc.currentProject().getUuid(), relation);
        }

        // update the cache
        m_monitor.clearResourceCache();
        m_monitor.flushCache(CmsMemoryMonitor.CacheType.PROPERTY, CmsMemoryMonitor.CacheType.PROPERTY_LIST);

        if ((offlineResource == null) || offlineResource.getRootPath().equals(onlineResource.getRootPath())) {
            log(dbc, new CmsLogEntry(dbc, onlineResource.getStructureId(), CmsLogEntryType.RESOURCE_RESTORED,
                    new String[] { onlineResource.getRootPath() }), false);
        } else {
            log(dbc, new CmsLogEntry(dbc, offlineResource.getStructureId(), CmsLogEntryType.RESOURCE_MOVE_RESTORED,
                    new String[] { offlineResource.getRootPath(), onlineResource.getRootPath() }), false);
        }
        if (offlineResource != null) {
            OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_AND_PROPERTIES_MODIFIED,
                    Collections.<String, Object>singletonMap(I_CmsEventListener.KEY_RESOURCE, offlineResource)));
        } else {
            OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_AND_PROPERTIES_MODIFIED,
                    Collections.<String, Object>singletonMap(I_CmsEventListener.KEY_RESOURCE, onlineResource)));
        }
    }

    /**
     * Updates the current users context dates with the given resource.<p>
     * 
     * This checks the date information of the resource based on
     * {@link CmsResource#getDateLastModified()} as well as 
     * {@link CmsResource#getDateReleased()} and {@link CmsResource#getDateExpired()}.
     * The current users request context is updated with the the "latest" dates found.<p>
     * 
     * This is required in order to ensure proper setting of <code>"last-modified"</code> http headers
     * and also for expiration of cached elements in the Flex cache.
     * Consider the following use case: Page A is generated from resources x, y and z. 
     * If either x, y or z has an expiration / release date set, then page A must expire at a certain point 
     * in time. This is ensured by the context date check here.<p>
     * 
     * @param dbc the current database context
     * @param resource the resource to get the date information from
     */
    private void updateContextDates(CmsDbContext dbc, CmsResource resource) {

        CmsFlexRequestContextInfo info = dbc.getFlexRequestContextInfo();
        if (info != null) {
            info.updateFromResource(resource);
        }
    }

    /**
     * Updates the current users context dates with each {@link CmsResource} object in the given list.<p>
     * 
     * The given input list is returned unmodified.<p>
     * 
     * Please see {@link #updateContextDates(CmsDbContext, CmsResource)} for an explanation of what this method does.<p>
     * 
     * @param dbc the current database context
     * @param resourceList a list of {@link CmsResource} objects
     * 
     * @return the original list of CmsResources with the full resource name set 
     */
    private List<CmsResource> updateContextDates(CmsDbContext dbc, List<CmsResource> resourceList) {

        CmsFlexRequestContextInfo info = dbc.getFlexRequestContextInfo();
        if (info != null) {
            for (int i = 0; i < resourceList.size(); i++) {
                CmsResource resource = resourceList.get(i);
                info.updateFromResource(resource);
            }
        }
        return resourceList;
    }

    /**
     * Returns a List of {@link CmsResource} objects generated when applying the given filter to the given list,
     * also updates the current users context dates with each {@link CmsResource} object in the given list,
     * also applies the selected resource filter to all resources in the list and returns the remaining resources.<p>
     * 
     * Please see {@link #updateContextDates(CmsDbContext, CmsResource)} for an explanation of what this method does.<p>
     * 
     * @param dbc the current database context
     * @param resourceList a list of {@link CmsResource} objects
     * @param filter the resource filter to use
     * 
     * @return a List of {@link CmsResource} objects generated when applying the given filter to the given list
     */
    private List<CmsResource> updateContextDates(CmsDbContext dbc, List<CmsResource> resourceList,
            CmsResourceFilter filter) {

        if (CmsResourceFilter.ALL == filter) {
            // if there is no filter required, then use the simpler method that does not apply the filter
            return new ArrayList<CmsResource>(updateContextDates(dbc, resourceList));
        }

        CmsFlexRequestContextInfo info = dbc.getFlexRequestContextInfo();
        List<CmsResource> result = new ArrayList<CmsResource>(resourceList.size());
        for (int i = 0; i < resourceList.size(); i++) {
            CmsResource resource = resourceList.get(i);
            if (filter.isValid(dbc.getRequestContext(), resource)) {
                result.add(resource);
            }
            // must also include "invalid" resources for the update of context dates
            // since a resource may be invalid because of release / expiration date
            if (info != null) {
                info.updateFromResource(resource);
            }
        }
        return result;
    }

    /**
     * Updates the state of a resource, depending on the <code>resourceState</code> parameter.<p>
     * 
     * @param dbc the db context
     * @param resource the resource
     * @param resourceState if <code>true</code> the resource state will be updated, if not just the structure state.
     * 
     * @throws CmsDataAccessException if something goes wrong 
     */
    private void updateState(CmsDbContext dbc, CmsResource resource, boolean resourceState)
            throws CmsDataAccessException {

        CmsUUID projectId = ((dbc.getProjectId() == null) || dbc.getProjectId().isNullUUID())
                ? dbc.currentProject().getUuid()
                : dbc.getProjectId();
        resource.setUserLastModified(dbc.currentUser().getId());
        if (resourceState) {
            // update the whole resource state
            getVfsDriver(dbc).writeResource(dbc, projectId, resource, UPDATE_RESOURCE_STATE);
        } else {
            // update the structure state
            getVfsDriver(dbc).writeResource(dbc, projectId, resource, UPDATE_STRUCTURE_STATE);
        }
    }

}