001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (c) Alkacon Software GmbH (http://www.alkacon.com)
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 *
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015 * Lesser General Public License for more details.
016 *
017 * For further information about Alkacon Software GmbH, please see the
018 * company website: http://www.alkacon.com
019 *
020 * For further information about OpenCms, please see the
021 * project website: http://www.opencms.org
022 *
023 * You should have received a copy of the GNU Lesser General Public
024 * License along with this library; if not, write to the Free Software
025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
026 */
027
028package org.opencms.db;
029
030import org.opencms.configuration.CmsConfigurationManager;
031import org.opencms.configuration.CmsParameterConfiguration;
032import org.opencms.configuration.CmsSystemConfiguration;
033import org.opencms.db.generic.CmsUserDriver;
034import org.opencms.db.log.CmsLogEntry;
035import org.opencms.db.log.CmsLogEntryType;
036import org.opencms.db.log.CmsLogFilter;
037import org.opencms.db.urlname.CmsUrlNameMappingEntry;
038import org.opencms.db.urlname.CmsUrlNameMappingFilter;
039import org.opencms.file.CmsDataAccessException;
040import org.opencms.file.CmsFile;
041import org.opencms.file.CmsFolder;
042import org.opencms.file.CmsGroup;
043import org.opencms.file.CmsObject;
044import org.opencms.file.CmsProject;
045import org.opencms.file.CmsProperty;
046import org.opencms.file.CmsPropertyDefinition;
047import org.opencms.file.CmsRequestContext;
048import org.opencms.file.CmsResource;
049import org.opencms.file.CmsResourceFilter;
050import org.opencms.file.CmsUser;
051import org.opencms.file.CmsUserSearchParameters;
052import org.opencms.file.CmsVfsException;
053import org.opencms.file.CmsVfsResourceAlreadyExistsException;
054import org.opencms.file.CmsVfsResourceNotFoundException;
055import org.opencms.file.I_CmsResource;
056import org.opencms.file.history.CmsHistoryFile;
057import org.opencms.file.history.CmsHistoryFolder;
058import org.opencms.file.history.CmsHistoryPrincipal;
059import org.opencms.file.history.CmsHistoryProject;
060import org.opencms.file.history.I_CmsHistoryResource;
061import org.opencms.file.types.CmsResourceTypeFolder;
062import org.opencms.file.types.CmsResourceTypeJsp;
063import org.opencms.file.types.I_CmsResourceType;
064import org.opencms.flex.CmsFlexRequestContextInfo;
065import org.opencms.gwt.shared.alias.CmsAliasImportResult;
066import org.opencms.gwt.shared.alias.CmsAliasImportStatus;
067import org.opencms.gwt.shared.alias.CmsAliasMode;
068import org.opencms.i18n.CmsLocaleManager;
069import org.opencms.i18n.CmsMessageContainer;
070import org.opencms.jsp.CmsJspNavBuilder;
071import org.opencms.lock.CmsLock;
072import org.opencms.lock.CmsLockException;
073import org.opencms.lock.CmsLockFilter;
074import org.opencms.lock.CmsLockManager;
075import org.opencms.lock.CmsLockType;
076import org.opencms.main.CmsEvent;
077import org.opencms.main.CmsException;
078import org.opencms.main.CmsIllegalArgumentException;
079import org.opencms.main.CmsIllegalStateException;
080import org.opencms.main.CmsInitException;
081import org.opencms.main.CmsLog;
082import org.opencms.main.CmsMultiException;
083import org.opencms.main.I_CmsEventListener;
084import org.opencms.main.OpenCms;
085import org.opencms.module.CmsModule;
086import org.opencms.monitor.CmsMemoryMonitor;
087import org.opencms.publish.CmsPublishEngine;
088import org.opencms.publish.CmsPublishJobInfoBean;
089import org.opencms.publish.CmsPublishReport;
090import org.opencms.relations.CmsCategoryService;
091import org.opencms.relations.CmsLink;
092import org.opencms.relations.CmsRelation;
093import org.opencms.relations.CmsRelationFilter;
094import org.opencms.relations.CmsRelationSystemValidator;
095import org.opencms.relations.CmsRelationType;
096import org.opencms.relations.I_CmsLinkParseable;
097import org.opencms.report.CmsLogReport;
098import org.opencms.report.I_CmsReport;
099import org.opencms.security.CmsAccessControlEntry;
100import org.opencms.security.CmsAccessControlList;
101import org.opencms.security.CmsAuthentificationException;
102import org.opencms.security.CmsOrganizationalUnit;
103import org.opencms.security.CmsPasswordEncryptionException;
104import org.opencms.security.CmsPermissionSet;
105import org.opencms.security.CmsPermissionSetCustom;
106import org.opencms.security.CmsPrincipal;
107import org.opencms.security.CmsRole;
108import org.opencms.security.CmsSecurityException;
109import org.opencms.security.I_CmsPermissionHandler;
110import org.opencms.security.I_CmsPrincipal;
111import org.opencms.site.CmsSiteMatcher;
112import org.opencms.util.CmsFileUtil;
113import org.opencms.util.CmsStringUtil;
114import org.opencms.util.CmsUUID;
115import org.opencms.util.PrintfFormat;
116import org.opencms.workplace.threads.A_CmsProgressThread;
117
118import java.util.ArrayList;
119import java.util.Collection;
120import java.util.Collections;
121import java.util.Comparator;
122import java.util.Date;
123import java.util.HashMap;
124import java.util.HashSet;
125import java.util.Iterator;
126import java.util.List;
127import java.util.ListIterator;
128import java.util.Locale;
129import java.util.Map;
130import java.util.Set;
131import java.util.TreeSet;
132import java.util.regex.Pattern;
133import java.util.regex.PatternSyntaxException;
134
135import org.apache.commons.dbcp.PoolingDriver;
136import org.apache.commons.logging.Log;
137import org.apache.commons.pool.ObjectPool;
138
139import com.google.common.collect.ArrayListMultimap;
140
141/**
142 * The OpenCms driver manager.<p>
143 *
144 * @since 6.0.0
145 */
146public final class CmsDriverManager implements I_CmsEventListener {
147
148    /**
149     * The comparator used for comparing url name mapping entries by date.<p>
150     */
151    class UrlNameMappingComparator implements Comparator<CmsUrlNameMappingEntry> {
152
153        /**
154         * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
155         */
156        public int compare(CmsUrlNameMappingEntry o1, CmsUrlNameMappingEntry o2) {
157
158            long date1 = o1.getDateChanged();
159            long date2 = o2.getDateChanged();
160            if (date1 < date2) {
161                return -1;
162            }
163            if (date1 > date2) {
164                return +1;
165            }
166            return 0;
167        }
168    }
169
170    /**
171     * Enumeration class for the mode parameter in the
172     * {@link CmsDriverManager#readChangedResourcesInsideProject(CmsDbContext, CmsUUID, CmsReadChangedProjectResourceMode)}
173     * method.<p>
174     */
175    private static class CmsReadChangedProjectResourceMode {
176
177        /**
178         * Default constructor.<p>
179         */
180        protected CmsReadChangedProjectResourceMode() {
181
182            // noop
183        }
184    }
185
186    /** Attribute for signaling to the user driver that a specific OU should be initialized by fillDefaults. */
187    public static final String ATTR_INIT_OU = "INIT_OU";
188
189    /** Attribute login. */
190    public static final String ATTRIBUTE_LOGIN = "A_LOGIN";
191
192    /** Cache key for all properties. */
193    public static final String CACHE_ALL_PROPERTIES = "_CAP_";
194
195    /**
196     * Values indicating changes of a resource,
197     * ordered according to the scope of the change.
198     */
199    /** Value to indicate a change in access control entries of a resource. */
200    public static final int CHANGED_ACCESSCONTROL = 1;
201
202    /** Value to indicate a content change. */
203    public static final int CHANGED_CONTENT = 16;
204
205    /** Value to indicate a change in the lastmodified settings of a resource. */
206    public static final int CHANGED_LASTMODIFIED = 4;
207
208    /** Value to indicate a project change. */
209    public static final int CHANGED_PROJECT = 32;
210
211    /** Value to indicate a change in the resource data. */
212    public static final int CHANGED_RESOURCE = 8;
213
214    /** Value to indicate a change in the availability timeframe. */
215    public static final int CHANGED_TIMEFRAME = 2;
216
217    /** "cache" string in the configuration-file. */
218    public static final String CONFIGURATION_CACHE = "cache";
219
220    /** "db" string in the configuration-file. */
221    public static final String CONFIGURATION_DB = "db";
222
223    /** "driver.history" string in the configuration-file. */
224    public static final String CONFIGURATION_HISTORY = "driver.history";
225
226    /** "driver.project" string in the configuration-file. */
227    public static final String CONFIGURATION_PROJECT = "driver.project";
228
229    /** "subscription.vfs" string in the configuration file. */
230    public static final String CONFIGURATION_SUBSCRIPTION = "driver.subscription";
231
232    /** "driver.user" string in the configuration-file. */
233    public static final String CONFIGURATION_USER = "driver.user";
234
235    /** "driver.vfs" string in the configuration-file. */
236    public static final String CONFIGURATION_VFS = "driver.vfs";
237
238    /** DBC attribute key needed to fix publishing behavior involving siblings. */
239    public static final String KEY_CHANGED_AND_DELETED = "changedAndDeleted";
240
241    /** The vfs path of the loast and found folder. */
242    public static final String LOST_AND_FOUND_FOLDER = "/system/lost-found";
243
244    /** The maximum length of a VFS resource path. */
245    public static final int MAX_VFS_RESOURCE_PATH_LENGTH = 512;
246
247    /** Key for indicating no changes. */
248    public static final int NOTHING_CHANGED = 0;
249
250    /** Name of the configuration parameter to enable/disable logging to the CMS_LOG table. */
251    public static final String PARAM_LOG_TABLE_ENABLED = "log.table.enabled";
252
253    /** Indicates to ignore the resource path when matching resources. */
254    public static final String READ_IGNORE_PARENT = null;
255
256    /** Indicates to ignore the time value. */
257    public static final long READ_IGNORE_TIME = 0L;
258
259    /** Indicates to ignore the resource type when matching resources. */
260    public static final int READ_IGNORE_TYPE = -1;
261
262    /** Indicates to match resources NOT having the given state. */
263    public static final int READMODE_EXCLUDE_STATE = 8;
264
265    /** Indicates to match immediate children only. */
266    public static final int READMODE_EXCLUDE_TREE = 1;
267
268    /** Indicates to match resources NOT having the given type. */
269    public static final int READMODE_EXCLUDE_TYPE = 4;
270
271    /** Mode for reading project resources from the db. */
272    public static final int READMODE_IGNORESTATE = 0;
273
274    /** Indicates to match resources in given project only. */
275    public static final int READMODE_INCLUDE_PROJECT = 2;
276
277    /** Indicates to match all successors. */
278    public static final int READMODE_INCLUDE_TREE = 0;
279
280    /** Mode for reading project resources from the db. */
281    public static final int READMODE_MATCHSTATE = 1;
282
283    /** Indicates if only file resources should be read. */
284    public static final int READMODE_ONLY_FILES = 128;
285
286    /** Indicates if only folder resources should be read. */
287    public static final int READMODE_ONLY_FOLDERS = 64;
288
289    /** Mode for reading project resources from the db. */
290    public static final int READMODE_UNMATCHSTATE = 2;
291
292    /** Prefix char for temporary files in the VFS. */
293    public static final String TEMP_FILE_PREFIX = "~";
294
295    /** Key to indicate complete update. */
296    public static final int UPDATE_ALL = 3;
297
298    /** Key to indicate update of resource record. */
299    public static final int UPDATE_RESOURCE = 4;
300
301    /** Key to indicate update of last modified project reference. */
302    public static final int UPDATE_RESOURCE_PROJECT = 6;
303
304    /** Key to indicate update of resource state. */
305    public static final int UPDATE_RESOURCE_STATE = 1;
306
307    /** Key to indicate update of resource state including the content date. */
308    public static final int UPDATE_RESOURCE_STATE_CONTENT = 7;
309
310    /** Key to indicate update of structure record. */
311    public static final int UPDATE_STRUCTURE = 5;
312
313    /** Key to indicate update of structure state. */
314    public static final int UPDATE_STRUCTURE_STATE = 2;
315
316    /** The log object for this class. */
317    private static final Log LOG = CmsLog.getLog(CmsDriverManager.class);
318
319    /** Constant mode parameter to read all files and folders in the {@link #readChangedResourcesInsideProject(CmsDbContext, CmsUUID, CmsReadChangedProjectResourceMode)}} method. */
320    private static final CmsReadChangedProjectResourceMode RCPRM_FILES_AND_FOLDERS_MODE = new CmsReadChangedProjectResourceMode();
321
322    /** Constant mode parameter to read all files and folders in the {@link #readChangedResourcesInsideProject(CmsDbContext, CmsUUID, CmsReadChangedProjectResourceMode)}} method. */
323    private static final CmsReadChangedProjectResourceMode RCPRM_FILES_ONLY_MODE = new CmsReadChangedProjectResourceMode();
324
325    /** Constant mode parameter to read all files and folders in the {@link #readChangedResourcesInsideProject(CmsDbContext, CmsUUID, CmsReadChangedProjectResourceMode)}} method. */
326    private static final CmsReadChangedProjectResourceMode RCPRM_FOLDERS_ONLY_MODE = new CmsReadChangedProjectResourceMode();
327
328    /** The list of initialized JDBC pools. */
329    private List<PoolingDriver> m_connectionPools;
330
331    /** The history driver. */
332    private I_CmsHistoryDriver m_historyDriver;
333
334    /** The HTML link validator. */
335    private CmsRelationSystemValidator m_htmlLinkValidator;
336
337    /** The class used for cache key generation. */
338    private I_CmsCacheKey m_keyGenerator;
339
340    /** The lock manager. */
341    private CmsLockManager m_lockManager;
342
343    /** The log entry cache. */
344    private List<CmsLogEntry> m_log = new ArrayList<CmsLogEntry>();
345
346    /** Local reference to the memory monitor to avoid multiple lookups through the OpenCms singleton. */
347    private CmsMemoryMonitor m_monitor;
348
349    /** The project driver. */
350    private I_CmsProjectDriver m_projectDriver;
351
352    /** The the configuration read from the <code>opencms.properties</code> file. */
353    private CmsParameterConfiguration m_propertyConfiguration;
354
355    /** the publish engine. */
356    private CmsPublishEngine m_publishEngine;
357
358    /** Object used for synchronizing updates to the user publish list. */
359    private Object m_publishListUpdateLock = new Object();
360
361    /** The security manager (for access checks). */
362    private CmsSecurityManager m_securityManager;
363
364    /** The sql manager. */
365    private CmsSqlManager m_sqlManager;
366
367    /** The subscription driver. */
368    private I_CmsSubscriptionDriver m_subscriptionDriver;
369
370    /** The user driver. */
371    private I_CmsUserDriver m_userDriver;
372
373    /** The VFS driver. */
374    private I_CmsVfsDriver m_vfsDriver;
375
376    /**
377     * Private constructor, initializes some required member variables.<p>
378     */
379    private CmsDriverManager() {
380
381        // intentionally left blank
382    }
383
384    /**
385     * Reads the required configurations from the opencms.properties file and creates
386     * the various drivers to access the cms resources.<p>
387     *
388     * The initialization process of the driver manager and its drivers is split into
389     * the following phases:
390     * <ul>
391     * <li>the database pool configuration is read</li>
392     * <li>a plain and empty driver manager instance is created</li>
393     * <li>an instance of each driver is created</li>
394     * <li>the driver manager is passed to each driver during initialization</li>
395     * <li>finally, the driver instances are passed to the driver manager during initialization</li>
396     * </ul>
397     *
398     * @param configurationManager the configuration manager
399     * @param securityManager the security manager
400     * @param runtimeInfoFactory the initialized OpenCms runtime info factory
401     * @param publishEngine the publish engine
402     *
403     * @return CmsDriverManager the instantiated driver manager
404     * @throws CmsInitException if the driver manager couldn't be instantiated
405     */
406    public static CmsDriverManager newInstance(
407        CmsConfigurationManager configurationManager,
408        CmsSecurityManager securityManager,
409        I_CmsDbContextFactory runtimeInfoFactory,
410        CmsPublishEngine publishEngine) throws CmsInitException {
411
412        // read the opencms.properties from the configuration
413        CmsParameterConfiguration config = configurationManager.getConfiguration();
414
415        CmsDriverManager driverManager = null;
416        try {
417            // create a driver manager instance
418            driverManager = new CmsDriverManager();
419            if (CmsLog.INIT.isInfoEnabled()) {
420                CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_DRIVER_MANAGER_START_PHASE1_0));
421            }
422            if (runtimeInfoFactory == null) {
423                throw new CmsInitException(
424                    org.opencms.main.Messages.get().container(org.opencms.main.Messages.ERR_CRITICAL_NO_DB_CONTEXT_0));
425            }
426        } catch (Exception exc) {
427            CmsMessageContainer message = Messages.get().container(Messages.LOG_ERR_DRIVER_MANAGER_START_0);
428            if (LOG.isFatalEnabled()) {
429                LOG.fatal(message.key(), exc);
430            }
431            throw new CmsInitException(message, exc);
432        }
433
434        // store the configuration
435        driverManager.m_propertyConfiguration = config;
436
437        // set the security manager
438        driverManager.m_securityManager = securityManager;
439
440        // set connection pools
441        driverManager.m_connectionPools = new ArrayList<PoolingDriver>();
442
443        // set the lock manager
444        driverManager.m_lockManager = new CmsLockManager(driverManager);
445
446        // create and set the sql manager
447        driverManager.m_sqlManager = new CmsSqlManager(driverManager);
448
449        // set the publish engine
450        driverManager.m_publishEngine = publishEngine;
451
452        if (CmsLog.INIT.isInfoEnabled()) {
453            CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_DRIVER_MANAGER_START_PHASE2_0));
454        }
455
456        // read the pool names to initialize
457        List<String> driverPoolNames = config.getList(CmsDriverManager.CONFIGURATION_DB + ".pools");
458        if (CmsLog.INIT.isInfoEnabled()) {
459            String names = "";
460            for (String name : driverPoolNames) {
461                names += name + " ";
462            }
463            CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_DRIVER_MANAGER_START_POOLS_1, names));
464        }
465
466        // initialize each pool
467        for (String name : driverPoolNames) {
468            driverManager.newPoolInstance(config, name);
469        }
470
471        // initialize the runtime info factory with the generated driver manager
472        runtimeInfoFactory.initialize(driverManager);
473
474        if (CmsLog.INIT.isInfoEnabled()) {
475            CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_DRIVER_MANAGER_START_PHASE3_0));
476        }
477
478        // store the access objects
479        CmsDbContext dbc = runtimeInfoFactory.getDbContext();
480        driverManager.m_vfsDriver = (I_CmsVfsDriver)driverManager.createDriver(
481            dbc,
482            configurationManager,
483            config,
484            CONFIGURATION_VFS,
485            ".vfs.driver");
486        dbc.clear();
487
488        dbc = runtimeInfoFactory.getDbContext();
489        driverManager.m_userDriver = (I_CmsUserDriver)driverManager.createDriver(
490            dbc,
491            configurationManager,
492            config,
493            CONFIGURATION_USER,
494            ".user.driver");
495        dbc.clear();
496
497        dbc = runtimeInfoFactory.getDbContext();
498        driverManager.m_projectDriver = (I_CmsProjectDriver)driverManager.createDriver(
499            dbc,
500            configurationManager,
501            config,
502            CONFIGURATION_PROJECT,
503            ".project.driver");
504        dbc.clear();
505
506        dbc = runtimeInfoFactory.getDbContext();
507        driverManager.m_historyDriver = (I_CmsHistoryDriver)driverManager.createDriver(
508            dbc,
509            configurationManager,
510            config,
511            CONFIGURATION_HISTORY,
512            ".history.driver");
513        dbc.clear();
514
515        dbc = runtimeInfoFactory.getDbContext();
516        try {
517            // we wrap this in a try-catch because otherwise it would fail during the update
518            // process, since the subscription driver configuration does not exist at that point.
519            driverManager.m_subscriptionDriver = (I_CmsSubscriptionDriver)driverManager.createDriver(
520                dbc,
521                configurationManager,
522                config,
523                CONFIGURATION_SUBSCRIPTION,
524                ".subscription.driver");
525        } catch (IndexOutOfBoundsException npe) {
526            LOG.warn("Could not instantiate subscription driver!");
527            LOG.warn(npe.getLocalizedMessage(), npe);
528        }
529        dbc.clear();
530
531        // register the driver manager for required events
532        org.opencms.main.OpenCms.addCmsEventListener(
533            driverManager,
534            new int[] {
535                I_CmsEventListener.EVENT_UPDATE_EXPORTS,
536                I_CmsEventListener.EVENT_CLEAR_CACHES,
537                I_CmsEventListener.EVENT_CLEAR_PRINCIPAL_CACHES,
538                I_CmsEventListener.EVENT_USER_MODIFIED,
539                I_CmsEventListener.EVENT_PUBLISH_PROJECT});
540
541        // return the configured driver manager
542        return driverManager;
543    }
544
545    /**
546     * Adds an alias entry.<p>
547     *
548     * @param dbc the database context
549     * @param project the current project
550     * @param alias the alias to add
551     *
552     * @throws CmsException if something goes wrong
553     */
554    public void addAlias(CmsDbContext dbc, CmsProject project, CmsAlias alias) throws CmsException {
555
556        I_CmsVfsDriver vfsDriver = getVfsDriver(dbc);
557        vfsDriver.insertAlias(dbc, project, alias);
558    }
559
560    /**
561     * Adds a new relation to the given resource.<p>
562     *
563     * @param dbc the database context
564     * @param resource the resource to add the relation to
565     * @param target the target of the relation
566     * @param type the type of the relation
567     * @param importCase if importing relations
568     *
569     * @throws CmsException if something goes wrong
570     */
571    public void addRelationToResource(
572        CmsDbContext dbc,
573        CmsResource resource,
574        CmsResource target,
575        CmsRelationType type,
576        boolean importCase) throws CmsException {
577
578        if (type.isDefinedInContent()) {
579            throw new CmsIllegalArgumentException(
580                Messages.get().container(
581                    Messages.ERR_ADD_RELATION_IN_CONTENT_3,
582                    dbc.removeSiteRoot(resource.getRootPath()),
583                    dbc.removeSiteRoot(target.getRootPath()),
584                    type.getLocalizedName(dbc.getRequestContext().getLocale())));
585        }
586        CmsRelation relation = new CmsRelation(resource, target, type);
587        getVfsDriver(dbc).createRelation(dbc, dbc.currentProject().getUuid(), relation);
588        if (!importCase) {
589            // log it
590            log(
591                dbc,
592                new CmsLogEntry(
593                    dbc,
594                    resource.getStructureId(),
595                    CmsLogEntryType.RESOURCE_ADD_RELATION,
596                    new String[] {relation.getSourcePath(), relation.getTargetPath()}),
597                false);
598            // touch the resource
599            setDateLastModified(dbc, resource, System.currentTimeMillis());
600        }
601    }
602
603    /**
604     * Adds a resource to the given organizational unit.<p>
605     *
606     * @param dbc the current db context
607     * @param orgUnit the organizational unit to add the resource to
608     * @param resource the resource that is to be added to the organizational unit
609     *
610     * @throws CmsException if something goes wrong
611     *
612     * @see org.opencms.security.CmsOrgUnitManager#addResourceToOrgUnit(CmsObject, String, String)
613     * @see org.opencms.security.CmsOrgUnitManager#addResourceToOrgUnit(CmsObject, String, String)
614     */
615    public void addResourceToOrgUnit(CmsDbContext dbc, CmsOrganizationalUnit orgUnit, CmsResource resource)
616    throws CmsException {
617
618        m_monitor.flushCache(CmsMemoryMonitor.CacheType.HAS_ROLE, CmsMemoryMonitor.CacheType.ROLE_LIST);
619        getUserDriver(dbc).addResourceToOrganizationalUnit(dbc, orgUnit, resource);
620    }
621
622    /**
623     * Adds a user to a group.<p>
624     *
625     * @param dbc the current database context
626     * @param username the name of the user that is to be added to the group
627     * @param groupname the name of the group
628     * @param readRoles if reading roles or groups
629     *
630     * @throws CmsException if operation was not successful
631     * @throws CmsDbEntryNotFoundException if the given user or the given group was not found
632     *
633     * @see #removeUserFromGroup(CmsDbContext, String, String, boolean)
634     */
635    public void addUserToGroup(CmsDbContext dbc, String username, String groupname, boolean readRoles)
636    throws CmsException, CmsDbEntryNotFoundException {
637
638        //check if group exists
639        CmsGroup group = readGroup(dbc, groupname);
640        if (group == null) {
641            // the group does not exists
642            throw new CmsDbEntryNotFoundException(Messages.get().container(Messages.ERR_UNKNOWN_GROUP_1, groupname));
643        }
644        if (group.isVirtual() && !readRoles) {
645            String roleName = CmsRole.valueOf(group).getGroupName();
646            if (!userInGroup(dbc, username, roleName, true)) {
647                addUserToGroup(dbc, username, roleName, true);
648                return;
649            }
650        }
651        if (group.isVirtual()) {
652            // this is an hack to prevent unlimited recursive calls
653            readRoles = false;
654        }
655        if ((readRoles && !group.isRole()) || (!readRoles && group.isRole())) {
656            // we want a role but we got a group, or the other way
657            throw new CmsDbEntryNotFoundException(Messages.get().container(Messages.ERR_UNKNOWN_GROUP_1, groupname));
658        }
659        if (userInGroup(dbc, username, groupname, readRoles)) {
660            // the user is already member of the group
661            return;
662        }
663        //check if the user exists
664        CmsUser user = readUser(dbc, username);
665        if (user == null) {
666            // the user does not exists
667            throw new CmsDbEntryNotFoundException(Messages.get().container(Messages.ERR_UNKNOWN_USER_1, username));
668        }
669
670        // if adding an user to a role
671        if (readRoles) {
672            CmsRole role = CmsRole.valueOf(group);
673            // a role can only be set if the user has the given role
674            m_securityManager.checkRole(dbc, role);
675            // now we check if we already have the role
676            if (m_securityManager.hasRole(dbc, user, role)) {
677                // do nothing
678                return;
679            }
680            // and now we need to remove all possible child-roles
681            List<CmsRole> children = role.getChildren(true);
682            Iterator<CmsGroup> itUserGroups = getGroupsOfUser(
683                dbc,
684                username,
685                group.getOuFqn(),
686                true,
687                true,
688                true,
689                dbc.getRequestContext().getRemoteAddress()).iterator();
690            while (itUserGroups.hasNext()) {
691                CmsGroup roleGroup = itUserGroups.next();
692                if (children.contains(CmsRole.valueOf(roleGroup))) {
693                    // remove only child roles
694                    removeUserFromGroup(dbc, username, roleGroup.getName(), true);
695                }
696            }
697            // update virtual groups
698            Iterator<CmsGroup> it = getVirtualGroupsForRole(dbc, role).iterator();
699            while (it.hasNext()) {
700                CmsGroup virtualGroup = it.next();
701                // here we say readroles = true, to prevent an unlimited recursive calls
702                addUserToGroup(dbc, username, virtualGroup.getName(), true);
703            }
704        }
705
706        //add this user to the group
707        getUserDriver(dbc).createUserInGroup(dbc, user.getId(), group.getId());
708
709        // flush the cache
710        if (readRoles) {
711            m_monitor.flushCache(CmsMemoryMonitor.CacheType.HAS_ROLE, CmsMemoryMonitor.CacheType.ROLE_LIST);
712        }
713        m_monitor.flushCache(CmsMemoryMonitor.CacheType.USERGROUPS, CmsMemoryMonitor.CacheType.USER_LIST);
714
715        if (!dbc.getProjectId().isNullUUID() && !CmsProject.ONLINE_PROJECT_ID.equals(dbc.getProjectId())) {
716            // user modified event is not needed
717            return;
718        }
719        // fire user modified event
720        Map<String, Object> eventData = new HashMap<String, Object>();
721        eventData.put(I_CmsEventListener.KEY_USER_ID, user.getId().toString());
722        eventData.put(I_CmsEventListener.KEY_USER_NAME, user.getName());
723        eventData.put(I_CmsEventListener.KEY_USER_ID, user.getId().toString());
724        eventData.put(I_CmsEventListener.KEY_GROUP_NAME, group.getName());
725        eventData.put(
726            I_CmsEventListener.KEY_USER_ACTION,
727            I_CmsEventListener.VALUE_USER_MODIFIED_ACTION_ADD_USER_TO_GROUP);
728        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_USER_MODIFIED, eventData));
729    }
730
731    /**
732     * Changes the lock of a resource to the current user,
733     * that is "steals" the lock from another user.<p>
734     *
735     * @param dbc the current database context
736     * @param resource the resource to change the lock for
737     * @param lockType the new lock type to set
738     *
739     * @throws CmsException if something goes wrong
740     * @throws CmsSecurityException if something goes wrong
741     *
742     *
743     * @see CmsObject#changeLock(String)
744     * @see I_CmsResourceType#changeLock(CmsObject, CmsSecurityManager, CmsResource)
745     *
746     * @see CmsSecurityManager#hasPermissions(CmsRequestContext, CmsResource, CmsPermissionSet, boolean, CmsResourceFilter)
747     */
748    public void changeLock(CmsDbContext dbc, CmsResource resource, CmsLockType lockType)
749    throws CmsException, CmsSecurityException {
750
751        // get the current lock
752        CmsLock currentLock = getLock(dbc, resource);
753        // check if the resource is locked at all
754        if (currentLock.getEditionLock().isUnlocked() && currentLock.getSystemLock().isUnlocked()) {
755            throw new CmsLockException(
756                Messages.get().container(
757                    Messages.ERR_CHANGE_LOCK_UNLOCKED_RESOURCE_1,
758                    dbc.getRequestContext().getSitePath(resource)));
759        } else if ((lockType == CmsLockType.EXCLUSIVE)
760            && currentLock.isExclusiveOwnedInProjectBy(dbc.currentUser(), dbc.currentProject())) {
761            // the current lock requires no change
762            return;
763        }
764
765        // duplicate logic from CmsSecurityManager#hasPermissions() because lock state can't be ignored
766        // if another user has locked the file, the current user can never get WRITE permissions with the default check
767        int denied = 0;
768
769        // check if the current user is vfs manager
770        boolean canIgnorePermissions = m_securityManager.hasRoleForResource(
771            dbc,
772            dbc.currentUser(),
773            CmsRole.VFS_MANAGER,
774            resource);
775        // if the resource type is jsp
776        // write is only allowed for developers
777        if (!canIgnorePermissions && (CmsResourceTypeJsp.isJsp(resource))) {
778            if (!m_securityManager.hasRoleForResource(dbc, dbc.currentUser(), CmsRole.DEVELOPER, resource)) {
779                denied |= CmsPermissionSet.PERMISSION_WRITE;
780            }
781        }
782        CmsPermissionSetCustom permissions;
783        if (canIgnorePermissions) {
784            // if the current user is administrator, anything is allowed
785            permissions = new CmsPermissionSetCustom(~0);
786        } else {
787            // otherwise, get the permissions from the access control list
788            permissions = getPermissions(dbc, resource, dbc.currentUser());
789        }
790        // revoke the denied permissions
791        permissions.denyPermissions(denied);
792        // now check if write permission is granted
793        if ((CmsPermissionSet.ACCESS_WRITE.getPermissions()
794            & permissions.getPermissions()) != CmsPermissionSet.ACCESS_WRITE.getPermissions()) {
795            // check failed, throw exception
796            m_securityManager.checkPermissions(
797                dbc.getRequestContext(),
798                resource,
799                CmsPermissionSet.ACCESS_WRITE,
800                I_CmsPermissionHandler.PERM_DENIED);
801        }
802        // if we got here write permission is granted on the target
803
804        // remove the old lock
805        m_lockManager.removeResource(dbc, resource, true, lockType.isSystem());
806        // apply the new lock
807        lockResource(dbc, resource, lockType);
808    }
809
810    /**
811     * Returns a list with all sub resources of a given folder that have set the given property,
812     * matching the current property's value with the given old value and replacing it by a given new value.<p>
813     *
814     * @param dbc the current database context
815     * @param resource the resource on which property definition values are changed
816     * @param propertyDefinition the name of the propertydefinition to change the value
817     * @param oldValue the old value of the propertydefinition
818     * @param newValue the new value of the propertydefinition
819     * @param recursive if true, change the property value on the resource and recursively all property values on
820     *                     sub-resources (only for folders)
821     * @return a list with the <code>{@link CmsResource}</code>'s where the property value has been changed
822     *
823     * @throws CmsVfsException for now only when the search for the oldvalue failed.
824     * @throws CmsException if operation was not successful
825     */
826    public List<CmsResource> changeResourcesInFolderWithProperty(
827        CmsDbContext dbc,
828        CmsResource resource,
829        String propertyDefinition,
830        String oldValue,
831        String newValue,
832        boolean recursive) throws CmsVfsException, CmsException {
833
834        CmsResourceFilter filter = CmsResourceFilter.IGNORE_EXPIRATION;
835        // collect the resources to look up
836        List<CmsResource> resources = new ArrayList<CmsResource>();
837        if (recursive) {
838            // read the files in the folder
839            resources = readResourcesWithProperty(dbc, resource, propertyDefinition, null, filter);
840            // add the folder itself
841            resources.add(resource);
842        } else {
843            resources.add(resource);
844        }
845
846        Pattern oldPattern;
847        try {
848            // remove the place holder if available
849            String tmpOldValue = oldValue;
850            if (tmpOldValue.contains(CmsStringUtil.PLACEHOLDER_START)
851                && tmpOldValue.contains(CmsStringUtil.PLACEHOLDER_END)) {
852                tmpOldValue = tmpOldValue.replace(CmsStringUtil.PLACEHOLDER_START, "");
853                tmpOldValue = tmpOldValue.replace(CmsStringUtil.PLACEHOLDER_END, "");
854            }
855            // compile regular expression pattern
856            oldPattern = Pattern.compile(tmpOldValue);
857        } catch (PatternSyntaxException e) {
858            throw new CmsVfsException(
859                Messages.get().container(
860                    Messages.ERR_CHANGE_RESOURCES_IN_FOLDER_WITH_PROP_4,
861                    new Object[] {propertyDefinition, oldValue, newValue, resource.getRootPath()}),
862                e);
863        }
864
865        List<CmsResource> changedResources = new ArrayList<CmsResource>(resources.size());
866        // create permission set and filter to check each resource
867        CmsPermissionSet perm = CmsPermissionSet.ACCESS_WRITE;
868        for (int i = 0; i < resources.size(); i++) {
869            // loop through found resources and check property values
870            CmsResource res = resources.get(i);
871            // check resource state and permissions
872            try {
873                m_securityManager.checkPermissions(dbc, res, perm, true, filter);
874            } catch (Exception e) {
875                // resource is deleted or not writable for current user
876                continue;
877            }
878            CmsProperty property = readPropertyObject(dbc, res, propertyDefinition, false);
879            String propertyValue = property.getValue();
880            boolean changed = false;
881            if ((propertyValue != null) && oldPattern.matcher(propertyValue).matches()) {
882                // apply the place holder content
883                String tmpNewValue = CmsStringUtil.transformValues(oldValue, newValue, propertyValue);
884                // change structure value
885                property.setStructureValue(tmpNewValue);
886                changed = true;
887            }
888            if (changed) {
889                // write property object if something has changed
890                writePropertyObject(dbc, res, property);
891                changedResources.add(res);
892            }
893        }
894        return changedResources;
895    }
896
897    /**
898     * Changes the resource flags of a resource.<p>
899     *
900     * The resource flags are used to indicate various "special" conditions
901     * for a resource. Most notably, the "internal only" setting which signals
902     * that a resource can not be directly requested with it's URL.<p>
903     *
904     * @param dbc the current database context
905     * @param resource the resource to change the flags for
906     * @param flags the new resource flags for this resource
907     *
908     * @throws CmsException if something goes wrong
909     *
910     * @see CmsObject#chflags(String, int)
911     * @see I_CmsResourceType#chflags(CmsObject, CmsSecurityManager, CmsResource, int)
912     */
913    public void chflags(CmsDbContext dbc, CmsResource resource, int flags) throws CmsException {
914
915        // must operate on a clone to ensure resource is not modified in case permissions are not granted
916        CmsResource clone = (CmsResource)resource.clone();
917        clone.setFlags(flags);
918        // log it
919        log(
920            dbc,
921            new CmsLogEntry(
922                dbc,
923                resource.getStructureId(),
924                CmsLogEntryType.RESOURCE_FLAGS,
925                new String[] {resource.getRootPath()}),
926            false);
927        // write it
928        writeResource(dbc, clone);
929    }
930
931    /**
932     * Changes the resource type of a resource.<p>
933     *
934     * OpenCms handles resources according to the resource type,
935     * not the file suffix. This is e.g. why a JSP in OpenCms can have the
936     * suffix ".html" instead of ".jsp" only. Changing the resource type
937     * makes sense e.g. if you want to make a plain text file a JSP resource,
938     * or a binary file an image, etc.<p>
939     *
940     * @param dbc the current database context
941     * @param resource the resource to change the type for
942     * @param type the new resource type for this resource
943     *
944     * @throws CmsException if something goes wrong
945     *
946     * @see CmsObject#chtype(String, int)
947     * @see I_CmsResourceType#chtype(CmsObject, CmsSecurityManager, CmsResource, int)
948     */
949    public void chtype(CmsDbContext dbc, CmsResource resource, int type) throws CmsException {
950
951        // must operate on a clone to ensure resource is not modified in case permissions are not granted
952        CmsResource clone = (CmsResource)resource.clone();
953        I_CmsResourceType newType = OpenCms.getResourceManager().getResourceType(type);
954        clone.setType(newType.getTypeId());
955        // log it
956        log(
957            dbc,
958            new CmsLogEntry(
959                dbc,
960                resource.getStructureId(),
961                CmsLogEntryType.RESOURCE_TYPE,
962                new String[] {resource.getRootPath()}),
963            false);
964        // write it
965        writeResource(dbc, clone);
966    }
967
968    /**
969     * @see org.opencms.main.I_CmsEventListener#cmsEvent(org.opencms.main.CmsEvent)
970     */
971    public void cmsEvent(CmsEvent event) {
972
973        if (LOG.isDebugEnabled()) {
974            LOG.debug(Messages.get().getBundle().key(Messages.LOG_CMS_EVENT_1, new Integer(event.getType())));
975        }
976
977        I_CmsReport report;
978        CmsDbContext dbc;
979
980        switch (event.getType()) {
981
982            case I_CmsEventListener.EVENT_UPDATE_EXPORTS:
983                dbc = (CmsDbContext)event.getData().get(I_CmsEventListener.KEY_DBCONTEXT);
984                updateExportPoints(dbc);
985                break;
986
987            case I_CmsEventListener.EVENT_PUBLISH_PROJECT:
988                CmsUUID publishHistoryId = new CmsUUID((String)event.getData().get(I_CmsEventListener.KEY_PUBLISHID));
989                report = (I_CmsReport)event.getData().get(I_CmsEventListener.KEY_REPORT);
990                dbc = (CmsDbContext)event.getData().get(I_CmsEventListener.KEY_DBCONTEXT);
991                m_monitor.clearCache();
992                writeExportPoints(dbc, report, publishHistoryId);
993                break;
994
995            case I_CmsEventListener.EVENT_CLEAR_CACHES:
996                m_monitor.clearCache();
997                break;
998            case I_CmsEventListener.EVENT_CLEAR_PRINCIPAL_CACHES:
999            case I_CmsEventListener.EVENT_USER_MODIFIED:
1000                m_monitor.clearPrincipalsCache();
1001                break;
1002            default:
1003                // noop
1004        }
1005    }
1006
1007    /**
1008     * Copies the access control entries of a given resource to a destination resource.<p>
1009     *
1010     * Already existing access control entries of the destination resource are removed.<p>
1011     *
1012     * @param dbc the current database context
1013     * @param source the resource to copy the access control entries from
1014     * @param destination the resource to which the access control entries are copied
1015     * @param updateLastModifiedInfo if true, user and date "last modified" information on the target resource will be updated
1016     *
1017     * @throws CmsException if something goes wrong
1018     */
1019    public void copyAccessControlEntries(
1020        CmsDbContext dbc,
1021        CmsResource source,
1022        CmsResource destination,
1023        boolean updateLastModifiedInfo) throws CmsException {
1024
1025        // get the entries to copy
1026        ListIterator<CmsAccessControlEntry> aceList = getUserDriver(dbc).readAccessControlEntries(
1027            dbc,
1028            dbc.currentProject(),
1029            source.getResourceId(),
1030            false).listIterator();
1031
1032        // remove the current entries from the destination
1033        getUserDriver(dbc).removeAccessControlEntries(dbc, dbc.currentProject(), destination.getResourceId());
1034
1035        // now write the new entries
1036        while (aceList.hasNext()) {
1037            CmsAccessControlEntry ace = aceList.next();
1038            getUserDriver(dbc).createAccessControlEntry(
1039                dbc,
1040                dbc.currentProject(),
1041                destination.getResourceId(),
1042                ace.getPrincipal(),
1043                ace.getPermissions().getAllowedPermissions(),
1044                ace.getPermissions().getDeniedPermissions(),
1045                ace.getFlags());
1046        }
1047
1048        // log it
1049        log(
1050            dbc,
1051            new CmsLogEntry(
1052                dbc,
1053                destination.getStructureId(),
1054                CmsLogEntryType.RESOURCE_PERMISSIONS,
1055                new String[] {destination.getRootPath()}),
1056            false);
1057
1058        // update the "last modified" information
1059        if (updateLastModifiedInfo) {
1060            setDateLastModified(dbc, destination, destination.getDateLastModified());
1061        }
1062
1063        // clear the cache
1064        m_monitor.clearAccessControlListCache();
1065
1066        // fire a resource modification event
1067        Map<String, Object> data = new HashMap<String, Object>(2);
1068        data.put(I_CmsEventListener.KEY_RESOURCE, destination);
1069        data.put(I_CmsEventListener.KEY_CHANGE, new Integer(CHANGED_ACCESSCONTROL));
1070        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_MODIFIED, data));
1071    }
1072
1073    /**
1074     * Copies a resource.<p>
1075     *
1076     * You must ensure that the destination path is an absolute, valid and
1077     * existing VFS path. Relative paths from the source are currently not supported.<p>
1078     *
1079     * In case the target resource already exists, it is overwritten with the
1080     * source resource.<p>
1081     *
1082     * The <code>siblingMode</code> parameter controls how to handle siblings
1083     * during the copy operation.
1084     * Possible values for this parameter are:
1085     * <ul>
1086     * <li><code>{@link org.opencms.file.CmsResource#COPY_AS_NEW}</code></li>
1087     * <li><code>{@link org.opencms.file.CmsResource#COPY_AS_SIBLING}</code></li>
1088     * <li><code>{@link org.opencms.file.CmsResource#COPY_PRESERVE_SIBLING}</code></li>
1089     * </ul><p>
1090     *
1091     * @param dbc the current database context
1092     * @param source the resource to copy
1093     * @param destination the name of the copy destination with complete path
1094     * @param siblingMode indicates how to handle siblings during copy
1095     *
1096     * @throws CmsException if something goes wrong
1097     * @throws CmsIllegalArgumentException if the <code>source</code> argument is <code>null</code>
1098     *
1099     * @see CmsObject#copyResource(String, String, CmsResource.CmsResourceCopyMode)
1100     * @see I_CmsResourceType#copyResource(CmsObject, CmsSecurityManager, CmsResource, String, CmsResource.CmsResourceCopyMode)
1101     */
1102    public void copyResource(
1103        CmsDbContext dbc,
1104        CmsResource source,
1105        String destination,
1106        CmsResource.CmsResourceCopyMode siblingMode) throws CmsException, CmsIllegalArgumentException {
1107
1108        // check the sibling mode to see if this resource has to be copied as a sibling
1109        boolean copyAsSibling = false;
1110
1111        // siblings of folders are not supported
1112        if (!source.isFolder()) {
1113            // if the "copy as sibling" mode is used, set the flag to true
1114            if (siblingMode == CmsResource.COPY_AS_SIBLING) {
1115                copyAsSibling = true;
1116            }
1117            // if the mode is "preserve siblings", we have to check the sibling counter
1118            if (siblingMode == CmsResource.COPY_PRESERVE_SIBLING) {
1119                if (source.getSiblingCount() > 1) {
1120                    copyAsSibling = true;
1121                }
1122            }
1123        }
1124
1125        // read the source properties
1126        List<CmsProperty> properties = readPropertyObjects(dbc, source, false);
1127
1128        if (copyAsSibling) {
1129            // create a sibling of the source file at the destination
1130            createSibling(dbc, source, destination, properties);
1131            // after the sibling is created the copy operation is finished
1132            return;
1133        }
1134
1135        // prepare the content if required
1136        byte[] content = null;
1137        if (source.isFile()) {
1138            if (source instanceof CmsFile) {
1139                // resource already is a file
1140                content = ((CmsFile)source).getContents();
1141            }
1142            if ((content == null) || (content.length < 1)) {
1143                // no known content yet - read from database
1144                content = getVfsDriver(dbc).readContent(dbc, dbc.currentProject().getUuid(), source.getResourceId());
1145            }
1146        }
1147
1148        // determine destination folder
1149        String destinationFoldername = CmsResource.getParentFolder(destination);
1150
1151        // read the destination folder (will also check read permissions)
1152        CmsFolder destinationFolder = m_securityManager.readFolder(
1153            dbc,
1154            destinationFoldername,
1155            CmsResourceFilter.IGNORE_EXPIRATION);
1156
1157        // no further permission check required here, will be done in createResource()
1158
1159        // set user and creation time stamps
1160        long currentTime = System.currentTimeMillis();
1161        long dateLastModified;
1162        CmsUUID userLastModified;
1163        if (source.isFolder()) {
1164            // folders always get a new date and user when they are copied
1165            dateLastModified = currentTime;
1166            userLastModified = dbc.currentUser().getId();
1167        } else {
1168            // files keep the date and user last modified from the source
1169            dateLastModified = source.getDateLastModified();
1170            userLastModified = source.getUserLastModified();
1171        }
1172
1173        // check the resource flags
1174        int flags = source.getFlags();
1175        if (source.isLabeled()) {
1176            // reset "labeled" link flag for new resource
1177            flags &= ~CmsResource.FLAG_LABELED;
1178        }
1179
1180        // create the new resource
1181        CmsResource newResource = new CmsResource(
1182            new CmsUUID(),
1183            new CmsUUID(),
1184            destination,
1185            source.getTypeId(),
1186            source.isFolder(),
1187            flags,
1188            dbc.currentProject().getUuid(),
1189            CmsResource.STATE_NEW,
1190            currentTime,
1191            dbc.currentUser().getId(),
1192            dateLastModified,
1193            userLastModified,
1194            source.getDateReleased(),
1195            source.getDateExpired(),
1196            1,
1197            source.getLength(),
1198            source.getDateContent(),
1199            source.getVersion()); // version number does not matter since it will be computed later
1200
1201        // trigger "is touched" state on resource (will ensure modification date is kept unchanged)
1202        newResource.setDateLastModified(dateLastModified);
1203
1204        // log it
1205        log(
1206            dbc,
1207            new CmsLogEntry(
1208                dbc,
1209                newResource.getStructureId(),
1210                CmsLogEntryType.RESOURCE_COPIED,
1211                new String[] {newResource.getRootPath()}),
1212            false);
1213
1214        // create the resource
1215        newResource = createResource(dbc, destination, newResource, content, properties, false);
1216        // copy relations
1217        copyRelations(dbc, source, newResource);
1218
1219        // copy the access control entries to the created resource
1220        copyAccessControlEntries(dbc, source, newResource, false);
1221
1222        // clear the cache
1223        m_monitor.clearAccessControlListCache();
1224
1225        List<CmsResource> modifiedResources = new ArrayList<CmsResource>();
1226        modifiedResources.add(source);
1227        modifiedResources.add(newResource);
1228        modifiedResources.add(destinationFolder);
1229        OpenCms.fireCmsEvent(
1230            new CmsEvent(
1231                I_CmsEventListener.EVENT_RESOURCE_COPIED,
1232                Collections.<String, Object> singletonMap(I_CmsEventListener.KEY_RESOURCES, modifiedResources)));
1233    }
1234
1235    /**
1236     * Copies a resource to the current project of the user.<p>
1237     *
1238     * @param dbc the current database context
1239     * @param resource the resource to apply this operation to
1240     *
1241     * @throws CmsException if something goes wrong
1242     *
1243     * @see CmsObject#copyResourceToProject(String)
1244     * @see I_CmsResourceType#copyResourceToProject(CmsObject, CmsSecurityManager, CmsResource)
1245     */
1246    public void copyResourceToProject(CmsDbContext dbc, CmsResource resource) throws CmsException {
1247
1248        // copy the resource to the project only if the resource is not already in the project
1249        if (!isInsideCurrentProject(dbc, resource.getRootPath())) {
1250            // check if there are already any subfolders of this resource
1251            I_CmsProjectDriver projectDriver = getProjectDriver(dbc);
1252            if (resource.isFolder()) {
1253                List<String> projectResources = projectDriver.readProjectResources(dbc, dbc.currentProject());
1254                for (int i = 0; i < projectResources.size(); i++) {
1255                    String resname = projectResources.get(i);
1256                    if (resname.startsWith(resource.getRootPath())) {
1257                        // delete the existing project resource first
1258                        projectDriver.deleteProjectResource(dbc, dbc.currentProject().getUuid(), resname);
1259                    }
1260                }
1261            }
1262            try {
1263                projectDriver.createProjectResource(dbc, dbc.currentProject().getUuid(), resource.getRootPath());
1264            } catch (CmsException exc) {
1265                // if the subfolder exists already - all is ok
1266            } finally {
1267                m_monitor.flushCache(CmsMemoryMonitor.CacheType.PROJECT_RESOURCES);
1268
1269                OpenCms.fireCmsEvent(
1270                    new CmsEvent(
1271                        I_CmsEventListener.EVENT_PROJECT_MODIFIED,
1272                        Collections.<String, Object> singletonMap("project", dbc.currentProject())));
1273            }
1274        }
1275    }
1276
1277    /**
1278     * Counts the locked resources in this project.<p>
1279     *
1280     * @param project the project to count the locked resources in
1281     *
1282     * @return the amount of locked resources in this project
1283     */
1284    public int countLockedResources(CmsProject project) {
1285
1286        // count locks
1287        return m_lockManager.countExclusiveLocksInProject(project);
1288    }
1289
1290    /**
1291     * Add a new group to the Cms.<p>
1292     *
1293     * Only the admin can do this.
1294     * Only users, which are in the group "administrators" are granted.<p>
1295     *
1296     * @param dbc the current database context
1297     * @param id the id of the new group
1298     * @param name the name of the new group
1299     * @param description the description for the new group
1300     * @param flags the flags for the new group
1301     * @param parent the name of the parent group (or <code>null</code>)
1302     *
1303     * @return new created group
1304     *
1305     * @throws CmsException if the creation of the group failed
1306     * @throws CmsIllegalArgumentException if the length of the given name was below 1
1307     */
1308    public CmsGroup createGroup(CmsDbContext dbc, CmsUUID id, String name, String description, int flags, String parent)
1309    throws CmsIllegalArgumentException, CmsException {
1310
1311        // check the group name
1312        OpenCms.getValidationHandler().checkGroupName(CmsOrganizationalUnit.getSimpleName(name));
1313        // trim the name
1314        name = name.trim();
1315
1316        // check the OU
1317        readOrganizationalUnit(dbc, CmsOrganizationalUnit.getParentFqn(name));
1318
1319        // get the id of the parent group if necessary
1320        if (CmsStringUtil.isNotEmpty(parent)) {
1321            CmsGroup parentGroup = readGroup(dbc, parent);
1322            if (!parentGroup.isRole()
1323                && !CmsOrganizationalUnit.getParentFqn(parent).equals(CmsOrganizationalUnit.getParentFqn(name))) {
1324                throw new CmsDataAccessException(
1325                    Messages.get().container(
1326                        Messages.ERR_PARENT_GROUP_MUST_BE_IN_SAME_OU_3,
1327                        CmsOrganizationalUnit.getSimpleName(name),
1328                        CmsOrganizationalUnit.getParentFqn(name),
1329                        parent));
1330            }
1331        }
1332
1333        // create the group
1334        CmsGroup group = getUserDriver(dbc).createGroup(dbc, id, name, description, flags, parent);
1335
1336        // if the group is in fact a role, initialize it
1337        if (group.isVirtual()) {
1338            // get all users that have the given role
1339            String groupname = CmsRole.valueOf(group).getGroupName();
1340            Iterator<CmsUser> it = getUsersOfGroup(dbc, groupname, true, false, true).iterator();
1341            while (it.hasNext()) {
1342                CmsUser user = it.next();
1343                // put them in the new group
1344                addUserToGroup(dbc, user.getName(), group.getName(), true);
1345            }
1346        }
1347
1348        // put it into the cache
1349        m_monitor.cacheGroup(group);
1350
1351        if (!dbc.getProjectId().isNullUUID()) {
1352            // group modified event is not needed
1353            return group;
1354        }
1355        // fire group modified event
1356        Map<String, Object> eventData = new HashMap<String, Object>();
1357        eventData.put(I_CmsEventListener.KEY_GROUP_NAME, group.getName());
1358        eventData.put(I_CmsEventListener.KEY_GROUP_ID, group.getId().toString());
1359        eventData.put(I_CmsEventListener.KEY_USER_ACTION, I_CmsEventListener.VALUE_GROUP_MODIFIED_ACTION_CREATE);
1360        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_GROUP_MODIFIED, eventData));
1361
1362        // return it
1363        return group;
1364    }
1365
1366    /**
1367     * Creates a new organizational unit.<p>
1368     *
1369     * @param dbc the current db context
1370     * @param ouFqn the fully qualified name of the new organizational unit
1371     * @param description the description of the new organizational unit
1372     * @param flags the flags for the new organizational unit
1373     * @param resource the first associated resource
1374     *
1375     * @return a <code>{@link CmsOrganizationalUnit}</code> object representing
1376     *          the newly created organizational unit
1377     *
1378     * @throws CmsException if operation was not successful
1379     *
1380     * @see org.opencms.security.CmsOrgUnitManager#createOrganizationalUnit(CmsObject, String, String, int, String)
1381     */
1382    public CmsOrganizationalUnit createOrganizationalUnit(
1383        CmsDbContext dbc,
1384        String ouFqn,
1385        String description,
1386        int flags,
1387        CmsResource resource) throws CmsException {
1388
1389        // normal case
1390        CmsOrganizationalUnit parent = readOrganizationalUnit(dbc, CmsOrganizationalUnit.getParentFqn(ouFqn));
1391        String name = CmsOrganizationalUnit.getSimpleName(ouFqn);
1392        if (name.endsWith(CmsOrganizationalUnit.SEPARATOR)) {
1393            name = name.substring(0, name.length() - 1);
1394        }
1395
1396        // check the name
1397        CmsResource.checkResourceName(name);
1398
1399        // trim the name
1400        name = name.trim();
1401
1402        // check the description
1403        if (CmsStringUtil.isEmptyOrWhitespaceOnly(description)) {
1404            throw new CmsIllegalArgumentException(Messages.get().container(Messages.ERR_BAD_OU_DESCRIPTION_EMPTY_0));
1405        }
1406
1407        // create the organizational unit
1408        CmsOrganizationalUnit orgUnit = getUserDriver(dbc).createOrganizationalUnit(
1409            dbc,
1410            name,
1411            description,
1412            flags,
1413            parent,
1414            resource != null ? resource.getRootPath() : null);
1415        // put the new created org unit into the cache
1416        m_monitor.cacheOrgUnit(orgUnit);
1417
1418        // flush relevant caches
1419        m_monitor.clearPrincipalsCache();
1420        m_monitor.flushCache(CmsMemoryMonitor.CacheType.PROPERTY, CmsMemoryMonitor.CacheType.PROPERTY_LIST);
1421
1422        // create a publish list for the 'virtual' publish event
1423        CmsResource ouRes = readResource(
1424            dbc,
1425            CmsUserDriver.ORGUNIT_BASE_FOLDER + orgUnit.getName(),
1426            CmsResourceFilter.DEFAULT);
1427        CmsPublishList pl = new CmsPublishList(ouRes, false);
1428        pl.add(ouRes, false);
1429
1430        getProjectDriver(dbc).writePublishHistory(
1431            dbc,
1432            pl.getPublishHistoryId(),
1433            new CmsPublishedResource(ouRes, -1, CmsResourceState.STATE_NEW));
1434
1435        // fire the 'virtual' publish event
1436        Map<String, Object> eventData = new HashMap<String, Object>();
1437        eventData.put(I_CmsEventListener.KEY_PUBLISHID, pl.getPublishHistoryId().toString());
1438        eventData.put(I_CmsEventListener.KEY_PROJECTID, dbc.currentProject().getUuid());
1439        eventData.put(I_CmsEventListener.KEY_DBCONTEXT, dbc);
1440        CmsEvent afterPublishEvent = new CmsEvent(I_CmsEventListener.EVENT_PUBLISH_PROJECT, eventData);
1441        OpenCms.fireCmsEvent(afterPublishEvent);
1442
1443        if (!dbc.getProjectId().isNullUUID()) {
1444            // OU modified event is not needed
1445            return orgUnit;
1446        }
1447
1448        // fire OU modified event
1449        Map<String, Object> event2Data = new HashMap<String, Object>();
1450        event2Data.put(I_CmsEventListener.KEY_OU_NAME, orgUnit.getName());
1451        event2Data.put(I_CmsEventListener.KEY_OU_ID, orgUnit.getId().toString());
1452        event2Data.put(I_CmsEventListener.KEY_USER_ACTION, I_CmsEventListener.VALUE_OU_MODIFIED_ACTION_CREATE);
1453        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_OU_MODIFIED, event2Data));
1454
1455        // return it
1456        return orgUnit;
1457    }
1458
1459    /**
1460     * Creates a project.<p>
1461     *
1462     * @param dbc the current database context
1463     * @param name the name of the project to create
1464     * @param description the description of the project
1465     * @param groupname the project user group to be set
1466     * @param managergroupname the project manager group to be set
1467     * @param projecttype the type of the project
1468     *
1469     * @return the created project
1470     *
1471     * @throws CmsIllegalArgumentException if the chosen <code>name</code> is already used
1472     *         by the online project, or if the name is not valid
1473     * @throws CmsException if something goes wrong
1474     */
1475    public CmsProject createProject(
1476        CmsDbContext dbc,
1477        String name,
1478        String description,
1479        String groupname,
1480        String managergroupname,
1481        CmsProject.CmsProjectType projecttype) throws CmsIllegalArgumentException, CmsException {
1482
1483        if (CmsProject.ONLINE_PROJECT_NAME.equals(name)) {
1484            throw new CmsIllegalArgumentException(
1485                Messages.get().container(
1486                    Messages.ERR_CREATE_PROJECT_ONLINE_PROJECT_NAME_1,
1487                    CmsProject.ONLINE_PROJECT_NAME));
1488        }
1489        // check the name
1490        CmsProject.checkProjectName(CmsOrganizationalUnit.getSimpleName(name));
1491        // check the ou
1492        readOrganizationalUnit(dbc, CmsOrganizationalUnit.getParentFqn(name));
1493        // read the needed groups from the cms
1494        CmsGroup group = readGroup(dbc, groupname);
1495        CmsGroup managergroup = readGroup(dbc, managergroupname);
1496
1497        return getProjectDriver(dbc).createProject(
1498            dbc,
1499            new CmsUUID(),
1500            dbc.currentUser(),
1501            group,
1502            managergroup,
1503            name,
1504            description,
1505            projecttype.getDefaultFlags(),
1506            projecttype);
1507    }
1508
1509    /**
1510     * Creates a property definition.<p>
1511     *
1512     * Property definitions are valid for all resource types.<p>
1513     *
1514     * @param dbc the current database context
1515     * @param name the name of the property definition to create
1516     *
1517     * @return the created property definition
1518     *
1519     * @throws CmsException if something goes wrong
1520     */
1521    public CmsPropertyDefinition createPropertyDefinition(CmsDbContext dbc, String name) throws CmsException {
1522
1523        CmsPropertyDefinition propertyDefinition = null;
1524
1525        name = name.trim();
1526        // validate the property name
1527        CmsPropertyDefinition.checkPropertyName(name);
1528        // TODO: make the type a parameter
1529        try {
1530            try {
1531                propertyDefinition = getVfsDriver(dbc).readPropertyDefinition(
1532                    dbc,
1533                    name,
1534                    dbc.currentProject().getUuid());
1535            } catch (CmsException e) {
1536                propertyDefinition = getVfsDriver(dbc).createPropertyDefinition(
1537                    dbc,
1538                    dbc.currentProject().getUuid(),
1539                    name,
1540                    CmsPropertyDefinition.TYPE_NORMAL);
1541            }
1542
1543            try {
1544                getVfsDriver(dbc).readPropertyDefinition(dbc, name, CmsProject.ONLINE_PROJECT_ID);
1545            } catch (CmsException e) {
1546                getVfsDriver(dbc).createPropertyDefinition(
1547                    dbc,
1548                    CmsProject.ONLINE_PROJECT_ID,
1549                    name,
1550                    CmsPropertyDefinition.TYPE_NORMAL);
1551            }
1552
1553            try {
1554                getHistoryDriver(dbc).readPropertyDefinition(dbc, name);
1555            } catch (CmsException e) {
1556                getHistoryDriver(dbc).createPropertyDefinition(dbc, name, CmsPropertyDefinition.TYPE_NORMAL);
1557            }
1558        } finally {
1559
1560            // fire an event that a property of a resource has been deleted
1561            OpenCms.fireCmsEvent(
1562                new CmsEvent(
1563                    I_CmsEventListener.EVENT_PROPERTY_DEFINITION_CREATED,
1564                    Collections.<String, Object> singletonMap("propertyDefinition", propertyDefinition)));
1565
1566        }
1567
1568        return propertyDefinition;
1569    }
1570
1571    /**
1572     * Creates a new publish job.<p>
1573     *
1574     * @param dbc the current database context
1575     * @param publishJob the publish job to create
1576     *
1577     * @throws CmsException if something goes wrong
1578     */
1579    public void createPublishJob(CmsDbContext dbc, CmsPublishJobInfoBean publishJob) throws CmsException {
1580
1581        getProjectDriver(dbc).createPublishJob(dbc, publishJob);
1582    }
1583
1584    /**
1585     * Creates a new resource with the provided content and properties.<p>
1586     *
1587     * The <code>content</code> parameter may be <code>null</code> if the resource id
1588     * already exists. If so, the created resource will be a sibling of the existing
1589     * resource, the existing content will remain unchanged.<p>
1590     *
1591     * This is used during file import for import of siblings as the
1592     * <code>manifest.xml</code> only contains one binary copy per file.<p>
1593     *
1594     * If the resource id exists but the <code>content</code> is not <code>null</code>,
1595     * the created resource will be made a sibling of the existing resource,
1596     * and both will share the new content.<p>
1597     *
1598     * @param dbc the current database context
1599     * @param resourcePath the name of the resource to create (full path)
1600     * @param resource the new resource to create
1601     * @param content the content for the new resource
1602     * @param properties the properties for the new resource
1603     * @param importCase if <code>true</code>, signals that this operation is done while
1604     *                      importing resource, causing different lock behavior and
1605     *                      potential "lost and found" usage
1606     *
1607     * @return the created resource
1608     *
1609     * @throws CmsException if something goes wrong
1610     */
1611    public synchronized CmsResource createResource(
1612        CmsDbContext dbc,
1613        String resourcePath,
1614        CmsResource resource,
1615        byte[] content,
1616        List<CmsProperty> properties,
1617        boolean importCase) throws CmsException {
1618
1619        CmsResource newResource = null;
1620        if (resource.isFolder()) {
1621            resourcePath = CmsFileUtil.addTrailingSeparator(resourcePath);
1622        }
1623
1624        try {
1625            // need to provide the parent folder id for resource creation
1626            String parentFolderName = CmsResource.getParentFolder(resourcePath);
1627            CmsResource parentFolder = readFolder(dbc, parentFolderName, CmsResourceFilter.IGNORE_EXPIRATION);
1628
1629            CmsLock parentLock = getLock(dbc, parentFolder);
1630            // it is not allowed to create a resource in a folder locked by other user
1631            if (!parentLock.isUnlocked() && !parentLock.isOwnedBy(dbc.currentUser())) {
1632                // one exception is if the admin user tries to create a temporary resource
1633                if (!CmsResource.getName(resourcePath).startsWith(TEMP_FILE_PREFIX)
1634                    || !m_securityManager.hasRole(dbc, dbc.currentUser(), CmsRole.ROOT_ADMIN)) {
1635                    throw new CmsLockException(
1636                        Messages.get().container(
1637                            Messages.ERR_CREATE_RESOURCE_PARENT_LOCK_1,
1638                            dbc.removeSiteRoot(resourcePath)));
1639                }
1640            }
1641            if (CmsResourceTypeJsp.isJsp(resource)) {
1642                // security check when trying to create a new jsp file
1643                m_securityManager.checkRoleForResource(dbc, CmsRole.DEVELOPER, parentFolder);
1644            }
1645
1646            // check import configuration of "lost and found" folder
1647            boolean useLostAndFound = importCase && !OpenCms.getImportExportManager().overwriteCollidingResources();
1648
1649            // check if the resource already exists by name
1650            CmsResource currentResourceByName = null;
1651            try {
1652                currentResourceByName = readResource(dbc, resourcePath, CmsResourceFilter.ALL);
1653            } catch (CmsVfsResourceNotFoundException e) {
1654                // if the resource does exist, we have to check the id later to decide what to do
1655            }
1656
1657            // check if the resource already exists by id
1658            try {
1659                CmsResource currentResourceById = readResource(dbc, resource.getStructureId(), CmsResourceFilter.ALL);
1660                // it is not allowed to import resources when there is already a resource with the same id but different path
1661                if (!currentResourceById.getRootPath().equals(resourcePath)) {
1662                    throw new CmsVfsResourceAlreadyExistsException(
1663                        Messages.get().container(
1664                            Messages.ERR_RESOURCE_WITH_ID_ALREADY_EXISTS_3,
1665                            dbc.removeSiteRoot(resourcePath),
1666                            dbc.removeSiteRoot(currentResourceById.getRootPath()),
1667                            currentResourceById.getStructureId()));
1668                }
1669            } catch (CmsVfsResourceNotFoundException e) {
1670                // if the resource does exist, we have to check the id later to decide what to do
1671            }
1672
1673            // check the permissions
1674            if (currentResourceByName == null) {
1675                // resource does not exist - check parent folder
1676                m_securityManager.checkPermissions(
1677                    dbc,
1678                    parentFolder,
1679                    CmsPermissionSet.ACCESS_WRITE,
1680                    false,
1681                    CmsResourceFilter.IGNORE_EXPIRATION);
1682            } else {
1683                // resource already exists - check existing resource
1684                m_securityManager.checkPermissions(
1685                    dbc,
1686                    currentResourceByName,
1687                    CmsPermissionSet.ACCESS_WRITE,
1688                    !importCase,
1689                    CmsResourceFilter.ALL);
1690            }
1691
1692            // now look for the resource by name
1693            if (currentResourceByName != null) {
1694                boolean overwrite = true;
1695                if (currentResourceByName.getState().isDeleted()) {
1696                    if (!currentResourceByName.isFolder()) {
1697                        // if a non-folder resource was deleted it's treated like a new resource
1698                        overwrite = false;
1699                    }
1700                } else {
1701                    if (!importCase) {
1702                        // direct "overwrite" of a resource is possible only during import,
1703                        // or if the resource has been deleted
1704                        throw new CmsVfsResourceAlreadyExistsException(
1705                            org.opencms.db.generic.Messages.get().container(
1706                                org.opencms.db.generic.Messages.ERR_RESOURCE_WITH_NAME_ALREADY_EXISTS_1,
1707                                dbc.removeSiteRoot(resource.getRootPath())));
1708                    }
1709                    // the resource already exists
1710                    if (!resource.isFolder()
1711                        && useLostAndFound
1712                        && (!currentResourceByName.getResourceId().equals(resource.getResourceId()))) {
1713                        // semantic change: the current resource is moved to L&F and the imported resource will overwrite the old one
1714                        // will leave the resource with state deleted,
1715                        // but it does not matter, since the state will be set later again
1716                        moveToLostAndFound(dbc, currentResourceByName, false);
1717                    }
1718                }
1719                if (!overwrite) {
1720                    // lock the resource, will throw an exception if not lockable
1721                    lockResource(dbc, currentResourceByName, CmsLockType.EXCLUSIVE);
1722
1723                    // trigger createResource instead of writeResource
1724                    currentResourceByName = null;
1725                }
1726            }
1727            // if null, create new resource, if not null write resource
1728            CmsResource overwrittenResource = currentResourceByName;
1729
1730            // extract the name (without path)
1731            String targetName = CmsResource.getName(resourcePath);
1732
1733            int contentLength;
1734
1735            // modify target name and content length in case of folder creation
1736            if (resource.isFolder()) {
1737                // folders never have any content
1738                contentLength = -1;
1739                // must cut of trailing '/' for folder creation (or name check fails)
1740                if (CmsResource.isFolder(targetName)) {
1741                    targetName = targetName.substring(0, targetName.length() - 1);
1742                }
1743            } else {
1744                // otherwise ensure content and content length are set correctly
1745                if (content != null) {
1746                    // if a content is provided, in each case the length is the length of this content
1747                    contentLength = content.length;
1748                } else if (overwrittenResource != null) {
1749                    // we have no content, but an already existing resource - length remains unchanged
1750                    contentLength = overwrittenResource.getLength();
1751                } else {
1752                    // we have no content - length is used as set in the resource
1753                    contentLength = resource.getLength();
1754                }
1755            }
1756
1757            // check if the target name is valid (forbidden chars etc.),
1758            // if not throw an exception
1759            // must do this here since targetName is modified in folder case (see above)
1760            CmsResource.checkResourceName(targetName);
1761
1762            // set structure and resource ids as given
1763            CmsUUID structureId = resource.getStructureId();
1764            CmsUUID resourceId = resource.getResourceId();
1765
1766            // decide which structure id to use
1767            if (overwrittenResource != null) {
1768                // resource exists, re-use existing ids
1769                structureId = overwrittenResource.getStructureId();
1770            }
1771            if (structureId.isNullUUID()) {
1772                // need a new structure id
1773                structureId = new CmsUUID();
1774            }
1775
1776            // decide which resource id to use
1777            if (overwrittenResource != null) {
1778                // if we are overwriting we have to assure the resource id is the same
1779                resourceId = overwrittenResource.getResourceId();
1780            }
1781            if (resourceId.isNullUUID()) {
1782                // need a new resource id
1783                resourceId = new CmsUUID();
1784            }
1785
1786            try {
1787                // check online resource
1788                CmsResource onlineResource = getVfsDriver(dbc).readResource(
1789                    dbc,
1790                    CmsProject.ONLINE_PROJECT_ID,
1791                    resourcePath,
1792                    true);
1793                // only allow to overwrite with different id if importing (createResource will set the right id)
1794                try {
1795                    CmsResource offlineResource = getVfsDriver(dbc).readResource(
1796                        dbc,
1797                        dbc.currentProject().getUuid(),
1798                        onlineResource.getStructureId(),
1799                        true);
1800                    if (!offlineResource.getRootPath().equals(onlineResource.getRootPath())) {
1801                        throw new CmsVfsOnlineResourceAlreadyExistsException(
1802                            Messages.get().container(
1803                                Messages.ERR_ONLINE_RESOURCE_EXISTS_2,
1804                                dbc.removeSiteRoot(resourcePath),
1805                                dbc.removeSiteRoot(offlineResource.getRootPath())));
1806                    }
1807                } catch (CmsVfsResourceNotFoundException e) {
1808                    // there is no problem for now
1809                    // but should never happen
1810                    if (LOG.isErrorEnabled()) {
1811                        LOG.error(e.getLocalizedMessage(), e);
1812                    }
1813                }
1814            } catch (CmsVfsResourceNotFoundException e) {
1815                // ok, there is no online entry to worry about
1816            }
1817
1818            // now create a resource object with all informations
1819            newResource = new CmsResource(
1820                structureId,
1821                resourceId,
1822                resourcePath,
1823                resource.getTypeId(),
1824                resource.isFolder(),
1825                resource.getFlags(),
1826                dbc.currentProject().getUuid(),
1827                resource.getState(),
1828                resource.getDateCreated(),
1829                resource.getUserCreated(),
1830                resource.getDateLastModified(),
1831                resource.getUserLastModified(),
1832                resource.getDateReleased(),
1833                resource.getDateExpired(),
1834                1,
1835                contentLength,
1836                resource.getDateContent(),
1837                resource.getVersion()); // version number does not matter since it will be computed later
1838
1839            // ensure date is updated only if required
1840            if (resource.isTouched()) {
1841                // this will trigger the internal "is touched" state on the new resource
1842                newResource.setDateLastModified(resource.getDateLastModified());
1843            }
1844
1845            if (resource.isFile()) {
1846                // check if a sibling to the imported resource lies in a marked site
1847                if (labelResource(dbc, resource, resourcePath, 2)) {
1848                    int flags = resource.getFlags();
1849                    flags |= CmsResource.FLAG_LABELED;
1850                    resource.setFlags(flags);
1851                }
1852                // ensure siblings don't overwrite existing resource records
1853                if (content == null) {
1854                    newResource.setState(CmsResource.STATE_KEEP);
1855                }
1856            }
1857
1858            // delete all relations for the resource, before writing the content
1859            getVfsDriver(dbc).deleteRelations(
1860                dbc,
1861                dbc.currentProject().getUuid(),
1862                newResource,
1863                CmsRelationFilter.TARGETS);
1864            if (overwrittenResource == null) {
1865                CmsLock lock = getLock(dbc, newResource);
1866                if (lock.getEditionLock().isExclusive()) {
1867                    unlockResource(dbc, newResource, true, false);
1868                }
1869                // resource does not exist.
1870                newResource = getVfsDriver(dbc).createResource(
1871                    dbc,
1872                    dbc.currentProject().getUuid(),
1873                    newResource,
1874                    content);
1875            } else {
1876                // resource already exists.
1877                // probably the resource is a merged page file that gets overwritten during import, or it gets
1878                // overwritten by a copy operation. if so, the structure & resource state are not modified to changed.
1879                int updateStates = (overwrittenResource.getState().isNew()
1880                ? CmsDriverManager.NOTHING_CHANGED
1881                : CmsDriverManager.UPDATE_ALL);
1882                getVfsDriver(dbc).writeResource(dbc, dbc.currentProject().getUuid(), newResource, updateStates);
1883
1884                if ((content != null) && resource.isFile()) {
1885                    // also update file content if required
1886                    getVfsDriver(dbc).writeContent(dbc, newResource.getResourceId(), content);
1887                }
1888            }
1889
1890            // write the properties (internal operation, no events or duplicate permission checks)
1891            writePropertyObjects(dbc, newResource, properties, false);
1892
1893            // lock the created resource
1894            try {
1895                // if it is locked by another user (copied or moved resource) this lock should be preserved and
1896                // the exception is OK: locks on created resources are a slave feature to original locks
1897                lockResource(dbc, newResource, CmsLockType.EXCLUSIVE);
1898            } catch (CmsLockException cle) {
1899                if (LOG.isDebugEnabled()) {
1900                    LOG.debug(
1901                        Messages.get().getBundle().key(
1902                            Messages.ERR_CREATE_RESOURCE_LOCK_1,
1903                            new Object[] {dbc.removeSiteRoot(newResource.getRootPath())}));
1904                }
1905            }
1906
1907            if (!importCase) {
1908                log(
1909                    dbc,
1910                    new CmsLogEntry(
1911                        dbc,
1912                        newResource.getStructureId(),
1913                        CmsLogEntryType.RESOURCE_CREATED,
1914                        new String[] {resource.getRootPath()}),
1915                    false);
1916            } else {
1917                log(
1918                    dbc,
1919                    new CmsLogEntry(
1920                        dbc,
1921                        newResource.getStructureId(),
1922                        CmsLogEntryType.RESOURCE_IMPORTED,
1923                        new String[] {resource.getRootPath()}),
1924                    false);
1925            }
1926        } finally {
1927            // clear the internal caches
1928            m_monitor.clearAccessControlListCache();
1929            m_monitor.flushCache(CmsMemoryMonitor.CacheType.PROPERTY, CmsMemoryMonitor.CacheType.PROPERTY_LIST);
1930
1931            if (newResource != null) {
1932                // fire an event that a new resource has been created
1933                OpenCms.fireCmsEvent(
1934                    new CmsEvent(
1935                        I_CmsEventListener.EVENT_RESOURCE_CREATED,
1936                        Collections.<String, Object> singletonMap(I_CmsEventListener.KEY_RESOURCE, newResource)));
1937            }
1938        }
1939        return newResource;
1940    }
1941
1942    /**
1943     * Creates a new resource of the given resource type
1944     * with the provided content and properties.<p>
1945     *
1946     * If the provided content is null and the resource is not a folder,
1947     * the content will be set to an empty byte array.<p>
1948     *
1949     * @param dbc the current database context
1950     * @param resourcename the name of the resource to create (full path)
1951     * @param type the type of the resource to create
1952     * @param content the content for the new resource
1953     * @param properties the properties for the new resource
1954     *
1955     * @return the created resource
1956     *
1957     * @throws CmsException if something goes wrong
1958     * @throws CmsIllegalArgumentException if the <code>resourcename</code> argument is null or of length 0
1959     *
1960     * @see CmsObject#createResource(String, int, byte[], List)
1961     * @see CmsObject#createResource(String, int)
1962     * @see I_CmsResourceType#createResource(CmsObject, CmsSecurityManager, String, byte[], List)
1963     */
1964    public CmsResource createResource(
1965        CmsDbContext dbc,
1966        String resourcename,
1967        int type,
1968        byte[] content,
1969        List<CmsProperty> properties) throws CmsException, CmsIllegalArgumentException {
1970
1971        String targetName = resourcename;
1972
1973        if (content == null) {
1974            // name based resource creation MUST have a content
1975            content = new byte[0];
1976        }
1977        int size;
1978
1979        if (CmsFolder.isFolderType(type)) {
1980            // must cut of trailing '/' for folder creation
1981            if (CmsResource.isFolder(targetName)) {
1982                targetName = targetName.substring(0, targetName.length() - 1);
1983            }
1984            size = -1;
1985        } else {
1986            size = content.length;
1987        }
1988
1989        // create a new resource
1990        CmsResource newResource = new CmsResource(
1991            CmsUUID.getNullUUID(), // uuids will be "corrected" later
1992            CmsUUID.getNullUUID(),
1993            targetName,
1994            type,
1995            CmsFolder.isFolderType(type),
1996            0,
1997            dbc.currentProject().getUuid(),
1998            CmsResource.STATE_NEW,
1999            0,
2000            dbc.currentUser().getId(),
2001            0,
2002            dbc.currentUser().getId(),
2003            CmsResource.DATE_RELEASED_DEFAULT,
2004            CmsResource.DATE_EXPIRED_DEFAULT,
2005            1,
2006            size,
2007            0, // version number does not matter since it will be computed later
2008            0); // content time will be corrected later
2009
2010        return createResource(dbc, targetName, newResource, content, properties, false);
2011    }
2012
2013    /**
2014     * Creates a new sibling of the source resource.<p>
2015     *
2016     * @param dbc the current database context
2017     * @param source the resource to create a sibling for
2018     * @param destination the name of the sibling to create with complete path
2019     * @param properties the individual properties for the new sibling
2020     *
2021     * @return the new created sibling
2022     *
2023     * @throws CmsException if something goes wrong
2024     *
2025     * @see CmsObject#createSibling(String, String, List)
2026     * @see I_CmsResourceType#createSibling(CmsObject, CmsSecurityManager, CmsResource, String, List)
2027     */
2028    public CmsResource createSibling(
2029        CmsDbContext dbc,
2030        CmsResource source,
2031        String destination,
2032        List<CmsProperty> properties) throws CmsException {
2033
2034        if (source.isFolder()) {
2035            throw new CmsVfsException(Messages.get().container(Messages.ERR_VFS_FOLDERS_DONT_SUPPORT_SIBLINGS_0));
2036        }
2037
2038        // determine destination folder and resource name
2039        String destinationFoldername = CmsResource.getParentFolder(destination);
2040
2041        // read the destination folder (will also check read permissions)
2042        CmsFolder destinationFolder = readFolder(dbc, destinationFoldername, CmsResourceFilter.IGNORE_EXPIRATION);
2043
2044        // no further permission check required here, will be done in createResource()
2045
2046        // check the resource flags
2047        int flags = source.getFlags();
2048        if (labelResource(dbc, source, destination, 1)) {
2049            // set "labeled" link flag for new resource
2050            flags |= CmsResource.FLAG_LABELED;
2051        }
2052
2053        // create the new resource
2054        CmsResource newResource = new CmsResource(
2055            new CmsUUID(),
2056            source.getResourceId(),
2057            destination,
2058            source.getTypeId(),
2059            source.isFolder(),
2060            flags,
2061            dbc.currentProject().getUuid(),
2062            CmsResource.STATE_KEEP,
2063            source.getDateCreated(), // ensures current resource record remains untouched
2064            source.getUserCreated(),
2065            source.getDateLastModified(),
2066            source.getUserLastModified(),
2067            source.getDateReleased(),
2068            source.getDateExpired(),
2069            source.getSiblingCount() + 1,
2070            source.getLength(),
2071            source.getDateContent(),
2072            source.getVersion()); // version number does not matter since it will be computed later
2073
2074        // trigger "is touched" state on resource (will ensure modification date is kept unchanged)
2075        newResource.setDateLastModified(newResource.getDateLastModified());
2076
2077        log(
2078            dbc,
2079            new CmsLogEntry(
2080                dbc,
2081                newResource.getStructureId(),
2082                CmsLogEntryType.RESOURCE_CLONED,
2083                new String[] {newResource.getRootPath()}),
2084            false);
2085        // create the resource (null content signals creation of sibling)
2086        newResource = createResource(dbc, destination, newResource, null, properties, false);
2087
2088        // copy relations
2089        copyRelations(dbc, source, newResource);
2090
2091        // clear the caches
2092        m_monitor.clearAccessControlListCache();
2093
2094        List<CmsResource> modifiedResources = new ArrayList<CmsResource>();
2095        modifiedResources.add(source);
2096        modifiedResources.add(newResource);
2097        modifiedResources.add(destinationFolder);
2098        OpenCms.fireCmsEvent(
2099            new CmsEvent(
2100                I_CmsEventListener.EVENT_RESOURCES_AND_PROPERTIES_MODIFIED,
2101                Collections.<String, Object> singletonMap(I_CmsEventListener.KEY_RESOURCES, modifiedResources)));
2102
2103        return newResource;
2104    }
2105
2106    /**
2107     * Creates the project for the temporary workplace files.<p>
2108     *
2109     * @param dbc the current database context
2110     *
2111     * @return the created project for the temporary workplace files
2112     *
2113     * @throws CmsException if something goes wrong
2114     */
2115    public CmsProject createTempfileProject(CmsDbContext dbc) throws CmsException {
2116
2117        // read the needed groups from the cms
2118        CmsGroup projectUserGroup = readGroup(dbc, dbc.currentProject().getGroupId());
2119        CmsGroup projectManagerGroup = readGroup(dbc, dbc.currentProject().getManagerGroupId());
2120
2121        CmsProject tempProject = getProjectDriver(dbc).createProject(
2122            dbc,
2123            new CmsUUID(),
2124            dbc.currentUser(),
2125            projectUserGroup,
2126            projectManagerGroup,
2127            I_CmsProjectDriver.TEMP_FILE_PROJECT_NAME,
2128            Messages.get().getBundle(dbc.getRequestContext().getLocale()).key(
2129                Messages.GUI_WORKPLACE_TEMPFILE_PROJECT_DESC_0),
2130            CmsProject.PROJECT_FLAG_HIDDEN,
2131            CmsProject.PROJECT_TYPE_NORMAL);
2132        getProjectDriver(dbc).createProjectResource(dbc, tempProject.getUuid(), "/");
2133
2134        OpenCms.fireCmsEvent(
2135            new CmsEvent(
2136                I_CmsEventListener.EVENT_PROJECT_MODIFIED,
2137                Collections.<String, Object> singletonMap("project", tempProject)));
2138
2139        return tempProject;
2140    }
2141
2142    /**
2143     * Creates a new user.<p>
2144     *
2145     * @param dbc the current database context
2146     * @param name the name for the new user
2147     * @param password the password for the new user
2148     * @param description the description for the new user
2149     * @param additionalInfos the additional infos for the user
2150     *
2151     * @return the created user
2152     *
2153     * @see CmsObject#createUser(String, String, String, Map)
2154     *
2155     * @throws CmsException if something goes wrong
2156     * @throws CmsIllegalArgumentException if the name for the user is not valid
2157     */
2158    public CmsUser createUser(
2159        CmsDbContext dbc,
2160        String name,
2161        String password,
2162        String description,
2163        Map<String, Object> additionalInfos) throws CmsException, CmsIllegalArgumentException {
2164
2165        // no space before or after the name
2166        name = name.trim();
2167        // check the user name
2168        String userName = CmsOrganizationalUnit.getSimpleName(name);
2169        OpenCms.getValidationHandler().checkUserName(userName);
2170        if (CmsStringUtil.isEmptyOrWhitespaceOnly(userName)) {
2171            throw new CmsIllegalArgumentException(Messages.get().container(Messages.ERR_BAD_USER_1, userName));
2172        }
2173        // check the ou
2174        CmsOrganizationalUnit ou = readOrganizationalUnit(dbc, CmsOrganizationalUnit.getParentFqn(name));
2175        // check the password
2176        validatePassword(password);
2177
2178        Map<String, Object> info = new HashMap<String, Object>();
2179        if (additionalInfos != null) {
2180            info.putAll(additionalInfos);
2181        }
2182        if (description != null) {
2183            info.put(CmsUserSettings.ADDITIONAL_INFO_DESCRIPTION, description);
2184        }
2185        int flags = 0;
2186        if (ou.hasFlagWebuser()) {
2187            flags += I_CmsPrincipal.FLAG_USER_WEBUSER;
2188        }
2189        CmsUser user = getUserDriver(dbc).createUser(
2190            dbc,
2191            new CmsUUID(),
2192            name,
2193            OpenCms.getPasswordHandler().digest(password),
2194            " ",
2195            " ",
2196            " ",
2197            0,
2198            I_CmsPrincipal.FLAG_ENABLED + flags,
2199            0,
2200            info);
2201
2202        if (!dbc.getProjectId().isNullUUID()) {
2203            // user modified event is not needed
2204            return user;
2205        }
2206        // fire user modified event
2207        Map<String, Object> eventData = new HashMap<String, Object>();
2208        eventData.put(I_CmsEventListener.KEY_USER_ID, user.getId().toString());
2209        eventData.put(I_CmsEventListener.KEY_USER_ACTION, I_CmsEventListener.VALUE_USER_MODIFIED_ACTION_CREATE_USER);
2210        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_USER_MODIFIED, eventData));
2211        return user;
2212    }
2213
2214    /**
2215     * Deletes aliases indicated by a filter.<p>
2216     *
2217     * @param dbc the current database context
2218     * @param project the current project
2219     * @param filter the filter which describes which aliases to delete
2220     *
2221     * @throws CmsException if something goes wrong
2222     */
2223    public void deleteAliases(CmsDbContext dbc, CmsProject project, CmsAliasFilter filter) throws CmsException {
2224
2225        I_CmsVfsDriver vfsDriver = getVfsDriver(dbc);
2226        vfsDriver.deleteAliases(dbc, project, filter);
2227    }
2228
2229    /**
2230     * Deletes all property values of a file or folder.<p>
2231     *
2232     * If there are no other siblings than the specified resource,
2233     * both the structure and resource property values get deleted.
2234     * If the specified resource has siblings, only the structure
2235     * property values get deleted.<p>
2236     *
2237     * @param dbc the current database context
2238     * @param resourcename the name of the resource for which all properties should be deleted
2239     *
2240     * @throws CmsException if operation was not successful
2241     */
2242    public void deleteAllProperties(CmsDbContext dbc, String resourcename) throws CmsException {
2243
2244        CmsResource resource = null;
2245        List<CmsResource> resources = new ArrayList<CmsResource>();
2246
2247        try {
2248            // read the resource
2249            resource = readResource(dbc, resourcename, CmsResourceFilter.IGNORE_EXPIRATION);
2250
2251            // check the security
2252            m_securityManager.checkPermissions(
2253                dbc,
2254                resource,
2255                CmsPermissionSet.ACCESS_WRITE,
2256                false,
2257                CmsResourceFilter.ALL);
2258
2259            // delete the property values
2260            if (resource.getSiblingCount() > 1) {
2261                // the resource has siblings- delete only the (structure) properties of this sibling
2262                getVfsDriver(dbc).deletePropertyObjects(
2263                    dbc,
2264                    dbc.currentProject().getUuid(),
2265                    resource,
2266                    CmsProperty.DELETE_OPTION_DELETE_STRUCTURE_VALUES);
2267                resources.addAll(readSiblings(dbc, resource, CmsResourceFilter.ALL));
2268
2269            } else {
2270                // the resource has no other siblings- delete all (structure+resource) properties
2271                getVfsDriver(dbc).deletePropertyObjects(
2272                    dbc,
2273                    dbc.currentProject().getUuid(),
2274                    resource,
2275                    CmsProperty.DELETE_OPTION_DELETE_STRUCTURE_AND_RESOURCE_VALUES);
2276                resources.add(resource);
2277            }
2278        } finally {
2279            // clear the driver manager cache
2280            m_monitor.flushCache(CmsMemoryMonitor.CacheType.PROPERTY, CmsMemoryMonitor.CacheType.PROPERTY_LIST);
2281
2282            // fire an event that all properties of a resource have been deleted
2283            OpenCms.fireCmsEvent(
2284                new CmsEvent(
2285                    I_CmsEventListener.EVENT_RESOURCES_AND_PROPERTIES_MODIFIED,
2286                    Collections.<String, Object> singletonMap(I_CmsEventListener.KEY_RESOURCES, resources)));
2287        }
2288    }
2289
2290    /**
2291     * Deletes all entries in the published resource table.<p>
2292     *
2293     * @param dbc the current database context
2294     * @param linkType the type of resource deleted (0= non-paramter, 1=parameter)
2295     *
2296     * @throws CmsException if something goes wrong
2297     */
2298    public void deleteAllStaticExportPublishedResources(CmsDbContext dbc, int linkType) throws CmsException {
2299
2300        getProjectDriver(dbc).deleteAllStaticExportPublishedResources(dbc, linkType);
2301    }
2302
2303    /**
2304     * Deletes a group, where all permissions, users and children of the group
2305     * are transfered to a replacement group.<p>
2306     *
2307     * @param dbc the current request context
2308     * @param group the id of the group to be deleted
2309     * @param replacementId the id of the group to be transfered, can be <code>null</code>
2310     *
2311     * @throws CmsException if operation was not successful
2312     * @throws CmsDataAccessException if group to be deleted contains user
2313     */
2314    public void deleteGroup(CmsDbContext dbc, CmsGroup group, CmsUUID replacementId)
2315    throws CmsDataAccessException, CmsException {
2316
2317        CmsGroup replacementGroup = null;
2318        if (replacementId != null) {
2319            replacementGroup = readGroup(dbc, replacementId);
2320        }
2321        // get all child groups of the group
2322        List<CmsGroup> children = getChildren(dbc, group, false);
2323        // get all users in this group
2324        List<CmsUser> users = getUsersOfGroup(dbc, group.getName(), true, true, group.isRole());
2325        // get online project
2326        CmsProject onlineProject = readProject(dbc, CmsProject.ONLINE_PROJECT_ID);
2327        if (replacementGroup == null) {
2328            // remove users
2329            Iterator<CmsUser> itUsers = users.iterator();
2330            while (itUsers.hasNext()) {
2331                CmsUser user = itUsers.next();
2332                if (userInGroup(dbc, user.getName(), group.getName(), group.isRole())) {
2333                    removeUserFromGroup(dbc, user.getName(), group.getName(), group.isRole());
2334                }
2335            }
2336            // transfer children to grandfather if possible
2337            CmsUUID parentId = group.getParentId();
2338            if (parentId == null) {
2339                parentId = CmsUUID.getNullUUID();
2340            }
2341            Iterator<CmsGroup> itChildren = children.iterator();
2342            while (itChildren.hasNext()) {
2343                CmsGroup child = itChildren.next();
2344                child.setParentId(parentId);
2345                writeGroup(dbc, child);
2346            }
2347        } else {
2348            // move children
2349            Iterator<CmsGroup> itChildren = children.iterator();
2350            while (itChildren.hasNext()) {
2351                CmsGroup child = itChildren.next();
2352                child.setParentId(replacementId);
2353                writeGroup(dbc, child);
2354            }
2355            // move users
2356            Iterator<CmsUser> itUsers = users.iterator();
2357            while (itUsers.hasNext()) {
2358                CmsUser user = itUsers.next();
2359                addUserToGroup(dbc, user.getName(), replacementGroup.getName(), group.isRole());
2360                removeUserFromGroup(dbc, user.getName(), group.getName(), group.isRole());
2361            }
2362            // transfer for offline
2363            transferPrincipalResources(dbc, dbc.currentProject(), group.getId(), replacementId, true);
2364            // transfer for online
2365            transferPrincipalResources(dbc, onlineProject, group.getId(), replacementId, true);
2366        }
2367        // remove the group
2368        getUserDriver(dbc).removeAccessControlEntriesForPrincipal(
2369            dbc,
2370            dbc.currentProject(),
2371            onlineProject,
2372            group.getId());
2373        getUserDriver(dbc).deleteGroup(dbc, group.getName());
2374        // backup the group
2375        getHistoryDriver(dbc).writePrincipal(dbc, group);
2376        if (OpenCms.getSubscriptionManager().isEnabled()) {
2377            // delete all subscribed resources for group
2378            unsubscribeAllResourcesFor(dbc, OpenCms.getSubscriptionManager().getPoolName(), group);
2379        }
2380
2381        // clear the relevant caches
2382        m_monitor.uncacheGroup(group);
2383        m_monitor.flushCache(
2384            CmsMemoryMonitor.CacheType.USERGROUPS,
2385            CmsMemoryMonitor.CacheType.USER_LIST,
2386            CmsMemoryMonitor.CacheType.ACL);
2387
2388        if (!dbc.getProjectId().isNullUUID()) {
2389            // group modified event is not needed
2390            return;
2391        }
2392        // fire group modified event
2393        Map<String, Object> eventData = new HashMap<String, Object>();
2394        eventData.put(I_CmsEventListener.KEY_GROUP_ID, group.getId().toString());
2395        eventData.put(I_CmsEventListener.KEY_GROUP_NAME, group.getName());
2396        eventData.put(I_CmsEventListener.KEY_USER_ACTION, I_CmsEventListener.VALUE_GROUP_MODIFIED_ACTION_DELETE);
2397        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_GROUP_MODIFIED, eventData));
2398    }
2399
2400    /**
2401     * Deletes the versions from the history tables, keeping the given number of versions per resource.<p>
2402     *
2403     * if the <code>cleanUp</code> option is set, additionally versions of deleted resources will be removed.<p>
2404     *
2405     * @param dbc the current database context
2406     * @param versionsToKeep number of versions to keep, is ignored if negative
2407     * @param versionsDeleted number of versions to keep for deleted resources, is ignored if negative
2408     * @param timeDeleted deleted resources older than this will also be deleted, is ignored if negative
2409     * @param report the report for output logging
2410     *
2411     * @throws CmsException if operation was not successful
2412     */
2413    public void deleteHistoricalVersions(
2414        CmsDbContext dbc,
2415        int versionsToKeep,
2416        int versionsDeleted,
2417        long timeDeleted,
2418        I_CmsReport report) throws CmsException {
2419
2420        report.println(Messages.get().container(Messages.RPT_START_DELETE_VERSIONS_0), I_CmsReport.FORMAT_HEADLINE);
2421        if (versionsToKeep >= 0) {
2422            report.println(
2423                Messages.get().container(Messages.RPT_START_DELETE_ACT_VERSIONS_1, new Integer(versionsToKeep)),
2424                I_CmsReport.FORMAT_HEADLINE);
2425
2426            List<I_CmsHistoryResource> resources = getHistoryDriver(dbc).getAllNotDeletedEntries(dbc);
2427            if (resources.isEmpty()) {
2428                report.println(Messages.get().container(Messages.RPT_DELETE_NOTHING_0), I_CmsReport.FORMAT_OK);
2429            }
2430            int n = resources.size();
2431            int m = 1;
2432            Iterator<I_CmsHistoryResource> itResources = resources.iterator();
2433            while (itResources.hasNext()) {
2434                I_CmsHistoryResource histResource = itResources.next();
2435
2436                report.print(
2437                    org.opencms.report.Messages.get().container(
2438                        org.opencms.report.Messages.RPT_SUCCESSION_2,
2439                        String.valueOf(m),
2440                        String.valueOf(n)),
2441                    I_CmsReport.FORMAT_NOTE);
2442                report.print(
2443                    org.opencms.report.Messages.get().container(
2444                        org.opencms.report.Messages.RPT_ARGUMENT_1,
2445                        dbc.removeSiteRoot(histResource.getRootPath())));
2446                report.print(org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_DOTS_0));
2447
2448                try {
2449                    int deleted = getHistoryDriver(dbc).deleteEntries(dbc, histResource, versionsToKeep, -1);
2450
2451                    report.print(
2452                        Messages.get().container(Messages.RPT_VERSION_DELETING_1, new Integer(deleted)),
2453                        I_CmsReport.FORMAT_NOTE);
2454                    report.print(org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_DOTS_0));
2455                    report.println(
2456                        org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_OK_0),
2457                        I_CmsReport.FORMAT_OK);
2458                } catch (CmsDataAccessException e) {
2459                    report.println(
2460                        org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_ERROR_0),
2461                        I_CmsReport.FORMAT_ERROR);
2462
2463                    if (LOG.isDebugEnabled()) {
2464                        LOG.debug(e.getLocalizedMessage(), e);
2465                    }
2466                }
2467
2468                m++;
2469            }
2470
2471            report.println(
2472                Messages.get().container(Messages.RPT_END_DELETE_ACT_VERSIONS_0),
2473                I_CmsReport.FORMAT_HEADLINE);
2474        }
2475        if ((versionsDeleted >= 0) || (timeDeleted >= 0)) {
2476            if (timeDeleted >= 0) {
2477                report.println(
2478                    Messages.get().container(
2479                        Messages.RPT_START_DELETE_DEL_VERSIONS_2,
2480                        new Integer(versionsDeleted),
2481                        new Date(timeDeleted)),
2482                    I_CmsReport.FORMAT_HEADLINE);
2483            } else {
2484                report.println(
2485                    Messages.get().container(Messages.RPT_START_DELETE_DEL_VERSIONS_1, new Integer(versionsDeleted)),
2486                    I_CmsReport.FORMAT_HEADLINE);
2487            }
2488            List<I_CmsHistoryResource> resources = getHistoryDriver(dbc).getAllDeletedEntries(dbc);
2489            if (resources.isEmpty()) {
2490                report.println(Messages.get().container(Messages.RPT_DELETE_NOTHING_0), I_CmsReport.FORMAT_OK);
2491            }
2492            int n = resources.size();
2493            int m = 1;
2494            Iterator<I_CmsHistoryResource> itResources = resources.iterator();
2495            while (itResources.hasNext()) {
2496                I_CmsHistoryResource histResource = itResources.next();
2497
2498                report.print(
2499                    org.opencms.report.Messages.get().container(
2500                        org.opencms.report.Messages.RPT_SUCCESSION_2,
2501                        String.valueOf(m),
2502                        String.valueOf(n)),
2503                    I_CmsReport.FORMAT_NOTE);
2504                report.print(
2505                    org.opencms.report.Messages.get().container(
2506                        org.opencms.report.Messages.RPT_ARGUMENT_1,
2507                        dbc.removeSiteRoot(histResource.getRootPath())));
2508                report.print(org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_DOTS_0));
2509
2510                try {
2511                    int deleted = getHistoryDriver(dbc).deleteEntries(dbc, histResource, versionsDeleted, timeDeleted);
2512
2513                    report.print(
2514                        Messages.get().container(Messages.RPT_VERSION_DELETING_1, new Integer(deleted)),
2515                        I_CmsReport.FORMAT_NOTE);
2516                    report.print(org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_DOTS_0));
2517                    report.println(
2518                        org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_OK_0),
2519                        I_CmsReport.FORMAT_OK);
2520                } catch (CmsDataAccessException e) {
2521                    report.println(
2522                        org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_ERROR_0),
2523                        I_CmsReport.FORMAT_ERROR);
2524
2525                    if (LOG.isDebugEnabled()) {
2526                        LOG.debug(e.getLocalizedMessage(), e);
2527                    }
2528                }
2529
2530                m++;
2531            }
2532            report.println(
2533                Messages.get().container(Messages.RPT_END_DELETE_DEL_VERSIONS_0),
2534                I_CmsReport.FORMAT_HEADLINE);
2535        }
2536        report.println(Messages.get().container(Messages.RPT_END_DELETE_VERSIONS_0), I_CmsReport.FORMAT_HEADLINE);
2537    }
2538
2539    /**
2540     * Deletes all log entries matching the given filter.<p>
2541     *
2542     * @param dbc the current db context
2543     * @param filter the filter to use for deletion
2544     *
2545     * @throws CmsException if something goes wrong
2546     *
2547     * @see CmsSecurityManager#deleteLogEntries(CmsRequestContext, CmsLogFilter)
2548     */
2549    public void deleteLogEntries(CmsDbContext dbc, CmsLogFilter filter) throws CmsException {
2550
2551        updateLog(dbc);
2552        m_projectDriver.deleteLog(dbc, filter);
2553    }
2554
2555    /**
2556     * Deletes an organizational unit.<p>
2557     *
2558     * Only organizational units that contain no suborganizational unit can be deleted.<p>
2559     *
2560     * The organizational unit can not be delete if it is used in the request context,
2561     * or if the current user belongs to it.<p>
2562     *
2563     * All users and groups in the given organizational unit will be deleted.<p>
2564     *
2565     * @param dbc the current db context
2566     * @param organizationalUnit the organizational unit to delete
2567     *
2568     * @throws CmsException if operation was not successful
2569     *
2570     * @see org.opencms.security.CmsOrgUnitManager#deleteOrganizationalUnit(CmsObject, String)
2571     */
2572    public void deleteOrganizationalUnit(CmsDbContext dbc, CmsOrganizationalUnit organizationalUnit)
2573    throws CmsException {
2574
2575        // check organizational unit in context
2576        if (dbc.getRequestContext().getOuFqn().equals(organizationalUnit.getName())) {
2577            throw new CmsDbConsistencyException(
2578                Messages.get().container(Messages.ERR_ORGUNIT_DELETE_IN_CONTEXT_1, organizationalUnit.getName()));
2579        }
2580        // check organizational unit for user
2581        if (dbc.currentUser().getOuFqn().equals(organizationalUnit.getName())) {
2582            throw new CmsDbConsistencyException(
2583                Messages.get().container(Messages.ERR_ORGUNIT_DELETE_CURRENT_USER_1, organizationalUnit.getName()));
2584        }
2585        // check sub organizational units
2586        if (!getOrganizationalUnits(dbc, organizationalUnit, true).isEmpty()) {
2587            throw new CmsDbConsistencyException(
2588                Messages.get().container(Messages.ERR_ORGUNIT_DELETE_SUB_ORGUNITS_1, organizationalUnit.getName()));
2589        }
2590        // check groups
2591        List<CmsGroup> groups = getGroups(dbc, organizationalUnit, true, false);
2592        Iterator<CmsGroup> itGroups = groups.iterator();
2593        while (itGroups.hasNext()) {
2594            CmsGroup group = itGroups.next();
2595            if (!OpenCms.getDefaultUsers().isDefaultGroup(group.getName())) {
2596                throw new CmsDbConsistencyException(
2597                    Messages.get().container(Messages.ERR_ORGUNIT_DELETE_GROUPS_1, organizationalUnit.getName()));
2598            }
2599        }
2600        // check users
2601        if (!getUsers(dbc, organizationalUnit, true).isEmpty()) {
2602            throw new CmsDbConsistencyException(
2603                Messages.get().container(Messages.ERR_ORGUNIT_DELETE_USERS_1, organizationalUnit.getName()));
2604        }
2605
2606        // delete default groups if needed
2607        itGroups = groups.iterator();
2608        while (itGroups.hasNext()) {
2609            CmsGroup group = itGroups.next();
2610            deleteGroup(dbc, group, null);
2611        }
2612
2613        // delete projects
2614        Iterator<CmsProject> itProjects = getProjectDriver(dbc).readProjects(
2615            dbc,
2616            organizationalUnit.getName()).iterator();
2617        while (itProjects.hasNext()) {
2618            CmsProject project = itProjects.next();
2619            deleteProject(dbc, project, false);
2620        }
2621
2622        // delete roles
2623        Iterator<CmsGroup> itRoles = getGroups(dbc, organizationalUnit, true, true).iterator();
2624        while (itRoles.hasNext()) {
2625            CmsGroup role = itRoles.next();
2626            deleteGroup(dbc, role, null);
2627        }
2628
2629        // create a publish list for the 'virtual' publish event
2630        CmsResource resource = readResource(dbc, organizationalUnit.getId(), CmsResourceFilter.DEFAULT);
2631        CmsPublishList pl = new CmsPublishList(resource, false);
2632        pl.add(resource, false);
2633
2634        // remove the organizational unit itself
2635        getUserDriver(dbc).deleteOrganizationalUnit(dbc, organizationalUnit);
2636
2637        // write the publish history entry
2638        getProjectDriver(dbc).writePublishHistory(
2639            dbc,
2640            pl.getPublishHistoryId(),
2641            new CmsPublishedResource(resource, -1, CmsResourceState.STATE_DELETED));
2642
2643        // flush relevant caches
2644        m_monitor.clearPrincipalsCache();
2645        m_monitor.flushCache(CmsMemoryMonitor.CacheType.PROPERTY, CmsMemoryMonitor.CacheType.PROPERTY_LIST);
2646
2647        // fire the 'virtual' publish event
2648        Map<String, Object> eventData = new HashMap<String, Object>();
2649        eventData.put(I_CmsEventListener.KEY_PUBLISHID, pl.getPublishHistoryId().toString());
2650        eventData.put(I_CmsEventListener.KEY_PROJECTID, dbc.currentProject().getUuid());
2651        eventData.put(I_CmsEventListener.KEY_DBCONTEXT, dbc);
2652        CmsEvent afterPublishEvent = new CmsEvent(I_CmsEventListener.EVENT_PUBLISH_PROJECT, eventData);
2653        OpenCms.fireCmsEvent(afterPublishEvent);
2654
2655        m_lockManager.removeDeletedResource(dbc, resource.getRootPath());
2656
2657        if (!dbc.getProjectId().isNullUUID()) {
2658            // OU modified event is not needed
2659            return;
2660        }
2661        // fire OU modified event
2662        Map<String, Object> event2Data = new HashMap<String, Object>();
2663        event2Data.put(I_CmsEventListener.KEY_OU_NAME, organizationalUnit.getName());
2664        event2Data.put(I_CmsEventListener.KEY_USER_ACTION, I_CmsEventListener.VALUE_OU_MODIFIED_ACTION_DELETE);
2665        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_OU_MODIFIED, event2Data));
2666
2667    }
2668
2669    /**
2670     * Deletes a project.<p>
2671     *
2672     * Only the admin or the owner of the project can do this.
2673     *
2674     * @param dbc the current database context
2675     * @param deleteProject the project to be deleted
2676     *
2677     * @throws CmsException if something goes wrong
2678     */
2679    public void deleteProject(CmsDbContext dbc, CmsProject deleteProject) throws CmsException {
2680
2681        deleteProject(dbc, deleteProject, true);
2682    }
2683
2684    /**
2685     * Deletes a project.<p>
2686     *
2687     * Only the admin or the owner of the project can do this.
2688     *
2689     * @param dbc the current database context
2690     * @param deleteProject the project to be deleted
2691     * @param resetResources if true, the resources of the project to delete will be reset to their online state, or deleted if they have no online state
2692     *
2693     * @throws CmsException if something goes wrong
2694     */
2695    public void deleteProject(CmsDbContext dbc, CmsProject deleteProject, boolean resetResources) throws CmsException {
2696
2697        CmsUUID projectId = deleteProject.getUuid();
2698
2699        if (resetResources) {
2700            // changed/new/deleted files in the specified project
2701            List<CmsResource> modifiedFiles = readChangedResourcesInsideProject(dbc, projectId, RCPRM_FILES_ONLY_MODE);
2702            // changed/new/deleted folders in the specified project
2703            List<CmsResource> modifiedFolders = readChangedResourcesInsideProject(
2704                dbc,
2705                projectId,
2706                RCPRM_FOLDERS_ONLY_MODE);
2707            resetResourcesInProject(dbc, projectId, modifiedFiles, modifiedFolders);
2708        }
2709
2710        // unlock all resources in the project
2711        m_lockManager.removeResourcesInProject(deleteProject.getUuid(), true);
2712        m_monitor.clearAccessControlListCache();
2713        m_monitor.clearResourceCache();
2714
2715        // set project to online project if current project is the one which will be deleted
2716        if (projectId.equals(dbc.currentProject().getUuid())) {
2717            dbc.getRequestContext().setCurrentProject(readProject(dbc, CmsProject.ONLINE_PROJECT_ID));
2718        }
2719
2720        // delete the project itself
2721        getProjectDriver(dbc).deleteProject(dbc, deleteProject);
2722        m_monitor.uncacheProject(deleteProject);
2723
2724        // fire the corresponding event
2725        OpenCms.fireCmsEvent(
2726            new CmsEvent(
2727                I_CmsEventListener.EVENT_PROJECT_MODIFIED,
2728                Collections.<String, Object> singletonMap("project", deleteProject)));
2729
2730    }
2731
2732    /**
2733     * Deletes a property definition.<p>
2734     *
2735     * @param dbc the current database context
2736     * @param name the name of the property definition to delete
2737     *
2738     * @throws CmsException if something goes wrong
2739     */
2740    public void deletePropertyDefinition(CmsDbContext dbc, String name) throws CmsException {
2741
2742        CmsPropertyDefinition propertyDefinition = null;
2743
2744        try {
2745            // first read and then delete the metadefinition.
2746            propertyDefinition = readPropertyDefinition(dbc, name);
2747            getVfsDriver(dbc).deletePropertyDefinition(dbc, propertyDefinition);
2748            getHistoryDriver(dbc).deletePropertyDefinition(dbc, propertyDefinition);
2749        } finally {
2750
2751            // fire an event that a property of a resource has been deleted
2752            OpenCms.fireCmsEvent(
2753                new CmsEvent(
2754                    I_CmsEventListener.EVENT_PROPERTY_DEFINITION_MODIFIED,
2755                    Collections.<String, Object> singletonMap("propertyDefinition", propertyDefinition)));
2756        }
2757    }
2758
2759    /**
2760     * Deletes a publish job identified by its history id.<p>
2761     *
2762     * @param dbc the current database context
2763     * @param publishHistoryId the history id identifying the publish job
2764     *
2765     * @throws CmsException if something goes wrong
2766     */
2767    public void deletePublishJob(CmsDbContext dbc, CmsUUID publishHistoryId) throws CmsException {
2768
2769        getProjectDriver(dbc).deletePublishJob(dbc, publishHistoryId);
2770    }
2771
2772    /**
2773     * Deletes the publish list assigned to a publish job.<p>
2774     *
2775     * @param dbc the current database context
2776     * @param publishHistoryId the history id identifying the publish job
2777     * @throws CmsException if something goes wrong
2778     */
2779    public void deletePublishList(CmsDbContext dbc, CmsUUID publishHistoryId) throws CmsException {
2780
2781        getProjectDriver(dbc).deletePublishList(dbc, publishHistoryId);
2782    }
2783
2784    /**
2785     * Deletes all relations for the given resource matching the given filter.<p>
2786     *
2787     * @param dbc the current db context
2788     * @param resource the resource to delete the relations for
2789     * @param filter the filter to use for deletion
2790     *
2791     * @throws CmsException if something goes wrong
2792     *
2793     * @see CmsSecurityManager#deleteRelationsForResource(CmsRequestContext, CmsResource, CmsRelationFilter)
2794     */
2795    public void deleteRelationsForResource(CmsDbContext dbc, CmsResource resource, CmsRelationFilter filter)
2796    throws CmsException {
2797
2798        if (filter.includesDefinedInContent()) {
2799            throw new CmsIllegalArgumentException(
2800                Messages.get().container(
2801                    Messages.ERR_DELETE_RELATION_IN_CONTENT_2,
2802                    dbc.removeSiteRoot(resource.getRootPath()),
2803                    filter.getTypes()));
2804        }
2805        getVfsDriver(dbc).deleteRelations(dbc, dbc.currentProject().getUuid(), resource, filter);
2806        setDateLastModified(dbc, resource, System.currentTimeMillis());
2807        log(
2808            dbc,
2809            new CmsLogEntry(
2810                dbc,
2811                resource.getStructureId(),
2812                CmsLogEntryType.RESOURCE_REMOVE_RELATION,
2813                new String[] {resource.getRootPath(), filter.toString()}),
2814            false);
2815    }
2816
2817    /**
2818     * Deletes a resource.<p>
2819     *
2820     * The <code>siblingMode</code> parameter controls how to handle siblings
2821     * during the delete operation.
2822     * Possible values for this parameter are:
2823     * <ul>
2824     * <li><code>{@link CmsResource#DELETE_REMOVE_SIBLINGS}</code></li>
2825     * <li><code>{@link CmsResource#DELETE_PRESERVE_SIBLINGS}</code></li>
2826     * </ul><p>
2827     *
2828     * @param dbc the current database context
2829     * @param resource the name of the resource to delete (full path)
2830     * @param siblingMode indicates how to handle siblings of the deleted resource
2831     *
2832     * @throws CmsException if something goes wrong
2833     *
2834     * @see CmsObject#deleteResource(String, CmsResource.CmsResourceDeleteMode)
2835     * @see I_CmsResourceType#deleteResource(CmsObject, CmsSecurityManager, CmsResource, CmsResource.CmsResourceDeleteMode)
2836     */
2837    public void deleteResource(CmsDbContext dbc, CmsResource resource, CmsResource.CmsResourceDeleteMode siblingMode)
2838    throws CmsException {
2839
2840        // upgrade a potential inherited, non-shared lock into a common lock
2841        CmsLock currentLock = getLock(dbc, resource);
2842        if (currentLock.getEditionLock().isDirectlyInherited()) {
2843            // upgrade the lock status if required
2844            lockResource(dbc, resource, CmsLockType.EXCLUSIVE);
2845        }
2846
2847        // check if siblings of the resource exist and must be deleted as well
2848        if (resource.isFolder()) {
2849            // folder can have no siblings
2850            siblingMode = CmsResource.DELETE_PRESERVE_SIBLINGS;
2851        }
2852
2853        // if selected, add all siblings of this resource to the list of resources to be deleted
2854        boolean allSiblingsRemoved;
2855        List<CmsResource> resources;
2856        if (siblingMode == CmsResource.DELETE_REMOVE_SIBLINGS) {
2857            resources = new ArrayList<CmsResource>(readSiblings(dbc, resource, CmsResourceFilter.ALL));
2858            allSiblingsRemoved = true;
2859
2860            // ensure that the resource requested to be deleted is the last resource that gets actually deleted
2861            // to keep the shared locks of the siblings while those get deleted.
2862            resources.remove(resource);
2863            resources.add(resource);
2864        } else {
2865            // only delete the resource, no siblings
2866            resources = Collections.singletonList(resource);
2867            allSiblingsRemoved = false;
2868        }
2869
2870        int size = resources.size();
2871        // if we have only one resource no further check is required
2872        if (size > 1) {
2873            CmsMultiException me = new CmsMultiException();
2874            // ensure that each sibling is unlocked or locked by the current user
2875            for (int i = 0; i < size; i++) {
2876                CmsResource currentResource = resources.get(i);
2877                currentLock = getLock(dbc, currentResource);
2878                if (!currentLock.getEditionLock().isUnlocked() && !currentLock.isOwnedBy(dbc.currentUser())) {
2879                    // the resource is locked by a user different from the current user
2880                    CmsRequestContext context = dbc.getRequestContext();
2881                    me.addException(
2882                        new CmsLockException(
2883                            org.opencms.lock.Messages.get().container(
2884                                org.opencms.lock.Messages.ERR_SIBLING_LOCKED_2,
2885                                context.getSitePath(currentResource),
2886                                context.getSitePath(resource))));
2887                }
2888            }
2889            if (!me.getExceptions().isEmpty()) {
2890                throw me;
2891            }
2892        }
2893
2894        boolean removeAce = true;
2895
2896        if (resource.isFolder()) {
2897            // check if the folder has any resources in it
2898            Iterator<CmsResource> childResources = getVfsDriver(dbc).readChildResources(
2899                dbc,
2900                dbc.currentProject(),
2901                resource,
2902                true,
2903                true).iterator();
2904
2905            CmsUUID projectId = CmsProject.ONLINE_PROJECT_ID;
2906            if (dbc.currentProject().isOnlineProject()) {
2907                projectId = CmsUUID.getOpenCmsUUID(); // HACK: to get an offline project id
2908            }
2909
2910            // collect the names of the resources inside the folder, excluding the moved resources
2911            StringBuffer errorResNames = new StringBuffer(128);
2912            while (childResources.hasNext()) {
2913                CmsResource errorRes = childResources.next();
2914                if (errorRes.getState().isDeleted()) {
2915                    continue;
2916                }
2917                // if deleting offline, or not moved, or just renamed inside the deleted folder
2918                // so, it may remain some orphan online entries for moved resources
2919                // which will be fixed during the publishing of the moved resources
2920                boolean error = !dbc.currentProject().isOnlineProject();
2921                if (!error) {
2922                    try {
2923                        String originalPath = getVfsDriver(dbc).readResource(
2924                            dbc,
2925                            projectId,
2926                            errorRes.getRootPath(),
2927                            true).getRootPath();
2928                        error = originalPath.equals(errorRes.getRootPath())
2929                            || originalPath.startsWith(resource.getRootPath());
2930                    } catch (CmsVfsResourceNotFoundException e) {
2931                        // ignore
2932                    }
2933                }
2934                if (error) {
2935                    if (errorResNames.length() != 0) {
2936                        errorResNames.append(", ");
2937                    }
2938                    errorResNames.append("[" + dbc.removeSiteRoot(errorRes.getRootPath()) + "]");
2939                }
2940            }
2941
2942            // the current implementation only deletes empty folders
2943            if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(errorResNames.toString())) {
2944                throw new CmsVfsException(
2945                    org.opencms.db.generic.Messages.get().container(
2946                        org.opencms.db.generic.Messages.ERR_DELETE_NONEMTY_FOLDER_2,
2947                        dbc.removeSiteRoot(resource.getRootPath()),
2948                        errorResNames.toString()));
2949            }
2950        }
2951
2952        // delete all collected resources
2953        for (int i = 0; i < size; i++) {
2954            CmsResource currentResource = resources.get(i);
2955
2956            // try to delete/remove the resource only if the user has write access to the resource
2957            // check permissions only for the sibling, the resource it self was already checked or
2958            // is to be removed without write permissions, ie. while deleting a folder
2959            if (!currentResource.equals(resource)
2960                && (I_CmsPermissionHandler.PERM_ALLOWED != m_securityManager.hasPermissions(
2961                    dbc,
2962                    currentResource,
2963                    CmsPermissionSet.ACCESS_WRITE,
2964                    true,
2965                    CmsResourceFilter.ALL))) {
2966
2967                // no write access to sibling - must keep ACE (see below)
2968                allSiblingsRemoved = false;
2969            } else {
2970                // write access to sibling granted
2971                boolean existsOnline = (getVfsDriver(dbc).validateStructureIdExists(
2972                    dbc,
2973                    CmsProject.ONLINE_PROJECT_ID,
2974                    currentResource.getStructureId()) || !(currentResource.getState().equals(CmsResource.STATE_NEW)));
2975                if (!existsOnline) {
2976                    // the resource does not exist online => remove the resource
2977                    // this means the resource is "new" (blue) in the offline project
2978
2979                    // delete all properties of this resource
2980                    deleteAllProperties(dbc, currentResource.getRootPath());
2981
2982                    if (currentResource.isFolder()) {
2983                        getVfsDriver(dbc).removeFolder(dbc, dbc.currentProject(), currentResource);
2984                    } else {
2985                        // check labels
2986                        if (currentResource.isLabeled() && !labelResource(dbc, currentResource, null, 2)) {
2987                            // update the resource flags to "un label" the other siblings
2988                            int flags = currentResource.getFlags();
2989                            flags &= ~CmsResource.FLAG_LABELED;
2990                            currentResource.setFlags(flags);
2991                        }
2992                        getVfsDriver(dbc).removeFile(dbc, dbc.currentProject().getUuid(), currentResource);
2993                    }
2994
2995                    // ensure an exclusive lock is removed in the lock manager for a deleted new resource,
2996                    // otherwise it would "stick" in the lock manager, preventing other users from creating
2997                    // a file with the same name (issue with temp files in editor)
2998                    m_lockManager.removeDeletedResource(dbc, currentResource.getRootPath());
2999                    // delete relations
3000                    getVfsDriver(dbc).deleteRelations(
3001                        dbc,
3002                        dbc.currentProject().getUuid(),
3003                        currentResource,
3004                        CmsRelationFilter.TARGETS);
3005                    getVfsDriver(dbc).deleteUrlNameMappingEntries(
3006                        dbc,
3007                        false,
3008                        CmsUrlNameMappingFilter.ALL.filterStructureId(currentResource.getStructureId()));
3009                    getVfsDriver(dbc).deleteAliases(
3010                        dbc,
3011                        dbc.currentProject(),
3012                        new CmsAliasFilter(null, null, currentResource.getStructureId()));
3013                } else {
3014                    // the resource exists online => mark the resource as deleted
3015                    // structure record is removed during next publish
3016                    // if one (or more) siblings are not removed, the ACE can not be removed
3017                    removeAce = false;
3018
3019                    // set resource state to deleted
3020                    currentResource.setState(CmsResource.STATE_DELETED);
3021                    getVfsDriver(dbc).writeResourceState(
3022                        dbc,
3023                        dbc.currentProject(),
3024                        currentResource,
3025                        UPDATE_STRUCTURE,
3026                        false);
3027
3028                    // update the project ID
3029                    getVfsDriver(dbc).writeLastModifiedProjectId(
3030                        dbc,
3031                        dbc.currentProject(),
3032                        dbc.currentProject().getUuid(),
3033                        currentResource);
3034                    // log it
3035                    log(
3036                        dbc,
3037                        new CmsLogEntry(
3038                            dbc,
3039                            currentResource.getStructureId(),
3040                            CmsLogEntryType.RESOURCE_DELETED,
3041                            new String[] {currentResource.getRootPath()}),
3042                        true);
3043                }
3044            }
3045        }
3046
3047        if ((resource.getSiblingCount() <= 1) || allSiblingsRemoved) {
3048            if (removeAce) {
3049                // remove the access control entries
3050                getUserDriver(dbc).removeAccessControlEntries(dbc, dbc.currentProject(), resource.getResourceId());
3051            }
3052        }
3053
3054        // flush all caches
3055        m_monitor.clearAccessControlListCache();
3056        m_monitor.flushCache(
3057            CmsMemoryMonitor.CacheType.PROPERTY,
3058            CmsMemoryMonitor.CacheType.PROPERTY_LIST,
3059            CmsMemoryMonitor.CacheType.PROJECT_RESOURCES);
3060
3061        OpenCms.fireCmsEvent(
3062            new CmsEvent(
3063                I_CmsEventListener.EVENT_RESOURCE_DELETED,
3064                Collections.<String, Object> singletonMap(I_CmsEventListener.KEY_RESOURCES, resources)));
3065    }
3066
3067    /**
3068     * Deletes an entry in the published resource table.<p>
3069     *
3070     * @param dbc the current database context
3071     * @param resourceName The name of the resource to be deleted in the static export
3072     * @param linkType the type of resource deleted (0= non-parameter, 1=parameter)
3073     * @param linkParameter the parameters of the resource
3074     *
3075     * @throws CmsException if something goes wrong
3076     */
3077    public void deleteStaticExportPublishedResource(
3078        CmsDbContext dbc,
3079        String resourceName,
3080        int linkType,
3081        String linkParameter) throws CmsException {
3082
3083        getProjectDriver(dbc).deleteStaticExportPublishedResource(dbc, resourceName, linkType, linkParameter);
3084    }
3085
3086    /**
3087     * Deletes a user, where all permissions and resources attributes of the user
3088     * were transfered to a replacement user, if given.<p>
3089     *
3090     * Only users, which are in the group "administrators" are granted.<p>
3091     *
3092     * @param dbc the current database context
3093     * @param project the current project
3094     * @param username the name of the user to be deleted
3095     * @param replacementUsername the name of the user to be transfered, can be <code>null</code>
3096     *
3097     * @throws CmsException if operation was not successful
3098     */
3099    public void deleteUser(CmsDbContext dbc, CmsProject project, String username, String replacementUsername)
3100    throws CmsException {
3101
3102        // Test if the users exists
3103        CmsUser user = readUser(dbc, username);
3104        CmsUser replacementUser = null;
3105        if (replacementUsername != null) {
3106            replacementUser = readUser(dbc, replacementUsername);
3107        }
3108
3109        CmsProject onlineProject = readProject(dbc, CmsProject.ONLINE_PROJECT_ID);
3110        boolean withACEs = true;
3111        if (replacementUser == null) {
3112            withACEs = false;
3113            replacementUser = readUser(dbc, OpenCms.getDefaultUsers().getUserDeletedResource());
3114        }
3115
3116        boolean isVfsManager = m_securityManager.hasRole(dbc, replacementUser, CmsRole.VFS_MANAGER);
3117
3118        // iterate groups and roles
3119        for (boolean readRoles = false; !readRoles; readRoles = !readRoles) {
3120            Iterator<CmsGroup> itGroups = getGroupsOfUser(
3121                dbc,
3122                username,
3123                "",
3124                true,
3125                readRoles,
3126                true,
3127                dbc.getRequestContext().getRemoteAddress()).iterator();
3128            while (itGroups.hasNext()) {
3129                CmsGroup group = itGroups.next();
3130                if (!isVfsManager) {
3131                    // add replacement user to user groups
3132                    if (!userInGroup(dbc, replacementUser.getName(), group.getName(), readRoles)) {
3133                        addUserToGroup(dbc, replacementUser.getName(), group.getName(), readRoles);
3134                    }
3135                }
3136                // remove user from groups
3137                if (userInGroup(dbc, username, group.getName(), readRoles)) {
3138                    // we need this additional check because removing a user from a group
3139                    // may also automatically remove him from other groups if the group was
3140                    // associated with a role.
3141                    removeUserFromGroup(dbc, username, group.getName(), readRoles);
3142                }
3143            }
3144        }
3145        // remove all locks set for the deleted user
3146        m_lockManager.removeLocks(user.getId());
3147        // offline
3148        if (dbc.getProjectId().isNullUUID()) {
3149            // offline project available
3150            transferPrincipalResources(dbc, project, user.getId(), replacementUser.getId(), withACEs);
3151        }
3152        // online
3153        transferPrincipalResources(dbc, onlineProject, user.getId(), replacementUser.getId(), withACEs);
3154        getUserDriver(dbc).removeAccessControlEntriesForPrincipal(dbc, project, onlineProject, user.getId());
3155        getHistoryDriver(dbc).writePrincipal(dbc, user);
3156        getUserDriver(dbc).deleteUser(dbc, username);
3157        // delete user from cache
3158        m_monitor.clearUserCache(user);
3159
3160        if (!dbc.getProjectId().isNullUUID()) {
3161            // user modified event is not needed
3162            return;
3163        }
3164        // fire user modified event
3165        Map<String, Object> eventData = new HashMap<String, Object>();
3166        eventData.put(I_CmsEventListener.KEY_USER_ID, user.getId().toString());
3167        eventData.put(I_CmsEventListener.KEY_USER_NAME, user.getName());
3168        eventData.put(I_CmsEventListener.KEY_USER_ACTION, I_CmsEventListener.VALUE_USER_MODIFIED_ACTION_DELETE_USER);
3169        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_USER_MODIFIED, eventData));
3170    }
3171
3172    /**
3173     * Destroys this driver manager and releases all allocated resources.<p>
3174     */
3175    public void destroy() {
3176
3177        try {
3178            if (m_projectDriver != null) {
3179                try {
3180                    m_projectDriver.destroy();
3181                } catch (Throwable t) {
3182                    LOG.error(Messages.get().getBundle().key(Messages.ERR_CLOSE_PROJECT_DRIVER_0), t);
3183                }
3184                m_projectDriver = null;
3185            }
3186            if (m_userDriver != null) {
3187                try {
3188                    m_userDriver.destroy();
3189                } catch (Throwable t) {
3190                    LOG.error(Messages.get().getBundle().key(Messages.ERR_CLOSE_USER_DRIVER_0), t);
3191                }
3192                m_userDriver = null;
3193            }
3194            if (m_vfsDriver != null) {
3195                try {
3196                    m_vfsDriver.destroy();
3197                } catch (Throwable t) {
3198                    LOG.error(Messages.get().getBundle().key(Messages.ERR_CLOSE_VFS_DRIVER_0), t);
3199                }
3200                m_vfsDriver = null;
3201            }
3202            if (m_historyDriver != null) {
3203                try {
3204                    m_historyDriver.destroy();
3205                } catch (Throwable t) {
3206                    LOG.error(Messages.get().getBundle().key(Messages.ERR_CLOSE_HISTORY_DRIVER_0), t);
3207                }
3208                m_historyDriver = null;
3209            }
3210
3211            if (m_connectionPools != null) {
3212                for (int i = 0; i < m_connectionPools.size(); i++) {
3213                    PoolingDriver driver = m_connectionPools.get(i);
3214                    String[] pools = driver.getPoolNames();
3215                    for (String pool : pools) {
3216                        try {
3217                            driver.closePool(pool);
3218                            if (CmsLog.INIT.isDebugEnabled()) {
3219                                CmsLog.INIT.debug(
3220                                    Messages.get().getBundle().key(Messages.INIT_CLOSE_CONN_POOL_1, pool));
3221                            }
3222                        } catch (Throwable t) {
3223                            LOG.error(Messages.get().getBundle().key(Messages.LOG_CLOSE_CONN_POOL_ERROR_1, pool), t);
3224                        }
3225                    }
3226                }
3227                m_connectionPools = null;
3228            }
3229
3230            m_monitor.clearCache();
3231
3232            m_lockManager = null;
3233            m_htmlLinkValidator = null;
3234        } catch (Throwable t) {
3235            // ignore
3236        }
3237        if (CmsLog.INIT.isInfoEnabled()) {
3238            CmsLog.INIT.info(
3239                Messages.get().getBundle().key(Messages.INIT_DRIVER_MANAGER_DESTROY_1, getClass().getName()));
3240        }
3241
3242        org.opencms.db.jpa.CmsSqlManager.destroy();
3243    }
3244
3245    /**
3246     * Tests if a resource with the given resourceId does already exist in the Database.<p>
3247     *
3248     * @param dbc the current database context
3249     * @param resourceId the resource id to test for
3250     * @return true if a resource with the given id was found, false otherweise
3251     * @throws CmsException if something goes wrong
3252     */
3253    public boolean existsResourceId(CmsDbContext dbc, CmsUUID resourceId) throws CmsException {
3254
3255        return getVfsDriver(dbc).validateResourceIdExists(dbc, dbc.currentProject().getUuid(), resourceId);
3256    }
3257
3258    /**
3259     * Fills the given publish list with the the VFS resources that actually get published.<p>
3260     *
3261     * Please refer to the source code of this method for the rules on how to decide whether a
3262     * new/changed/deleted <code>{@link CmsResource}</code> object can be published or not.<p>
3263     *
3264     * @param dbc the current database context
3265     * @param publishList must be initialized with basic publish information (Project or direct publish operation),
3266     *                    the given publish list will be filled with all new/changed/deleted files from the current
3267     *                    (offline) project that will be actually published
3268     *
3269     * @throws CmsException if something goes wrong
3270     *
3271     * @see org.opencms.db.CmsPublishList
3272     */
3273    public void fillPublishList(CmsDbContext dbc, CmsPublishList publishList) throws CmsException {
3274
3275        if (!publishList.isDirectPublish()) {
3276            // when publishing a project
3277            // all modified resources with the last change done in the current project are candidates if unlocked
3278            List<CmsResource> folderList = getVfsDriver(dbc).readResourceTree(
3279                dbc,
3280                dbc.currentProject().getUuid(),
3281                CmsDriverManager.READ_IGNORE_PARENT,
3282                CmsDriverManager.READ_IGNORE_TYPE,
3283                CmsResource.STATE_UNCHANGED,
3284                CmsDriverManager.READ_IGNORE_TIME,
3285                CmsDriverManager.READ_IGNORE_TIME,
3286                CmsDriverManager.READ_IGNORE_TIME,
3287                CmsDriverManager.READ_IGNORE_TIME,
3288                CmsDriverManager.READ_IGNORE_TIME,
3289                CmsDriverManager.READ_IGNORE_TIME,
3290                CmsDriverManager.READMODE_INCLUDE_TREE
3291                    | CmsDriverManager.READMODE_INCLUDE_PROJECT
3292                    | CmsDriverManager.READMODE_EXCLUDE_STATE
3293                    | CmsDriverManager.READMODE_ONLY_FOLDERS);
3294
3295            publishList.addAll(filterResources(dbc, null, folderList), true);
3296
3297            List<CmsResource> fileList = getVfsDriver(dbc).readResourceTree(
3298                dbc,
3299                dbc.currentProject().getUuid(),
3300                CmsDriverManager.READ_IGNORE_PARENT,
3301                CmsDriverManager.READ_IGNORE_TYPE,
3302                CmsResource.STATE_UNCHANGED,
3303                CmsDriverManager.READ_IGNORE_TIME,
3304                CmsDriverManager.READ_IGNORE_TIME,
3305                CmsDriverManager.READ_IGNORE_TIME,
3306                CmsDriverManager.READ_IGNORE_TIME,
3307                CmsDriverManager.READ_IGNORE_TIME,
3308                CmsDriverManager.READ_IGNORE_TIME,
3309                CmsDriverManager.READMODE_INCLUDE_TREE
3310                    | CmsDriverManager.READMODE_INCLUDE_PROJECT
3311                    | CmsDriverManager.READMODE_EXCLUDE_STATE
3312                    | CmsDriverManager.READMODE_ONLY_FILES);
3313
3314            publishList.addAll(filterResources(dbc, publishList, fileList), true);
3315        } else {
3316            // this is a direct publish
3317            Iterator<CmsResource> it = publishList.getDirectPublishResources().iterator();
3318            while (it.hasNext()) {
3319                // iterate all resources in the direct publish list
3320                CmsResource directPublishResource = it.next();
3321                if (directPublishResource.isFolder()) {
3322                    // when publishing a folder directly,
3323                    // the folder and all modified resources within the tree below this folder
3324                    // and with the last change done in the current project are candidates if lockable
3325                    CmsLock lock = getLock(dbc, directPublishResource);
3326                    if (!directPublishResource.getState().isUnchanged() && lock.isLockableBy(dbc.currentUser())) {
3327
3328                        try {
3329                            m_securityManager.checkPermissions(
3330                                dbc,
3331                                directPublishResource,
3332                                CmsPermissionSet.ACCESS_DIRECT_PUBLISH,
3333                                false,
3334                                CmsResourceFilter.ALL);
3335                            publishList.add(directPublishResource, true);
3336                        } catch (CmsException e) {
3337                            // skip if not enough permissions
3338                        }
3339                    }
3340                    boolean shouldPublishDeletedSubResources = publishList.isUserPublishList()
3341                        && directPublishResource.getState().isDeleted();
3342                    if (publishList.isPublishSubResources() || shouldPublishDeletedSubResources) {
3343                        addSubResources(dbc, publishList, directPublishResource);
3344                    }
3345                } else if (directPublishResource.isFile() && !directPublishResource.getState().isUnchanged()) {
3346
3347                    // when publishing a file directly this file is the only candidate
3348                    // if it is modified and lockable
3349                    CmsLock lock = getLock(dbc, directPublishResource);
3350                    if (lock.isLockableBy(dbc.currentUser())) {
3351                        // check permissions
3352                        try {
3353                            m_securityManager.checkPermissions(
3354                                dbc,
3355                                directPublishResource,
3356                                CmsPermissionSet.ACCESS_DIRECT_PUBLISH,
3357                                false,
3358                                CmsResourceFilter.ALL);
3359                            publishList.add(directPublishResource, true);
3360                        } catch (CmsException e) {
3361                            // skip if not enough permissions
3362                        }
3363                    }
3364                }
3365            }
3366        }
3367
3368        // Step 2: if desired, extend the list of files to publish with related siblings
3369        if (publishList.isPublishSiblings()) {
3370            List<CmsResource> publishFiles = publishList.getFileList();
3371            int size = publishFiles.size();
3372
3373            // Improved: first calculate closure of all siblings, then filter and add them
3374            Set<CmsResource> siblingsClosure = new HashSet<CmsResource>(publishFiles);
3375            for (int i = 0; i < size; i++) {
3376                CmsResource currentFile = publishFiles.get(i);
3377                if (currentFile.getSiblingCount() > 1) {
3378                    siblingsClosure.addAll(readSiblings(dbc, currentFile, CmsResourceFilter.ALL_MODIFIED));
3379                }
3380            }
3381            publishList.addAll(filterSiblings(dbc, publishList, siblingsClosure), true);
3382        }
3383        publishList.initialize();
3384    }
3385
3386    /**
3387     * Returns the list of access control entries of a resource given its name.<p>
3388     *
3389     * @param dbc the current database context
3390     * @param resource the resource to read the access control entries for
3391     * @param getInherited true if the result should include all access control entries inherited by parent folders
3392     *
3393     * @return a list of <code>{@link CmsAccessControlEntry}</code> objects defining all permissions for the given resource
3394     *
3395     * @throws CmsException if something goes wrong
3396     */
3397    public List<CmsAccessControlEntry> getAccessControlEntries(
3398        CmsDbContext dbc,
3399        CmsResource resource,
3400        boolean getInherited) throws CmsException {
3401
3402        // get the ACE of the resource itself
3403        I_CmsUserDriver userDriver = getUserDriver(dbc);
3404        I_CmsVfsDriver vfsDriver = getVfsDriver(dbc);
3405        List<CmsAccessControlEntry> ace = userDriver.readAccessControlEntries(
3406            dbc,
3407            dbc.currentProject(),
3408            resource.getResourceId(),
3409            false);
3410
3411        // sort and check if we got the 'overwrite all' ace to stop looking up
3412        boolean overwriteAll = sortAceList(ace);
3413
3414        // get the ACE of each parent folder
3415        // Note: for the immediate parent, get non-inherited access control entries too,
3416        // if the resource is not a folder
3417        String parentPath = CmsResource.getParentFolder(resource.getRootPath());
3418        int d = (resource.isFolder()) ? 1 : 0;
3419
3420        while (!overwriteAll && getInherited && (parentPath != null)) {
3421            resource = vfsDriver.readFolder(dbc, dbc.currentProject().getUuid(), parentPath);
3422            List<CmsAccessControlEntry> entries = userDriver.readAccessControlEntries(
3423                dbc,
3424                dbc.currentProject(),
3425                resource.getResourceId(),
3426                d > 0);
3427
3428            // sort and check if we got the 'overwrite all' ace to stop looking up
3429            overwriteAll = sortAceList(entries);
3430
3431            for (CmsAccessControlEntry e : entries) {
3432                e.setFlags(CmsAccessControlEntry.ACCESS_FLAGS_INHERITED);
3433            }
3434
3435            ace.addAll(entries);
3436            parentPath = CmsResource.getParentFolder(resource.getRootPath());
3437            d++;
3438        }
3439
3440        return ace;
3441    }
3442
3443    /**
3444     * Returns the full access control list of a given resource.<p>
3445     *
3446     * @param dbc the current database context
3447     * @param resource the resource
3448     *
3449     * @return the access control list of the resource
3450     *
3451     * @throws CmsException if something goes wrong
3452     */
3453    public CmsAccessControlList getAccessControlList(CmsDbContext dbc, CmsResource resource) throws CmsException {
3454
3455        return getAccessControlList(dbc, resource, false);
3456    }
3457
3458    /**
3459     * Returns the access control list of a given resource.<p>
3460     *
3461     * If <code>inheritedOnly</code> is set, only inherited access control entries
3462     * are returned.<p>
3463     *
3464     * Note: For file resources, *all* permissions set at the immediate parent folder are inherited,
3465     * not only these marked to inherit.
3466     *
3467     * @param dbc the current database context
3468     * @param resource the resource
3469     * @param inheritedOnly skip non-inherited entries if set
3470     *
3471     * @return the access control list of the resource
3472     *
3473     * @throws CmsException if something goes wrong
3474     */
3475    public CmsAccessControlList getAccessControlList(CmsDbContext dbc, CmsResource resource, boolean inheritedOnly)
3476    throws CmsException {
3477
3478        return getAccessControlList(dbc, resource, inheritedOnly, resource.isFolder(), 0);
3479    }
3480
3481    /**
3482     * Returns the number of active connections managed by a pool.<p>
3483     *
3484     * @param dbPoolUrl the url of a pool
3485     * @return the number of active connections
3486     * @throws CmsDbException if something goes wrong
3487     */
3488    public int getActiveConnections(String dbPoolUrl) throws CmsDbException {
3489
3490        try {
3491            for (PoolingDriver d : m_connectionPools) {
3492                ObjectPool p = d.getConnectionPool(dbPoolUrl);
3493                return p.getNumActive();
3494            }
3495        } catch (Exception exc) {
3496            CmsMessageContainer message = Messages.get().container(Messages.ERR_ACCESSING_POOL_1, dbPoolUrl);
3497            throw new CmsDbException(message, exc);
3498        }
3499
3500        CmsMessageContainer message = Messages.get().container(Messages.ERR_UNKNOWN_POOL_URL_1, dbPoolUrl);
3501        throw new CmsDbException(message);
3502    }
3503
3504    /**
3505     * Reads all access control entries.<p>
3506     *
3507     * @param dbc the current database context
3508     * @return all access control entries for the current project (offline/online)
3509     *
3510     * @throws CmsException if something goes wrong
3511     */
3512    public List<CmsAccessControlEntry> getAllAccessControlEntries(CmsDbContext dbc) throws CmsException {
3513
3514        I_CmsUserDriver userDriver = getUserDriver(dbc);
3515        List<CmsAccessControlEntry> ace = userDriver.readAccessControlEntries(
3516            dbc,
3517            dbc.currentProject(),
3518            CmsAccessControlEntry.PRINCIPAL_READALL_ID,
3519            false);
3520        return ace;
3521    }
3522
3523    /**
3524     * Returns all projects which are owned by the current user or which are
3525     * accessible by the current user.<p>
3526     *
3527     * @param dbc the current database context
3528     * @param orgUnit the organizational unit to search project in
3529     * @param includeSubOus if to include sub organizational units
3530     *
3531     * @return a list of objects of type <code>{@link CmsProject}</code>
3532     *
3533     * @throws CmsException if something goes wrong
3534     */
3535    public List<CmsProject> getAllAccessibleProjects(
3536        CmsDbContext dbc,
3537        CmsOrganizationalUnit orgUnit,
3538        boolean includeSubOus) throws CmsException {
3539
3540        Set<CmsProject> projects = new HashSet<CmsProject>();
3541
3542        // get the ous where the user has the project manager role
3543        List<CmsOrganizationalUnit> ous = getOrgUnitsForRole(
3544            dbc,
3545            CmsRole.PROJECT_MANAGER.forOrgUnit(orgUnit.getName()),
3546            includeSubOus);
3547
3548        // get the groups of the user if needed
3549        Set<CmsUUID> userGroupIds = new HashSet<CmsUUID>();
3550        Iterator<CmsGroup> itGroups = getGroupsOfUser(dbc, dbc.currentUser().getName(), false).iterator();
3551        while (itGroups.hasNext()) {
3552            CmsGroup group = itGroups.next();
3553            userGroupIds.add(group.getId());
3554        }
3555
3556        // TODO: this could be optimize if this method would have an additional parameter 'includeSubOus'
3557        // get all projects that might come in question
3558        projects.addAll(getProjectDriver(dbc).readProjects(dbc, orgUnit.getName()));
3559
3560        // filter hidden and not accessible projects
3561        Iterator<CmsProject> itProjects = projects.iterator();
3562        while (itProjects.hasNext()) {
3563            CmsProject project = itProjects.next();
3564            boolean accessible = true;
3565            // if hidden
3566            accessible = accessible && !project.isHidden();
3567
3568            if (!includeSubOus) {
3569                // if not exact in the given ou
3570                accessible = accessible && project.getOuFqn().equals(orgUnit.getName());
3571            } else {
3572                // if not in the given ou
3573                accessible = accessible && project.getOuFqn().startsWith(orgUnit.getName());
3574            }
3575
3576            if (!accessible) {
3577                itProjects.remove();
3578                continue;
3579            }
3580
3581            accessible = false;
3582            // online project
3583            accessible = accessible || project.isOnlineProject();
3584            // if owner
3585            accessible = accessible || project.getOwnerId().equals(dbc.currentUser().getId());
3586
3587            // project managers
3588            Iterator<CmsOrganizationalUnit> itOus = ous.iterator();
3589            while (!accessible && itOus.hasNext()) {
3590                CmsOrganizationalUnit ou = itOus.next();
3591                // for project managers check visibility
3592                accessible = accessible || project.getOuFqn().startsWith(ou.getName());
3593            }
3594
3595            if (!accessible) {
3596                // if direct user or manager of project
3597                CmsUUID groupId = null;
3598                if (userGroupIds.contains(project.getGroupId())) {
3599                    groupId = project.getGroupId();
3600                } else if (userGroupIds.contains(project.getManagerGroupId())) {
3601                    groupId = project.getManagerGroupId();
3602                }
3603                if (groupId != null) {
3604                    String oufqn = readGroup(dbc, groupId).getOuFqn();
3605                    accessible = accessible || (oufqn.startsWith(dbc.getRequestContext().getOuFqn()));
3606                }
3607            }
3608            if (!accessible) {
3609                // remove not accessible project
3610                itProjects.remove();
3611            }
3612        }
3613
3614        List<CmsProject> accessibleProjects = new ArrayList<CmsProject>(projects);
3615        // sort the list of projects based on the project name
3616        Collections.sort(accessibleProjects);
3617        // ensure the online project is in first place
3618        CmsProject onlineProject = readProject(dbc, CmsProject.ONLINE_PROJECT_ID);
3619        if (accessibleProjects.contains(onlineProject)) {
3620            accessibleProjects.remove(onlineProject);
3621        }
3622        accessibleProjects.add(0, onlineProject);
3623
3624        return accessibleProjects;
3625    }
3626
3627    /**
3628     * Returns a list with all projects from history.<p>
3629     *
3630     * @param dbc the current database context
3631     *
3632     * @return list of <code>{@link CmsHistoryProject}</code> objects
3633     *           with all projects from history.
3634     *
3635     * @throws CmsException if operation was not successful
3636     */
3637    public List<CmsHistoryProject> getAllHistoricalProjects(CmsDbContext dbc) throws CmsException {
3638
3639        // user is allowed to access all existing projects for the ous he has the project_manager role
3640        Set<CmsOrganizationalUnit> manOus = new HashSet<CmsOrganizationalUnit>(
3641            getOrgUnitsForRole(dbc, CmsRole.PROJECT_MANAGER, true));
3642
3643        List<CmsHistoryProject> projects = getHistoryDriver(dbc).readProjects(dbc);
3644        Iterator<CmsHistoryProject> itProjects = projects.iterator();
3645        while (itProjects.hasNext()) {
3646            CmsHistoryProject project = itProjects.next();
3647            if (project.isHidden()) {
3648                // project is hidden
3649                itProjects.remove();
3650                continue;
3651            }
3652            if (!project.getOuFqn().startsWith(dbc.currentUser().getOuFqn())) {
3653                // project is not visible from the users ou
3654                itProjects.remove();
3655                continue;
3656            }
3657            CmsOrganizationalUnit ou = readOrganizationalUnit(dbc, project.getOuFqn());
3658            if (manOus.contains(ou)) {
3659                // user is project manager for this project
3660                continue;
3661            } else if (project.getOwnerId().equals(dbc.currentUser().getId())) {
3662                // user is owner of the project
3663                continue;
3664            } else {
3665                boolean found = false;
3666                Iterator<CmsGroup> itGroups = getGroupsOfUser(dbc, dbc.currentUser().getName(), false).iterator();
3667                while (itGroups.hasNext()) {
3668                    CmsGroup group = itGroups.next();
3669                    if (project.getManagerGroupId().equals(group.getId())) {
3670                        found = true;
3671                        break;
3672                    }
3673                }
3674                if (found) {
3675                    // user is member of the manager group of the project
3676                    continue;
3677                }
3678            }
3679            itProjects.remove();
3680        }
3681        return projects;
3682    }
3683
3684    /**
3685     * Returns all projects which are owned by the current user or which are manageable
3686     * for the group of the user.<p>
3687     *
3688     * @param dbc the current database context
3689     * @param orgUnit the organizational unit to search project in
3690     * @param includeSubOus if to include sub organizational units
3691     *
3692     * @return a list of objects of type <code>{@link CmsProject}</code>
3693     *
3694     * @throws CmsException if operation was not successful
3695     */
3696    public List<CmsProject> getAllManageableProjects(
3697        CmsDbContext dbc,
3698        CmsOrganizationalUnit orgUnit,
3699        boolean includeSubOus) throws CmsException {
3700
3701        Set<CmsProject> projects = new HashSet<CmsProject>();
3702
3703        // get the ous where the user has the project manager role
3704        List<CmsOrganizationalUnit> ous = getOrgUnitsForRole(
3705            dbc,
3706            CmsRole.PROJECT_MANAGER.forOrgUnit(orgUnit.getName()),
3707            includeSubOus);
3708
3709        // get the groups of the user if needed
3710        Set<CmsUUID> userGroupIds = new HashSet<CmsUUID>();
3711        Iterator<CmsGroup> itGroups = getGroupsOfUser(dbc, dbc.currentUser().getName(), false).iterator();
3712        while (itGroups.hasNext()) {
3713            CmsGroup group = itGroups.next();
3714            userGroupIds.add(group.getId());
3715        }
3716
3717        // TODO: this could be optimize if this method would have an additional parameter 'includeSubOus'
3718        // get all projects that might come in question
3719        projects.addAll(getProjectDriver(dbc).readProjects(dbc, orgUnit.getName()));
3720
3721        // filter hidden and not manageable projects
3722        Iterator<CmsProject> itProjects = projects.iterator();
3723        while (itProjects.hasNext()) {
3724            CmsProject project = itProjects.next();
3725            boolean manageable = true;
3726            // if online
3727            manageable = manageable && !project.isOnlineProject();
3728            // if hidden
3729            manageable = manageable && !project.isHidden();
3730
3731            if (!includeSubOus) {
3732                // if not exact in the given ou
3733                manageable = manageable && project.getOuFqn().equals(orgUnit.getName());
3734            } else {
3735                // if not in the given ou
3736                manageable = manageable && project.getOuFqn().startsWith(orgUnit.getName());
3737            }
3738
3739            if (!manageable) {
3740                itProjects.remove();
3741                continue;
3742            }
3743
3744            manageable = false;
3745            // if owner
3746            manageable = manageable || project.getOwnerId().equals(dbc.currentUser().getId());
3747
3748            // project managers
3749            Iterator<CmsOrganizationalUnit> itOus = ous.iterator();
3750            while (!manageable && itOus.hasNext()) {
3751                CmsOrganizationalUnit ou = itOus.next();
3752                // for project managers check visibility
3753                manageable = manageable || project.getOuFqn().startsWith(ou.getName());
3754            }
3755
3756            if (!manageable) {
3757                // if manager of project
3758                if (userGroupIds.contains(project.getManagerGroupId())) {
3759                    String oufqn = readGroup(dbc, project.getManagerGroupId()).getOuFqn();
3760                    manageable = manageable || (oufqn.startsWith(dbc.getRequestContext().getOuFqn()));
3761                }
3762            }
3763            if (!manageable) {
3764                // remove not accessible project
3765                itProjects.remove();
3766            }
3767        }
3768
3769        List<CmsProject> manageableProjects = new ArrayList<CmsProject>(projects);
3770        // sort the list of projects based on the project name
3771        Collections.sort(manageableProjects);
3772        // ensure the online project is not in the list
3773        CmsProject onlineProject = readProject(dbc, CmsProject.ONLINE_PROJECT_ID);
3774        if (manageableProjects.contains(onlineProject)) {
3775            manageableProjects.remove(onlineProject);
3776        }
3777
3778        return manageableProjects;
3779    }
3780
3781    /**
3782     * Returns all child groups of a group.<p>
3783     *
3784     * @param dbc the current database context
3785     * @param group the group to get the child for
3786     * @param includeSubChildren if set also returns all sub-child groups of the given group
3787     *
3788     * @return a list of all child <code>{@link CmsGroup}</code> objects
3789     *
3790     * @throws CmsException if operation was not successful
3791     */
3792    public List<CmsGroup> getChildren(CmsDbContext dbc, CmsGroup group, boolean includeSubChildren)
3793    throws CmsException {
3794
3795        if (!includeSubChildren) {
3796            return getUserDriver(dbc).readChildGroups(dbc, group.getName());
3797        }
3798        Set<CmsGroup> allChildren = new TreeSet<CmsGroup>();
3799        // iterate all child groups
3800        Iterator<CmsGroup> it = getUserDriver(dbc).readChildGroups(dbc, group.getName()).iterator();
3801        while (it.hasNext()) {
3802            CmsGroup child = it.next();
3803            // add the group itself
3804            allChildren.add(child);
3805            // now get all sub-children for each group
3806            allChildren.addAll(getChildren(dbc, child, true));
3807        }
3808        return new ArrayList<CmsGroup>(allChildren);
3809    }
3810
3811    /**
3812     * Returns the date when the resource was last visited by the user.<p>
3813     *
3814     * @param dbc the database context
3815     * @param poolName the name of the database pool to use
3816     * @param user the user to check the date
3817     * @param resource the resource to check the date
3818     *
3819     * @return the date when the resource was last visited by the user
3820     *
3821     * @throws CmsException if something goes wrong
3822     */
3823    public long getDateLastVisitedBy(CmsDbContext dbc, String poolName, CmsUser user, CmsResource resource)
3824    throws CmsException {
3825
3826        return m_subscriptionDriver.getDateLastVisitedBy(dbc, poolName, user, resource);
3827    }
3828
3829    /**
3830     * Returns all groups of the given organizational unit.<p>
3831     *
3832     * @param dbc the current db context
3833     * @param orgUnit the organizational unit to get the groups for
3834     * @param includeSubOus if all groups of sub-organizational units should be retrieved too
3835     * @param readRoles if to read roles or groups
3836     *
3837     * @return all <code>{@link CmsGroup}</code> objects in the organizational unit
3838     *
3839     * @throws CmsException if operation was not successful
3840     *
3841     * @see org.opencms.security.CmsOrgUnitManager#getResourcesForOrganizationalUnit(CmsObject, String)
3842     * @see org.opencms.security.CmsOrgUnitManager#getGroups(CmsObject, String, boolean)
3843     */
3844    public List<CmsGroup> getGroups(
3845        CmsDbContext dbc,
3846        CmsOrganizationalUnit orgUnit,
3847        boolean includeSubOus,
3848        boolean readRoles) throws CmsException {
3849
3850        return getUserDriver(dbc).getGroups(dbc, orgUnit, includeSubOus, readRoles);
3851    }
3852
3853    /**
3854     * Returns the groups of an user filtered by the specified IP address.<p>
3855     *
3856     * @param dbc the current database context
3857     * @param username the name of the user
3858     * @param readRoles if to read roles or groups
3859     *
3860     * @return the groups of the given user, as a list of {@link CmsGroup} objects
3861     *
3862     * @throws CmsException if something goes wrong
3863     */
3864    public List<CmsGroup> getGroupsOfUser(CmsDbContext dbc, String username, boolean readRoles) throws CmsException {
3865
3866        return getGroupsOfUser(dbc, username, "", true, readRoles, false, dbc.getRequestContext().getRemoteAddress());
3867    }
3868
3869    /**
3870     * Returns the groups of an user filtered by the specified IP address.<p>
3871     *
3872     * @param dbc the current database context
3873     * @param username the name of the user
3874     * @param ouFqn the fully qualified name of the organizational unit to restrict the result set for
3875     * @param includeChildOus include groups of child organizational units
3876     * @param readRoles if to read roles or groups
3877     * @param directGroupsOnly if set only the direct assigned groups will be returned, if not also indirect groups
3878     * @param remoteAddress the IP address to filter the groups in the result list
3879     *
3880     * @return a list of <code>{@link CmsGroup}</code> objects
3881     *
3882     * @throws CmsException if operation was not successful
3883     */
3884    public List<CmsGroup> getGroupsOfUser(
3885        CmsDbContext dbc,
3886        String username,
3887        String ouFqn,
3888        boolean includeChildOus,
3889        boolean readRoles,
3890        boolean directGroupsOnly,
3891        String remoteAddress) throws CmsException {
3892
3893        CmsUser user = readUser(dbc, username);
3894        String prefix = ouFqn + "_" + includeChildOus + "_" + directGroupsOnly + "_" + readRoles + "_" + remoteAddress;
3895        String cacheKey = m_keyGenerator.getCacheKeyForUserGroups(prefix, dbc, user);
3896        List<CmsGroup> groups = m_monitor.getCachedUserGroups(cacheKey);
3897        if (groups == null) {
3898            // get all groups of the user
3899            List<CmsGroup> directGroups = getUserDriver(dbc).readGroupsOfUser(
3900                dbc,
3901                user.getId(),
3902                readRoles ? "" : ouFqn,
3903                readRoles ? true : includeChildOus,
3904                remoteAddress,
3905                readRoles);
3906            Set<CmsGroup> allGroups = new HashSet<CmsGroup>();
3907            if (!readRoles) {
3908                allGroups.addAll(directGroups);
3909            }
3910            if (!directGroupsOnly) {
3911                if (!readRoles) {
3912                    // now get all parents of the groups
3913                    for (int i = 0; i < directGroups.size(); i++) {
3914                        CmsGroup parent = getParent(dbc, directGroups.get(i).getName());
3915                        while ((parent != null) && (!allGroups.contains(parent))) {
3916                            if (parent.getOuFqn().startsWith(ouFqn)) {
3917                                allGroups.add(parent);
3918                            }
3919                            // read next parent group
3920                            parent = getParent(dbc, parent.getName());
3921                        }
3922                    }
3923                }
3924            }
3925            if (readRoles) {
3926                // for each for role
3927                for (int i = 0; i < directGroups.size(); i++) {
3928                    CmsGroup group = directGroups.get(i);
3929                    CmsRole role = CmsRole.valueOf(group);
3930                    if (!includeChildOus && role.getOuFqn().equals(ouFqn)) {
3931                        allGroups.add(group);
3932                    }
3933                    if (includeChildOus && role.getOuFqn().startsWith(ouFqn)) {
3934                        allGroups.add(group);
3935                    }
3936                    if (directGroupsOnly || (!includeChildOus && !role.getOuFqn().equals(ouFqn))) {
3937                        // if roles of child OUs are not requested and the role does not belong to the requested OU don't include the role children
3938                        continue;
3939                    }
3940                    CmsOrganizationalUnit currentOu = readOrganizationalUnit(dbc, group.getOuFqn());
3941                    boolean readChildRoleGroups = true;
3942                    if (currentOu.hasFlagWebuser() && role.forOrgUnit(null).equals(CmsRole.ACCOUNT_MANAGER)) {
3943                        readChildRoleGroups = false;
3944                    }
3945                    if (readChildRoleGroups) {
3946                        // get the child roles
3947                        Iterator<CmsRole> itChildRoles = role.getChildren(true).iterator();
3948                        while (itChildRoles.hasNext()) {
3949                            CmsRole childRole = itChildRoles.next();
3950                            if (childRole.isSystemRole()) {
3951                                if (canReadRoleInOu(currentOu, childRole)) {
3952                                    // include system roles only
3953                                    allGroups.add(readGroup(dbc, childRole.getGroupName()));
3954                                }
3955                            }
3956                        }
3957                    } else {
3958                        LOG.info("Skipping child role group check for web user OU " + currentOu.getName());
3959                    }
3960                    if (includeChildOus) {
3961                        // if needed include the roles of child ous
3962                        Iterator<CmsOrganizationalUnit> itSubOus = getOrganizationalUnits(
3963                            dbc,
3964                            readOrganizationalUnit(dbc, group.getOuFqn()),
3965                            true).iterator();
3966                        while (itSubOus.hasNext()) {
3967                            CmsOrganizationalUnit subOu = itSubOus.next();
3968                            // add role in child ou
3969                            try {
3970                                if (canReadRoleInOu(subOu, role)) {
3971                                    allGroups.add(readGroup(dbc, role.forOrgUnit(subOu.getName()).getGroupName()));
3972                                }
3973                            } catch (CmsDbEntryNotFoundException e) {
3974                                // ignore, this may happen while deleting an orgunit
3975                                if (LOG.isDebugEnabled()) {
3976                                    LOG.debug(e.getLocalizedMessage(), e);
3977                                }
3978                            }
3979                            // add child roles in child ous
3980                            Iterator<CmsRole> itChildRoles = role.getChildren(true).iterator();
3981                            while (itChildRoles.hasNext()) {
3982                                CmsRole childRole = itChildRoles.next();
3983                                try {
3984                                    if (canReadRoleInOu(subOu, childRole)) {
3985                                        allGroups.add(
3986                                            readGroup(dbc, childRole.forOrgUnit(subOu.getName()).getGroupName()));
3987                                    }
3988                                } catch (CmsDbEntryNotFoundException e) {
3989                                    // ignore, this may happen while deleting an orgunit
3990                                    if (LOG.isDebugEnabled()) {
3991                                        LOG.debug(e.getLocalizedMessage(), e);
3992                                    }
3993                                }
3994                            }
3995                        }
3996                    }
3997                }
3998            }
3999            // make group list unmodifiable for caching
4000            groups = Collections.unmodifiableList(new ArrayList<CmsGroup>(allGroups));
4001            if (dbc.getProjectId().isNullUUID()) {
4002                m_monitor.cacheUserGroups(cacheKey, groups);
4003            }
4004        }
4005
4006        return groups;
4007    }
4008
4009    /**
4010     * Returns the history driver.<p>
4011     *
4012     * @return the history driver
4013     */
4014    public I_CmsHistoryDriver getHistoryDriver() {
4015
4016        return m_historyDriver;
4017    }
4018
4019    /**
4020     * Returns the history driver for a given database context.<p>
4021     *
4022     * @param dbc the database context
4023     * @return the history driver for the database context
4024     */
4025    public I_CmsHistoryDriver getHistoryDriver(CmsDbContext dbc) {
4026
4027        if ((dbc == null) || (dbc.getProjectId() == null) || dbc.getProjectId().isNullUUID()) {
4028            return m_historyDriver;
4029        }
4030        I_CmsHistoryDriver driver = dbc.getHistoryDriver(dbc.getProjectId());
4031        return driver != null ? driver : m_historyDriver;
4032
4033    }
4034
4035    /**
4036     * Returns the number of idle connections managed by a pool.<p>
4037     *
4038     * @param dbPoolUrl the url of a pool
4039     * @return the number of idle connections
4040     * @throws CmsDbException if something goes wrong
4041     */
4042    public int getIdleConnections(String dbPoolUrl) throws CmsDbException {
4043
4044        try {
4045            for (PoolingDriver d : m_connectionPools) {
4046                ObjectPool p = d.getConnectionPool(dbPoolUrl);
4047                return p.getNumIdle();
4048            }
4049        } catch (Exception exc) {
4050            CmsMessageContainer message = Messages.get().container(Messages.ERR_ACCESSING_POOL_1, dbPoolUrl);
4051            throw new CmsDbException(message, exc);
4052        }
4053
4054        CmsMessageContainer message = Messages.get().container(Messages.ERR_UNKNOWN_POOL_URL_1, dbPoolUrl);
4055        throw new CmsDbException(message);
4056    }
4057
4058    /**
4059     * Returns the lock state of a resource.<p>
4060     *
4061     * @param dbc the current database context
4062     * @param resource the resource to return the lock state for
4063     *
4064     * @return the lock state of the resource
4065     *
4066     * @throws CmsException if something goes wrong
4067     */
4068    public CmsLock getLock(CmsDbContext dbc, CmsResource resource) throws CmsException {
4069
4070        return m_lockManager.getLock(dbc, resource);
4071    }
4072
4073    /**
4074     * Returns all locked resources in a given folder.<p>
4075     *
4076     * @param dbc the current database context
4077     * @param resource the folder to search in
4078     * @param filter the lock filter
4079     *
4080     * @return a list of locked resource paths (relative to current site)
4081     *
4082     * @throws CmsException if the current project is locked
4083     */
4084    public List<String> getLockedResources(CmsDbContext dbc, CmsResource resource, CmsLockFilter filter)
4085    throws CmsException {
4086
4087        List<String> lockedResources = new ArrayList<String>();
4088        // get locked resources
4089        Iterator<CmsLock> it = m_lockManager.getLocks(dbc, resource.getRootPath(), filter).iterator();
4090        while (it.hasNext()) {
4091            CmsLock lock = it.next();
4092            lockedResources.add(dbc.removeSiteRoot(lock.getResourceName()));
4093        }
4094        Collections.sort(lockedResources);
4095        return lockedResources;
4096    }
4097
4098    /**
4099     * Returns all locked resources in a given folder.<p>
4100     *
4101     * @param dbc the current database context
4102     * @param resource the folder to search in
4103     * @param filter the lock filter
4104     *
4105     * @return a list of locked resources
4106     *
4107     * @throws CmsException if the current project is locked
4108     */
4109    public List<CmsResource> getLockedResourcesObjects(CmsDbContext dbc, CmsResource resource, CmsLockFilter filter)
4110    throws CmsException {
4111
4112        return m_lockManager.getLockedResources(dbc, resource, filter);
4113    }
4114
4115    /**
4116     * Returns all locked resources in a given folder, but uses a cache for resource lookups.<p>
4117     *
4118     * @param dbc the current database context
4119     * @param resource the folder to search in
4120     * @param filter the lock filter
4121     * @param cache the cache to use for resource lookups
4122     *
4123     * @return a list of locked resources
4124     *
4125     * @throws CmsException if the current project is locked
4126     */
4127    public List<CmsResource> getLockedResourcesObjectsWithCache(
4128        CmsDbContext dbc,
4129        CmsResource resource,
4130        CmsLockFilter filter,
4131        Map<String, CmsResource> cache) throws CmsException {
4132
4133        return m_lockManager.getLockedResourcesWithCache(dbc, resource, filter, cache);
4134    }
4135
4136    /**
4137     * Returns all log entries matching the given filter.<p>
4138     *
4139     * @param dbc the current db context
4140     * @param filter the filter to match the log entries
4141     *
4142     * @return all log entries matching the given filter
4143     *
4144     * @throws CmsException if something goes wrong
4145     *
4146     * @see CmsSecurityManager#getLogEntries(CmsRequestContext, CmsLogFilter)
4147     */
4148    public List<CmsLogEntry> getLogEntries(CmsDbContext dbc, CmsLogFilter filter) throws CmsException {
4149
4150        updateLog(dbc);
4151        return m_projectDriver.readLog(dbc, filter);
4152    }
4153
4154    /**
4155     * Returns the next publish tag for the published historical resources.<p>
4156     *
4157     * @param dbc the current database context
4158     *
4159     * @return the next available publish tag
4160     */
4161    public int getNextPublishTag(CmsDbContext dbc) {
4162
4163        return getHistoryDriver(dbc).readNextPublishTag(dbc);
4164    }
4165
4166    /**
4167     * Returns all child organizational units of the given parent organizational unit including
4168     * hierarchical deeper organization units if needed.<p>
4169     *
4170     * @param dbc the current db context
4171     * @param parent the parent organizational unit, or <code>null</code> for the root
4172     * @param includeChildren if hierarchical deeper organization units should also be returned
4173     *
4174     * @return a list of <code>{@link CmsOrganizationalUnit}</code> objects
4175     *
4176     * @throws CmsException if operation was not successful
4177     *
4178     * @see org.opencms.security.CmsOrgUnitManager#getOrganizationalUnits(CmsObject, String, boolean)
4179     */
4180    public List<CmsOrganizationalUnit> getOrganizationalUnits(
4181        CmsDbContext dbc,
4182        CmsOrganizationalUnit parent,
4183        boolean includeChildren) throws CmsException {
4184
4185        if (parent == null) {
4186            throw new CmsIllegalArgumentException(Messages.get().container(Messages.ERR_PARENT_ORGUNIT_NULL_0));
4187        }
4188        return getUserDriver(dbc).getOrganizationalUnits(dbc, parent, includeChildren);
4189    }
4190
4191    /**
4192     * Returns all the organizational units for which the current user has the given role.<p>
4193     *
4194     * @param dbc the current database context
4195     * @param role the role to check
4196     * @param includeSubOus if sub organizational units should be included in the search
4197     *
4198     * @return a list of {@link org.opencms.security.CmsOrganizationalUnit} objects
4199     *
4200     * @throws CmsException if something goes wrong
4201     */
4202    public List<CmsOrganizationalUnit> getOrgUnitsForRole(CmsDbContext dbc, CmsRole role, boolean includeSubOus)
4203    throws CmsException {
4204
4205        String ouFqn = role.getOuFqn();
4206        if (ouFqn == null) {
4207            ouFqn = "";
4208            role = role.forOrgUnit("");
4209        }
4210        CmsOrganizationalUnit ou = readOrganizationalUnit(dbc, ouFqn);
4211        List<CmsOrganizationalUnit> orgUnits = new ArrayList<CmsOrganizationalUnit>();
4212        if (m_securityManager.hasRole(dbc, dbc.currentUser(), role)) {
4213            orgUnits.add(ou);
4214        }
4215        if (includeSubOus) {
4216            Iterator<CmsOrganizationalUnit> it = getOrganizationalUnits(dbc, ou, true).iterator();
4217            while (it.hasNext()) {
4218                CmsOrganizationalUnit orgUnit = it.next();
4219                if (m_securityManager.hasRole(dbc, dbc.currentUser(), role.forOrgUnit(orgUnit.getName()))) {
4220                    orgUnits.add(orgUnit);
4221                }
4222            }
4223        }
4224        return orgUnits;
4225    }
4226
4227    /**
4228     * Returns the parent group of a group.<p>
4229     *
4230     * @param dbc the current database context
4231     * @param groupname the name of the group
4232     *
4233     * @return group the parent group or <code>null</code>
4234     *
4235     * @throws CmsException if operation was not successful
4236     */
4237    public CmsGroup getParent(CmsDbContext dbc, String groupname) throws CmsException {
4238
4239        CmsGroup group = readGroup(dbc, groupname);
4240        if (group.getParentId().isNullUUID()) {
4241            return null;
4242        }
4243
4244        // try to read from cache
4245        CmsGroup parent = m_monitor.getCachedGroup(group.getParentId().toString());
4246        if (parent == null) {
4247            parent = getUserDriver(dbc).readGroup(dbc, group.getParentId());
4248            m_monitor.cacheGroup(parent);
4249        }
4250        return parent;
4251    }
4252
4253    /**
4254     * Returns the set of permissions of the current user for a given resource.<p>
4255     *
4256     * @param dbc the current database context
4257     * @param resource the resource
4258     * @param user the user
4259     *
4260     * @return bit set with allowed permissions
4261     *
4262     * @throws CmsException if something goes wrong
4263     */
4264    public CmsPermissionSetCustom getPermissions(CmsDbContext dbc, CmsResource resource, CmsUser user)
4265    throws CmsException {
4266
4267        CmsAccessControlList acList = getAccessControlList(dbc, resource, false);
4268        return acList.getPermissions(user, getGroupsOfUser(dbc, user.getName(), false), getRolesForUser(dbc, user));
4269    }
4270
4271    /**
4272     * Returns the project driver.<p>
4273     *
4274     * @return the project driver
4275     */
4276    public I_CmsProjectDriver getProjectDriver() {
4277
4278        return m_projectDriver;
4279    }
4280
4281    /**
4282     * Returns the project driver for a given DB context.<p>
4283     *
4284     * @param dbc the database context
4285     *
4286     * @return the project driver for the database context
4287     */
4288    public I_CmsProjectDriver getProjectDriver(CmsDbContext dbc) {
4289
4290        if ((dbc == null) || (dbc.getProjectId() == null) || dbc.getProjectId().isNullUUID()) {
4291            return m_projectDriver;
4292        }
4293        I_CmsProjectDriver driver = dbc.getProjectDriver(dbc.getProjectId());
4294        return driver != null ? driver : m_projectDriver;
4295    }
4296
4297    /**
4298     * Returns either the project driver for the DB context (if it has one) or a default project driver.<p>
4299     *
4300     * @param dbc the DB context
4301     * @param defaultDriver the driver which should be returned if there is no project driver for the DB context
4302     *
4303     * @return either the project driver for the DB context, or the default driver
4304     */
4305    public I_CmsProjectDriver getProjectDriver(CmsDbContext dbc, I_CmsProjectDriver defaultDriver) {
4306
4307        if ((dbc == null) || (dbc.getProjectId() == null) || dbc.getProjectId().isNullUUID()) {
4308            return defaultDriver;
4309        }
4310        I_CmsProjectDriver driver = dbc.getProjectDriver(dbc.getProjectId());
4311        return driver != null ? driver : defaultDriver;
4312    }
4313
4314    /**
4315     * Returns the uuid id for the given id.<p>
4316     *
4317     * TODO: remove this method as soon as possible
4318     *
4319     * @param dbc the current database context
4320     * @param id the old project id
4321     *
4322     * @return the new uuid for the given id
4323     *
4324     * @throws CmsException if something goes wrong
4325     */
4326    public CmsUUID getProjectId(CmsDbContext dbc, int id) throws CmsException {
4327
4328        Iterator<CmsProject> itProjects = getAllAccessibleProjects(
4329            dbc,
4330            readOrganizationalUnit(dbc, ""),
4331            true).iterator();
4332        while (itProjects.hasNext()) {
4333            CmsProject project = itProjects.next();
4334            if (project.getUuid().hashCode() == id) {
4335                return project.getUuid();
4336            }
4337        }
4338        return null;
4339    }
4340
4341    /**
4342     * Returns the configuration read from the <code>opencms.properties</code> file.<p>
4343     *
4344     * @return the configuration read from the <code>opencms.properties</code> file
4345     */
4346    public CmsParameterConfiguration getPropertyConfiguration() {
4347
4348        return m_propertyConfiguration;
4349    }
4350
4351    /**
4352     * Returns a new publish list that contains the unpublished resources related
4353     * to all resources in the given publish list, the related resources exclude
4354     * all resources in the given publish list and also locked (by other users) resources.<p>
4355     *
4356     * @param dbc the current database context
4357     * @param publishList the publish list to exclude from result
4358     * @param filter the relation filter to use to get the related resources
4359     *
4360     * @return a new publish list that contains the related resources
4361     *
4362     * @throws CmsException if something goes wrong
4363     *
4364     * @see org.opencms.publish.CmsPublishManager#getRelatedResourcesToPublish(CmsObject, CmsPublishList)
4365     */
4366    public CmsPublishList getRelatedResourcesToPublish(
4367        CmsDbContext dbc,
4368        CmsPublishList publishList,
4369        CmsRelationFilter filter) throws CmsException {
4370
4371        Map<String, CmsResource> relations = new HashMap<String, CmsResource>();
4372
4373        // check if progress should be set in the thread
4374        A_CmsProgressThread thread = null;
4375        if (Thread.currentThread() instanceof A_CmsProgressThread) {
4376            thread = (A_CmsProgressThread)Thread.currentThread();
4377        }
4378
4379        // get all resources to publish
4380        List<CmsResource> publishResources = publishList.getAllResources();
4381        Iterator<CmsResource> itCheckList = publishResources.iterator();
4382        // iterate over them
4383        int count = 0;
4384        while (itCheckList.hasNext()) {
4385
4386            // set progress in thread
4387            count++;
4388            if (thread != null) {
4389
4390                if (thread.isInterrupted()) {
4391                    throw new CmsIllegalStateException(
4392                        org.opencms.workplace.commons.Messages.get().container(
4393                            org.opencms.workplace.commons.Messages.ERR_PROGRESS_INTERRUPTED_0));
4394                }
4395                thread.setProgress((count * 20) / publishResources.size());
4396                thread.setDescription(
4397                    org.opencms.workplace.commons.Messages.get().getBundle().key(
4398                        org.opencms.workplace.commons.Messages.GUI_PROGRESS_PUBLISH_STEP1_2,
4399                        new Integer(count),
4400                        new Integer(publishResources.size())));
4401            }
4402
4403            CmsResource checkResource = itCheckList.next();
4404            // get and iterate over all related resources
4405            Iterator<CmsRelation> itRelations = getRelationsForResource(dbc, checkResource, filter).iterator();
4406            while (itRelations.hasNext()) {
4407                CmsRelation relation = itRelations.next();
4408                try {
4409                    // get the target of the relation, see CmsRelation#getTarget(CmsObject, CmsResourceFilter)
4410                    CmsResource target;
4411                    try {
4412                        // first look up by id
4413                        target = readResource(dbc, relation.getTargetId(), CmsResourceFilter.ALL);
4414                    } catch (CmsVfsResourceNotFoundException e) {
4415                        // then look up by name, but from the root site
4416                        String storedSiteRoot = dbc.getRequestContext().getSiteRoot();
4417                        try {
4418                            dbc.getRequestContext().setSiteRoot("");
4419                            target = readResource(dbc, relation.getTargetPath(), CmsResourceFilter.ALL);
4420                        } finally {
4421                            dbc.getRequestContext().setSiteRoot(storedSiteRoot);
4422                        }
4423                    }
4424                    CmsLock lock = getLock(dbc, target);
4425                    // just add resources that may come in question
4426                    if (!publishResources.contains(target) // is not in the original list
4427                        && !relations.containsKey(target.getRootPath()) // has not been already added by another relation
4428                        && !target.getState().isUnchanged() // has been changed
4429                        && lock.isLockableBy(dbc.currentUser())) { // is lockable by current user
4430
4431                        relations.put(target.getRootPath(), target);
4432                        // now check the folder structure
4433                        CmsResource parent = getVfsDriver(dbc).readParentFolder(
4434                            dbc,
4435                            dbc.currentProject().getUuid(),
4436                            target.getStructureId());
4437                        while ((parent != null) && parent.getState().isNew()) {
4438                            // just add resources that may come in question
4439                            if (!publishResources.contains(parent) // is not in the original list
4440                                && !relations.containsKey(parent.getRootPath())) { // has not been already added by another relation
4441
4442                                relations.put(parent.getRootPath(), parent);
4443                            }
4444                            parent = getVfsDriver(dbc).readParentFolder(
4445                                dbc,
4446                                dbc.currentProject().getUuid(),
4447                                parent.getStructureId());
4448                        }
4449                    }
4450                } catch (CmsVfsResourceNotFoundException e) {
4451                    // ignore broken links
4452                    if (LOG.isDebugEnabled()) {
4453                        LOG.debug(e.getLocalizedMessage(), e);
4454                    }
4455                }
4456            }
4457        }
4458
4459        CmsPublishList ret = new CmsPublishList(publishList.getDirectPublishResources(), false, false);
4460        ret.addAll(relations.values(), false);
4461        ret.initialize();
4462        return ret;
4463    }
4464
4465    /**
4466     * Returns all relations for the given resource matching the given filter.<p>
4467     *
4468     * @param dbc the current db context
4469     * @param resource the resource to retrieve the relations for
4470     * @param filter the filter to match the relation
4471     *
4472     * @return all relations for the given resource matching the given filter
4473     *
4474     * @throws CmsException if something goes wrong
4475     *
4476     * @see CmsSecurityManager#getRelationsForResource(CmsRequestContext, CmsResource, CmsRelationFilter)
4477     */
4478    public List<CmsRelation> getRelationsForResource(CmsDbContext dbc, CmsResource resource, CmsRelationFilter filter)
4479    throws CmsException {
4480
4481        CmsUUID projectId = getProjectIdForContext(dbc);
4482        return getVfsDriver(dbc).readRelations(dbc, projectId, resource, filter);
4483    }
4484
4485    /**
4486     * Returns the list of organizational units the given resource belongs to.<p>
4487     *
4488     * @param dbc the current database context
4489     * @param resource the resource
4490     *
4491     * @return list of {@link CmsOrganizationalUnit} objects
4492     *
4493     * @throws CmsException if something goes wrong
4494     */
4495    public List<CmsOrganizationalUnit> getResourceOrgUnits(CmsDbContext dbc, CmsResource resource) throws CmsException {
4496
4497        List<CmsOrganizationalUnit> result = getVfsDriver(dbc).getResourceOus(
4498            dbc,
4499            dbc.currentProject().getUuid(),
4500            resource);
4501
4502        return result;
4503    }
4504
4505    /**
4506     * Returns all resources of the given organizational unit.<p>
4507     *
4508     * @param dbc the current db context
4509     * @param orgUnit the organizational unit to get all resources for
4510     *
4511     * @return all <code>{@link CmsResource}</code> objects in the organizational unit
4512     *
4513     * @throws CmsException if operation was not successful
4514     *
4515     * @see org.opencms.security.CmsOrgUnitManager#getResourcesForOrganizationalUnit(CmsObject, String)
4516     * @see org.opencms.security.CmsOrgUnitManager#getUsers(CmsObject, String, boolean)
4517     * @see org.opencms.security.CmsOrgUnitManager#getGroups(CmsObject, String, boolean)
4518     */
4519    public List<CmsResource> getResourcesForOrganizationalUnit(CmsDbContext dbc, CmsOrganizationalUnit orgUnit)
4520    throws CmsException {
4521
4522        return getUserDriver(dbc).getResourcesForOrganizationalUnit(dbc, orgUnit);
4523    }
4524
4525    /**
4526     * Returns all resources associated to a given principal via an ACE with the given permissions.<p>
4527     *
4528     * If the <code>includeAttr</code> flag is set it returns also all resources associated to
4529     * a given principal through some of following attributes.<p>
4530     *
4531     * <ul>
4532     *    <li>User Created</li>
4533     *    <li>User Last Modified</li>
4534     * </ul><p>
4535     *
4536     * @param dbc the current database context
4537     * @param project the to read the entries from
4538     * @param principalId the id of the principal
4539     * @param permissions a set of permissions to match, can be <code>null</code> for all ACEs
4540     * @param includeAttr a flag to include resources associated by attributes
4541     *
4542     * @return a set of <code>{@link CmsResource}</code> objects
4543     *
4544     * @throws CmsException if something goes wrong
4545     */
4546    public Set<CmsResource> getResourcesForPrincipal(
4547        CmsDbContext dbc,
4548        CmsProject project,
4549        CmsUUID principalId,
4550        CmsPermissionSet permissions,
4551        boolean includeAttr) throws CmsException {
4552
4553        Set<CmsResource> resources = new HashSet<CmsResource>(
4554            getVfsDriver(dbc).readResourcesForPrincipalACE(dbc, project, principalId));
4555        if (permissions != null) {
4556            Iterator<CmsResource> itRes = resources.iterator();
4557            while (itRes.hasNext()) {
4558                CmsAccessControlEntry ace = readAccessControlEntry(dbc, itRes.next(), principalId);
4559                if ((ace.getPermissions().getPermissions()
4560                    & permissions.getPermissions()) != permissions.getPermissions()) {
4561                    // remove if permissions does not match
4562                    itRes.remove();
4563                }
4564            }
4565        }
4566        if (includeAttr) {
4567            resources.addAll(getVfsDriver(dbc).readResourcesForPrincipalAttr(dbc, project, principalId));
4568        }
4569        return resources;
4570    }
4571
4572    /**
4573     * Gets the rewrite aliases matching a given filter.<p>
4574     *
4575     * @param dbc the current database context
4576     * @param filter the filter used for filtering rewrite aliases
4577     *
4578     * @return the rewrite aliases matching the given filter
4579     *
4580     * @throws CmsException if something goes wrong
4581     */
4582    public List<CmsRewriteAlias> getRewriteAliases(CmsDbContext dbc, CmsRewriteAliasFilter filter) throws CmsException {
4583
4584        return getVfsDriver(dbc).readRewriteAliases(dbc, filter);
4585    }
4586
4587    /**
4588     * Collects the groups which constitute a given role.<p>
4589     *
4590     * @param dbc the database context
4591     * @param roleGroupName the group related to the role
4592     * @param directUsersOnly if true, only the group belonging to the entry itself wil
4593     *
4594     * @return the set of groups which constitute the role
4595     *
4596     * @throws CmsException if something goes wrong
4597     */
4598    public Set<CmsGroup> getRoleGroups(CmsDbContext dbc, String roleGroupName, boolean directUsersOnly)
4599    throws CmsException {
4600
4601        return getRoleGroupsImpl(dbc, roleGroupName, directUsersOnly, new HashMap<String, Set<CmsGroup>>());
4602    }
4603
4604    /**
4605     * Collects the groups which constitute a given role.<p>
4606     *
4607     * @param dbc the database context
4608     * @param roleGroupName the group related to the role
4609     * @param directUsersOnly if true, only the group belonging to the entry itself wil
4610     * @param accumulator a map for memoizing return values of recursive calls
4611     *
4612     * @return the set of groups which constitute the role
4613     *
4614     * @throws CmsException if something goes wrong
4615     */
4616    public Set<CmsGroup> getRoleGroupsImpl(
4617        CmsDbContext dbc,
4618        String roleGroupName,
4619        boolean directUsersOnly,
4620        Map<String, Set<CmsGroup>> accumulator) throws CmsException {
4621
4622        Set<CmsGroup> result = new HashSet<CmsGroup>();
4623        if (accumulator.get(roleGroupName) != null) {
4624            return accumulator.get(roleGroupName);
4625        }
4626        CmsGroup group = readGroup(dbc, roleGroupName); // check that the group really exists
4627        if ((group == null) || (!group.isRole())) {
4628            throw new CmsDbEntryNotFoundException(
4629                Messages.get().container(Messages.ERR_UNKNOWN_GROUP_1, roleGroupName));
4630        }
4631        result.add(group);
4632        if (!directUsersOnly) {
4633            CmsRole role = CmsRole.valueOf(group);
4634            if (role.getParentRole() != null) {
4635                try {
4636                    String parentGroup = role.getParentRole().getGroupName();
4637                    // iterate the parent roles
4638                    result.addAll(getRoleGroupsImpl(dbc, parentGroup, directUsersOnly, accumulator));
4639                } catch (CmsDbEntryNotFoundException e) {
4640                    // ignore, this may happen while deleting an orgunit
4641                    if (LOG.isDebugEnabled()) {
4642                        LOG.debug(e.getLocalizedMessage(), e);
4643                    }
4644                }
4645            }
4646            String parentOu = CmsOrganizationalUnit.getParentFqn(group.getOuFqn());
4647            if (parentOu != null) {
4648                // iterate the parent ou's
4649                result.addAll(getRoleGroupsImpl(dbc, parentOu + group.getSimpleName(), directUsersOnly, accumulator));
4650            }
4651        }
4652        accumulator.put(roleGroupName, result);
4653        return result;
4654    }
4655
4656    /**
4657     * Returns all roles the given user has for the given resource.<p>
4658     *
4659     * @param dbc the current database context
4660     * @param user the user to check
4661     * @param resource the resource to check the roles for
4662     *
4663     * @return a list of {@link CmsRole} objects
4664     *
4665     * @throws CmsException if something goes wrong
4666     */
4667    public List<CmsRole> getRolesForResource(CmsDbContext dbc, CmsUser user, CmsResource resource) throws CmsException {
4668
4669        // guest user has no role
4670        if (user.isGuestUser()) {
4671            return Collections.emptyList();
4672        }
4673
4674        // try to read from cache
4675        String key = user.getId().toString() + resource.getRootPath();
4676        List<CmsRole> result = m_monitor.getCachedRoleList(key);
4677        if (result != null) {
4678            return result;
4679        }
4680        result = new ArrayList<CmsRole>();
4681
4682        Iterator<CmsOrganizationalUnit> itOus = getResourceOrgUnits(dbc, resource).iterator();
4683        while (itOus.hasNext()) {
4684            CmsOrganizationalUnit ou = itOus.next();
4685
4686            // read all roles of the current user
4687            List<CmsGroup> groups = new ArrayList<CmsGroup>(
4688                getGroupsOfUser(
4689                    dbc,
4690                    user.getName(),
4691                    ou.getName(),
4692                    false,
4693                    true,
4694                    false,
4695                    dbc.getRequestContext().getRemoteAddress()));
4696            // check the roles applying to the given resource
4697            Iterator<CmsGroup> it = groups.iterator();
4698            while (it.hasNext()) {
4699                CmsGroup group = it.next();
4700                CmsRole givenRole = CmsRole.valueOf(group).forOrgUnit(null);
4701                if (givenRole.isOrganizationalUnitIndependent() || result.contains(givenRole)) {
4702                    // skip already added roles
4703                    continue;
4704                }
4705                result.add(givenRole);
4706            }
4707        }
4708
4709        result = Collections.unmodifiableList(result);
4710        m_monitor.cacheRoleList(key, result);
4711        return result;
4712    }
4713
4714    /**
4715     * Returns all roles the given user has independent of the resource.<p>
4716     *
4717     * @param dbc the current database context
4718     * @param user the user to check
4719     *
4720     * @return a list of {@link CmsRole} objects
4721     *
4722     * @throws CmsException if something goes wrong
4723     */
4724    public List<CmsRole> getRolesForUser(CmsDbContext dbc, CmsUser user) throws CmsException {
4725
4726        // guest user has no role
4727        if (user.isGuestUser()) {
4728            return Collections.emptyList();
4729        }
4730
4731        // try to read from cache
4732        String key = user.getId().toString();
4733        List<CmsRole> result = m_monitor.getCachedRoleList(key);
4734        if (result != null) {
4735            return result;
4736        }
4737        result = new ArrayList<CmsRole>();
4738
4739        // read all roles of the current user
4740        List<CmsGroup> groups = new ArrayList<CmsGroup>(
4741            getGroupsOfUser(dbc, user.getName(), "", true, true, false, dbc.getRequestContext().getRemoteAddress()));
4742
4743        // check the roles applying to the given resource
4744        Iterator<CmsGroup> it = groups.iterator();
4745        while (it.hasNext()) {
4746            CmsGroup group = it.next();
4747            CmsRole givenRole = CmsRole.valueOf(group);
4748            givenRole = givenRole.forOrgUnit(null);
4749            if (!result.contains(givenRole)) {
4750                result.add(givenRole);
4751            }
4752        }
4753        result = Collections.unmodifiableList(result);
4754        m_monitor.cacheRoleList(key, result);
4755        return result;
4756    }
4757
4758    /**
4759     * Returns the security manager this driver manager belongs to.<p>
4760     *
4761     * @return the security manager this driver manager belongs to
4762     */
4763    public CmsSecurityManager getSecurityManager() {
4764
4765        return m_securityManager;
4766    }
4767
4768    /**
4769     * Returns an instance of the common sql manager.<p>
4770     *
4771     * @return an instance of the common sql manager
4772     */
4773    public CmsSqlManager getSqlManager() {
4774
4775        return m_sqlManager;
4776    }
4777
4778    /**
4779     * Returns the subscription driver of this driver manager.<p>
4780     *
4781     * @return a subscription driver
4782     */
4783    public I_CmsSubscriptionDriver getSubscriptionDriver() {
4784
4785        return m_subscriptionDriver;
4786    }
4787
4788    /**
4789     * Returns the user driver.<p>
4790     *
4791     * @return the user driver
4792     */
4793    public I_CmsUserDriver getUserDriver() {
4794
4795        return m_userDriver;
4796    }
4797
4798    /**
4799     * Returns the user driver for a given database context.<p>
4800     *
4801     * @param dbc the database context
4802     *
4803     * @return the user driver for the database context
4804     */
4805    public I_CmsUserDriver getUserDriver(CmsDbContext dbc) {
4806
4807        if ((dbc == null) || (dbc.getProjectId() == null) || dbc.getProjectId().isNullUUID()) {
4808            return m_userDriver;
4809        }
4810        I_CmsUserDriver driver = dbc.getUserDriver(dbc.getProjectId());
4811        return driver != null ? driver : m_userDriver;
4812
4813    }
4814
4815    /**
4816     * Returns either the user driver for the given DB context (if it has one) or a default value instead.<p>
4817     *
4818     * @param dbc the DB context
4819     * @param defaultDriver the driver that should be returned if no driver for the DB context was found
4820     *
4821     * @return either the user driver for the DB context, or <code>defaultDriver</code> if none were found
4822     */
4823    public I_CmsUserDriver getUserDriver(CmsDbContext dbc, I_CmsUserDriver defaultDriver) {
4824
4825        if ((dbc == null) || (dbc.getProjectId() == null) || dbc.getProjectId().isNullUUID()) {
4826            return defaultDriver;
4827        }
4828        I_CmsUserDriver driver = dbc.getUserDriver(dbc.getProjectId());
4829        return driver != null ? driver : defaultDriver;
4830    }
4831
4832    /**
4833     * Returns all direct users of the given organizational unit.<p>
4834     *
4835     * @param dbc the current db context
4836     * @param orgUnit the organizational unit to get all users for
4837     * @param recursive if all groups of sub-organizational units should be retrieved too
4838     *
4839     * @return all <code>{@link CmsUser}</code> objects in the organizational unit
4840     *
4841     * @throws CmsException if operation was not successful
4842     *
4843     * @see org.opencms.security.CmsOrgUnitManager#getResourcesForOrganizationalUnit(CmsObject, String)
4844     * @see org.opencms.security.CmsOrgUnitManager#getUsers(CmsObject, String, boolean)
4845     */
4846    public List<CmsUser> getUsers(CmsDbContext dbc, CmsOrganizationalUnit orgUnit, boolean recursive)
4847    throws CmsException {
4848
4849        return getUserDriver(dbc).getUsers(dbc, orgUnit, recursive);
4850    }
4851
4852    /**
4853     * Returns a list of users in a group.<p>
4854     *
4855     * @param dbc the current database context
4856     * @param groupname the name of the group to list users from
4857     * @param includeOtherOuUsers include users of other organizational units
4858     * @param directUsersOnly if set only the direct assigned users will be returned,
4859     *                        if not also indirect users, ie. members of parent roles,
4860     *                        this parameter only works with roles
4861     * @param readRoles if to read roles or groups
4862     *
4863     * @return all <code>{@link CmsUser}</code> objects in the group
4864     *
4865     * @throws CmsException if operation was not successful
4866     */
4867    public List<CmsUser> getUsersOfGroup(
4868        CmsDbContext dbc,
4869        String groupname,
4870        boolean includeOtherOuUsers,
4871        boolean directUsersOnly,
4872        boolean readRoles) throws CmsException {
4873
4874        return internalUsersOfGroup(
4875            dbc,
4876            CmsOrganizationalUnit.getParentFqn(groupname),
4877            groupname,
4878            includeOtherOuUsers,
4879            directUsersOnly,
4880            readRoles);
4881    }
4882
4883    /**
4884     * Returns the given user's publish list.<p>
4885     *
4886     * @param dbc the database context
4887     * @param userId the user's id
4888     *
4889     * @return the given user's publish list
4890     *
4891     * @throws CmsDataAccessException if something goes wrong
4892     */
4893    public List<CmsResource> getUsersPubList(CmsDbContext dbc, CmsUUID userId) throws CmsDataAccessException {
4894
4895        synchronized (m_publishListUpdateLock) {
4896            updateLog(dbc);
4897            return m_projectDriver.getUsersPubList(dbc, userId);
4898        }
4899    }
4900
4901    /**
4902     * Returns all direct users of the given organizational unit, without their additional info.<p>
4903     *
4904     * @param dbc the current db context
4905     * @param orgUnit the organizational unit to get all users for
4906     * @param recursive if all groups of sub-organizational units should be retrieved too
4907     *
4908     * @return all <code>{@link CmsUser}</code> objects in the organizational unit
4909     *
4910     * @throws CmsException if operation was not successful
4911     *
4912     * @see org.opencms.security.CmsOrgUnitManager#getResourcesForOrganizationalUnit(CmsObject, String)
4913     * @see org.opencms.security.CmsOrgUnitManager#getUsers(CmsObject, String, boolean)
4914     */
4915    public List<CmsUser> getUsersWithoutAdditionalInfo(
4916        CmsDbContext dbc,
4917        CmsOrganizationalUnit orgUnit,
4918        boolean recursive) throws CmsException {
4919
4920        return getUserDriver(dbc).getUsersWithoutAdditionalInfo(dbc, orgUnit, recursive);
4921    }
4922
4923    /**
4924     * Returns the VFS driver.<p>
4925     *
4926     * @return the VFS driver
4927     */
4928    public I_CmsVfsDriver getVfsDriver() {
4929
4930        return m_vfsDriver;
4931    }
4932
4933    /**
4934     * Returns the VFS driver for the given database context.<p>
4935     *
4936     * @param dbc the database context
4937     *
4938     * @return a VFS driver
4939     */
4940    public I_CmsVfsDriver getVfsDriver(CmsDbContext dbc) {
4941
4942        if ((dbc == null) || (dbc.getProjectId() == null) || dbc.getProjectId().isNullUUID()) {
4943            return m_vfsDriver;
4944        }
4945        I_CmsVfsDriver driver = dbc.getVfsDriver(dbc.getProjectId());
4946        return driver != null ? driver : m_vfsDriver;
4947
4948    }
4949
4950    /**
4951     * Writes a vector of access control entries as new access control entries of a given resource.<p>
4952     *
4953     * Already existing access control entries of this resource are removed before.
4954     * Access is granted, if:<p>
4955     * <ul>
4956     * <li>the current user has control permission on the resource</li>
4957     * </ul>
4958     *
4959     * @param dbc the current database context
4960     * @param resource the resource
4961     * @param acEntries a list of <code>{@link CmsAccessControlEntry}</code> objects
4962     *
4963     * @throws CmsException if something goes wrong
4964     */
4965    public void importAccessControlEntries(
4966        CmsDbContext dbc,
4967        CmsResource resource,
4968        List<CmsAccessControlEntry> acEntries) throws CmsException {
4969
4970        I_CmsUserDriver userDriver = getUserDriver(dbc);
4971        userDriver.removeAccessControlEntries(dbc, dbc.currentProject(), resource.getResourceId());
4972
4973        Iterator<CmsAccessControlEntry> i = acEntries.iterator();
4974        while (i.hasNext()) {
4975            userDriver.writeAccessControlEntry(dbc, dbc.currentProject(), i.next());
4976        }
4977        m_monitor.clearAccessControlListCache();
4978    }
4979
4980    /**
4981     * Imports a rewrite alias.<p>
4982     *
4983     * @param dbc the database context
4984     * @param siteRoot the site root of the alias
4985     * @param source the source of the alias
4986     * @param target the target of the alias
4987     * @param mode the alias mode
4988     *
4989     * @return the import result
4990     *
4991     * @throws CmsException if something goes wrong
4992     */
4993    public CmsAliasImportResult importRewriteAlias(
4994        CmsDbContext dbc,
4995        String siteRoot,
4996        String source,
4997        String target,
4998        CmsAliasMode mode) throws CmsException {
4999
5000        I_CmsVfsDriver vfs = getVfsDriver(dbc);
5001        List<CmsRewriteAlias> existingAliases = vfs.readRewriteAliases(
5002            dbc,
5003            new CmsRewriteAliasFilter().setSiteRoot(siteRoot));
5004        CmsUUID idToDelete = null;
5005        for (CmsRewriteAlias alias : existingAliases) {
5006            if (alias.getPatternString().equals(source)) {
5007                idToDelete = alias.getId();
5008            }
5009        }
5010        if (idToDelete != null) {
5011            vfs.deleteRewriteAliases(dbc, new CmsRewriteAliasFilter().setId(idToDelete));
5012        }
5013        CmsRewriteAlias alias = new CmsRewriteAlias(new CmsUUID(), siteRoot, source, target, mode);
5014        List<CmsRewriteAlias> aliases = new ArrayList<CmsRewriteAlias>();
5015        aliases.add(alias);
5016        getVfsDriver(dbc).insertRewriteAliases(dbc, aliases);
5017        CmsAliasImportResult result = new CmsAliasImportResult(
5018            CmsAliasImportStatus.aliasNew,
5019            "OK",
5020            source,
5021            target,
5022            mode);
5023        return result;
5024    }
5025
5026    /**
5027     * Creates a new user by import.<p>
5028     *
5029     * @param dbc the current database context
5030     * @param id the id of the user
5031     * @param name the new name for the user
5032     * @param password the new password for the user (already encrypted)
5033     * @param firstname the firstname of the user
5034     * @param lastname the lastname of the user
5035     * @param email the email of the user
5036     * @param flags the flags for a user (for example <code>{@link I_CmsPrincipal#FLAG_ENABLED}</code>)
5037     * @param dateCreated the creation date
5038     * @param additionalInfos the additional user infos
5039     *
5040     * @return the imported user
5041     *
5042     * @throws CmsException if something goes wrong
5043     */
5044    public CmsUser importUser(
5045        CmsDbContext dbc,
5046        String id,
5047        String name,
5048        String password,
5049        String firstname,
5050        String lastname,
5051        String email,
5052        int flags,
5053        long dateCreated,
5054        Map<String, Object> additionalInfos) throws CmsException {
5055
5056        // no space before or after the name
5057        name = name.trim();
5058        // check the user name
5059        String userName = CmsOrganizationalUnit.getSimpleName(name);
5060        OpenCms.getValidationHandler().checkUserName(userName);
5061        if (CmsStringUtil.isEmptyOrWhitespaceOnly(userName)) {
5062            throw new CmsIllegalArgumentException(Messages.get().container(Messages.ERR_BAD_USER_1, userName));
5063        }
5064        // check the ou
5065        CmsOrganizationalUnit ou = readOrganizationalUnit(dbc, CmsOrganizationalUnit.getParentFqn(name));
5066
5067        // check webuser ou
5068        if (ou.hasFlagWebuser() && ((flags & I_CmsPrincipal.FLAG_USER_WEBUSER) == 0)) {
5069            flags += I_CmsPrincipal.FLAG_USER_WEBUSER;
5070        }
5071        CmsUser newUser = getUserDriver(dbc).createUser(
5072            dbc,
5073            new CmsUUID(id),
5074            name,
5075            password,
5076            firstname,
5077            lastname,
5078            email,
5079            0,
5080            flags,
5081            dateCreated,
5082            additionalInfos);
5083        return newUser;
5084    }
5085
5086    /**
5087     * Increments a counter and returns its value before incrementing.<p>
5088     *
5089     * @param dbc the current database context
5090     * @param name the name of the counter which should be incremented
5091     *
5092     * @return the value of the counter
5093     *
5094     * @throws CmsException if something goes wrong
5095     */
5096    public int incrementCounter(CmsDbContext dbc, String name) throws CmsException {
5097
5098        return getVfsDriver(dbc).incrementCounter(dbc, name);
5099    }
5100
5101    /**
5102     * Initializes the driver and sets up all required modules and connections.<p>
5103     *
5104     * @param configurationManager the configuration manager
5105     * @param dbContextFactory the db context factory
5106     *
5107     * @throws CmsException if something goes wrong
5108     * @throws Exception if something goes wrong
5109     */
5110    public void init(CmsConfigurationManager configurationManager, I_CmsDbContextFactory dbContextFactory)
5111    throws CmsException, Exception {
5112
5113        // initialize the access-module.
5114        if (CmsLog.INIT.isInfoEnabled()) {
5115            CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_DRIVER_MANAGER_START_PHASE4_0));
5116        }
5117        // store local reference to the memory monitor to avoid multiple lookups through the OpenCms singelton
5118        m_monitor = OpenCms.getMemoryMonitor();
5119
5120        CmsSystemConfiguration systemConfiguation = (CmsSystemConfiguration)configurationManager.getConfiguration(
5121            CmsSystemConfiguration.class);
5122        CmsCacheSettings settings = systemConfiguation.getCacheSettings();
5123
5124        // initialize the key generator
5125        m_keyGenerator = (I_CmsCacheKey)Class.forName(settings.getCacheKeyGenerator()).newInstance();
5126
5127        // initialize the HTML link validator
5128        m_htmlLinkValidator = new CmsRelationSystemValidator(this);
5129
5130        // fills the defaults if needed
5131        CmsDbContext dbc1 = dbContextFactory.getDbContext();
5132        getUserDriver().fillDefaults(dbc1);
5133        getProjectDriver().fillDefaults(dbc1);
5134
5135        // set the driver manager in the publish engine
5136        m_publishEngine.setDriverManager(this);
5137        // create the root organizational unit if needed
5138        CmsDbContext dbc2 = dbContextFactory.getDbContext(
5139            new CmsRequestContext(
5140                readUser(dbc1, OpenCms.getDefaultUsers().getUserAdmin()),
5141                readProject(dbc1, CmsProject.ONLINE_PROJECT_ID),
5142                null,
5143                CmsSiteMatcher.DEFAULT_MATCHER,
5144                "",
5145                false,
5146                null,
5147                null,
5148                null,
5149                0,
5150                null,
5151                null,
5152                ""));
5153        dbc1.clear();
5154        getUserDriver().createRootOrganizationalUnit(dbc2);
5155        dbc2.clear();
5156    }
5157
5158    /**
5159     * Initializes the organizational unit.<p>
5160     *
5161     * @param dbc the DB context
5162     * @param ou the organizational unit
5163     */
5164    public void initOrgUnit(CmsDbContext dbc, CmsOrganizationalUnit ou) {
5165
5166        try {
5167            dbc.setAttribute(ATTR_INIT_OU, ou);
5168            m_userDriver.fillDefaults(dbc);
5169        } finally {
5170            dbc.removeAttribute(ATTR_INIT_OU);
5171        }
5172    }
5173
5174    /**
5175     * Checks if the specified resource is inside the current project.<p>
5176     *
5177     * The project "view" is determined by a set of path prefixes.
5178     * If the resource starts with any one of this prefixes, it is considered to
5179     * be "inside" the project.<p>
5180     *
5181     * @param dbc the current database context
5182     * @param resourcename the specified resource name (full path)
5183     *
5184     * @return <code>true</code>, if the specified resource is inside the current project
5185     */
5186    public boolean isInsideCurrentProject(CmsDbContext dbc, String resourcename) {
5187
5188        List<String> projectResources = null;
5189        try {
5190            projectResources = readProjectResources(dbc, dbc.currentProject());
5191        } catch (CmsException e) {
5192            if (LOG.isErrorEnabled()) {
5193                LOG.error(
5194                    Messages.get().getBundle().key(
5195                        Messages.LOG_CHECK_RESOURCE_INSIDE_CURRENT_PROJECT_2,
5196                        resourcename,
5197                        dbc.currentProject().getName()),
5198                    e);
5199            }
5200            return false;
5201        }
5202        return CmsProject.isInsideProject(projectResources, resourcename);
5203    }
5204
5205    /**
5206     * Checks whether the subscription driver is available.<p>
5207     *
5208     * @return true if the subscription driver is available
5209     */
5210    public boolean isSubscriptionDriverAvailable() {
5211
5212        return m_subscriptionDriver != null;
5213    }
5214
5215    /**
5216     * Checks if a project is the tempfile project.<p>
5217     * @param project the project to test
5218     * @return true if the project is the tempfile project
5219     */
5220    public boolean isTempfileProject(CmsProject project) {
5221
5222        return project.getName().equals("tempFileProject");
5223    }
5224
5225    /**
5226     * Checks if one of the resources (except the resource itself)
5227     * is a sibling in a "labeled" site folder.<p>
5228     *
5229     * This method is used when creating a new sibling
5230     * (use the <code>newResource</code> parameter & <code>action = 1</code>)
5231     * or deleting/importing a resource (call with <code>action = 2</code>).<p>
5232     *
5233     * @param dbc the current database context
5234     * @param resource the resource
5235     * @param newResource absolute path for a resource sibling which will be created
5236     * @param action the action which has to be performed (1: create VFS link, 2: all other actions)
5237     *
5238     * @return <code>true</code> if the flag should be set for the resource, otherwise <code>false</code>
5239     *
5240     * @throws CmsDataAccessException if something goes wrong
5241     */
5242    public boolean labelResource(CmsDbContext dbc, CmsResource resource, String newResource, int action)
5243    throws CmsDataAccessException {
5244
5245        // get the list of labeled site folders from the runtime property
5246        List<String> labeledSites = OpenCms.getWorkplaceManager().getLabelSiteFolders();
5247
5248        if (labeledSites.size() == 0) {
5249            // no labeled sites defined, just return false
5250            return false;
5251        }
5252
5253        if (action == 1) {
5254            // CASE 1: a new resource is created, check the sites
5255            if (!resource.isLabeled()) {
5256                // source isn't labeled yet, so check!
5257                boolean linkInside = false;
5258                boolean sourceInside = false;
5259                for (int i = 0; i < labeledSites.size(); i++) {
5260                    String curSite = labeledSites.get(i);
5261                    if (newResource.startsWith(curSite)) {
5262                        // the link lies in a labeled site
5263                        linkInside = true;
5264                    }
5265                    if (resource.getRootPath().startsWith(curSite)) {
5266                        // the source lies in a labeled site
5267                        sourceInside = true;
5268                    }
5269                    if (linkInside && sourceInside) {
5270                        break;
5271                    }
5272                }
5273                // return true when either source or link is in labeled site, otherwise false
5274                return (linkInside != sourceInside);
5275            }
5276            // resource is already labeled
5277            return false;
5278
5279        } else {
5280            // CASE 2: the resource will be deleted or created (import)
5281            // check if at least one of the other siblings resides inside a "labeled site"
5282            // and if at least one of the other siblings resides outside a "labeled site"
5283            boolean isInside = false;
5284            boolean isOutside = false;
5285            // check if one of the other vfs links lies in a labeled site folder
5286            List<CmsResource> siblings = getVfsDriver(dbc).readSiblings(
5287                dbc,
5288                dbc.currentProject().getUuid(),
5289                resource,
5290                false);
5291            updateContextDates(dbc, siblings);
5292            Iterator<CmsResource> i = siblings.iterator();
5293            while (i.hasNext() && (!isInside || !isOutside)) {
5294                CmsResource currentResource = i.next();
5295                if (currentResource.equals(resource)) {
5296                    // dont't check the resource itself!
5297                    continue;
5298                }
5299                String curPath = currentResource.getRootPath();
5300                boolean curInside = false;
5301                for (int k = 0; k < labeledSites.size(); k++) {
5302                    if (curPath.startsWith(labeledSites.get(k))) {
5303                        // the link is in the labeled site
5304                        isInside = true;
5305                        curInside = true;
5306                        break;
5307                    }
5308                }
5309                if (!curInside) {
5310                    // the current link was not found in labeled site, so it is outside
5311                    isOutside = true;
5312                }
5313            }
5314            // now check the new resource name if present
5315            if (newResource != null) {
5316                boolean curInside = false;
5317                for (int k = 0; k < labeledSites.size(); k++) {
5318                    if (newResource.startsWith(labeledSites.get(k))) {
5319                        // the new resource is in the labeled site
5320                        isInside = true;
5321                        curInside = true;
5322                        break;
5323                    }
5324                }
5325                if (!curInside) {
5326                    // the new resource was not found in labeled site, so it is outside
5327                    isOutside = true;
5328                }
5329            }
5330            return (isInside && isOutside);
5331        }
5332    }
5333
5334    /**
5335     * Returns the user, who had locked the resource.<p>
5336     *
5337     * A user can lock a resource, so he is the only one who can write this
5338     * resource. This methods checks, if a resource was locked.
5339     *
5340     * @param dbc the current database context
5341     * @param resource the resource
5342     *
5343     * @return the user, who had locked the resource
5344     *
5345     * @throws CmsException will be thrown, if the user has not the rights for this resource
5346     */
5347    public CmsUser lockedBy(CmsDbContext dbc, CmsResource resource) throws CmsException {
5348
5349        return readUser(dbc, m_lockManager.getLock(dbc, resource).getEditionLock().getUserId());
5350    }
5351
5352    /**
5353     * Locks a resource.<p>
5354     *
5355     * The <code>type</code> parameter controls what kind of lock is used.<br>
5356     * Possible values for this parameter are: <br>
5357     * <ul>
5358     * <li><code>{@link org.opencms.lock.CmsLockType#EXCLUSIVE}</code></li>
5359     * <li><code>{@link org.opencms.lock.CmsLockType#TEMPORARY}</code></li>
5360     * <li><code>{@link org.opencms.lock.CmsLockType#PUBLISH}</code></li>
5361     * </ul><p>
5362     *
5363     * @param dbc the current database context
5364     * @param resource the resource to lock
5365     * @param type type of the lock
5366     *
5367     * @throws CmsException if something goes wrong
5368     *
5369     * @see CmsObject#lockResource(String)
5370     * @see CmsObject#lockResourceTemporary(String)
5371     * @see org.opencms.file.types.I_CmsResourceType#lockResource(CmsObject, CmsSecurityManager, CmsResource, CmsLockType)
5372     */
5373    public void lockResource(CmsDbContext dbc, CmsResource resource, CmsLockType type) throws CmsException {
5374
5375        // update the resource cache
5376        m_monitor.clearResourceCache();
5377
5378        CmsProject project = dbc.currentProject();
5379
5380        // add the resource to the lock dispatcher
5381        m_lockManager.addResource(dbc, resource, dbc.currentUser(), project, type);
5382        boolean changedProjectLastModified = false;
5383        if (!resource.getState().isUnchanged() && !resource.getState().isKeep()) {
5384            // update the project flag of a modified resource as "last modified inside the current project"
5385            getVfsDriver(dbc).writeLastModifiedProjectId(dbc, project, project.getUuid(), resource);
5386            changedProjectLastModified = true;
5387        }
5388
5389        // we must also clear the permission cache
5390        m_monitor.flushCache(CmsMemoryMonitor.CacheType.PERMISSION);
5391
5392        // fire resource modification event
5393        Map<String, Object> data = new HashMap<String, Object>(2);
5394        data.put(I_CmsEventListener.KEY_RESOURCE, resource);
5395        data.put(
5396            I_CmsEventListener.KEY_CHANGE,
5397            new Integer(changedProjectLastModified ? CHANGED_PROJECT : NOTHING_CHANGED));
5398        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_MODIFIED, data));
5399    }
5400
5401    /**
5402     * Adds the given log entry to the current user's log.<p>
5403     *
5404     * This operation works only on memory, to get the log entries actually
5405     * written to DB you have to call the {@link #updateLog(CmsDbContext)} method.<p>
5406     *
5407     * @param dbc the current database context
5408     * @param logEntry the log entry to create
5409     * @param force forces the log entry to be counted,
5410     *              if not only the first log entry in a transaction will be taken into account
5411     */
5412    public void log(CmsDbContext dbc, CmsLogEntry logEntry, boolean force) {
5413
5414        if (dbc == null) {
5415            return;
5416        }
5417        // check log level
5418        if (!logEntry.getType().isActive()) {
5419            // do not log inactive entries
5420            return;
5421        }
5422        // if not forcing
5423        if (!force) {
5424            // operation already logged
5425            boolean abort = (dbc.getAttribute(CmsLogEntry.ATTR_LOG_ENTRY) != null);
5426            // disabled logging from outside
5427            abort |= (dbc.getRequestContext().getAttribute(CmsLogEntry.ATTR_LOG_ENTRY) != null);
5428            if (abort) {
5429                return;
5430            }
5431        }
5432        // prevent several entries for the same operation
5433        dbc.setAttribute(CmsLogEntry.ATTR_LOG_ENTRY, Boolean.TRUE);
5434        // keep it for later
5435        m_log.add(logEntry);
5436    }
5437
5438    /**
5439     * Attempts to authenticate a user into OpenCms with the given password.<p>
5440     *
5441     * @param dbc the current database context
5442     * @param userName the name of the user to be logged in
5443     * @param password the password of the user
5444     * @param remoteAddress the ip address of the request
5445     *
5446     * @return the logged in user
5447     *
5448     * @throws CmsAuthentificationException if the login was not successful
5449     * @throws CmsDataAccessException in case of errors accessing the database
5450     * @throws CmsPasswordEncryptionException in case of errors encrypting the users password
5451     */
5452    public CmsUser loginUser(CmsDbContext dbc, String userName, String password, String remoteAddress)
5453    throws CmsAuthentificationException, CmsDataAccessException, CmsPasswordEncryptionException {
5454
5455        if (CmsStringUtil.isEmptyOrWhitespaceOnly(password)) {
5456            throw new CmsDbEntryNotFoundException(Messages.get().container(Messages.ERR_UNKNOWN_USER_1, userName));
5457        }
5458        CmsUser newUser;
5459        try {
5460            // read the user from the driver to avoid the cache
5461            newUser = getUserDriver(dbc).readUser(dbc, userName, password, remoteAddress);
5462        } catch (CmsDbEntryNotFoundException e) {
5463            // this indicates that the username / password combination does not exist
5464            // any other exception indicates database issues, these are not catched here
5465
5466            // check if a user with this name exists at all
5467            CmsUser user = null;
5468            try {
5469                user = readUser(dbc, userName);
5470            } catch (CmsDataAccessException e2) {
5471                // apparently this user does not exist in the database
5472            }
5473
5474            if (user != null) {
5475                if (dbc.currentUser().isGuestUser()) {
5476                    // add an invalid login attempt for this user to the storage
5477                    OpenCms.getLoginManager().addInvalidLogin(userName, remoteAddress);
5478                }
5479                OpenCms.getLoginManager().checkInvalidLogins(userName, remoteAddress);
5480                throw new CmsAuthentificationException(
5481                    org.opencms.security.Messages.get().container(
5482                        org.opencms.security.Messages.ERR_LOGIN_FAILED_2,
5483                        userName,
5484                        remoteAddress),
5485                    e);
5486            } else {
5487                String userOu = CmsOrganizationalUnit.getParentFqn(userName);
5488                if (userOu != null) {
5489                    String parentOu = CmsOrganizationalUnit.getParentFqn(userOu);
5490                    if (parentOu != null) {
5491                        // try a higher level ou
5492                        String uName = CmsOrganizationalUnit.getSimpleName(userName);
5493                        return loginUser(dbc, parentOu + uName, password, remoteAddress);
5494                    }
5495                }
5496                throw new CmsAuthentificationException(
5497                    org.opencms.security.Messages.get().container(
5498                        org.opencms.security.Messages.ERR_LOGIN_FAILED_NO_USER_2,
5499                        userName,
5500                        remoteAddress),
5501                    e);
5502            }
5503        }
5504        // check if the "enabled" flag is set for the user
5505        if (!newUser.isEnabled()) {
5506            // user is disabled, throw a securiy exception
5507            throw new CmsAuthentificationException(
5508                org.opencms.security.Messages.get().container(
5509                    org.opencms.security.Messages.ERR_LOGIN_FAILED_DISABLED_2,
5510                    userName,
5511                    remoteAddress));
5512        }
5513
5514        if (dbc.currentUser().isGuestUser()) {
5515            // check if this account is temporarily disabled because of too many invalid login attempts
5516            // this will throw an exception if the test fails
5517            OpenCms.getLoginManager().checkInvalidLogins(userName, remoteAddress);
5518            // test successful, remove all previous invalid login attempts for this user from the storage
5519            OpenCms.getLoginManager().removeInvalidLogins(userName, remoteAddress);
5520        }
5521
5522        if (!m_securityManager.hasRole(
5523            dbc,
5524            newUser,
5525            CmsRole.ADMINISTRATOR.forOrgUnit(dbc.getRequestContext().getOuFqn()))) {
5526            // new user is not Administrator, check if login is currently allowed
5527            OpenCms.getLoginManager().checkLoginAllowed();
5528        }
5529        m_monitor.clearUserCache(newUser);
5530        // set the last login time to the current time
5531        newUser.setLastlogin(System.currentTimeMillis());
5532        dbc.setAttribute(ATTRIBUTE_LOGIN, newUser.getName());
5533        // write the changed user object back to the user driver
5534        Map<String, Object> additionalInfosForRepositories = OpenCms.getRepositoryManager().getAdditionalInfoForLogin(
5535            newUser.getName(),
5536            password);
5537        newUser.getAdditionalInfo().putAll(additionalInfosForRepositories);
5538        String lastPasswordChange = (String)newUser.getAdditionalInfo(
5539            CmsUserSettings.ADDITIONAL_INFO_LAST_PASSWORD_CHANGE);
5540        if (lastPasswordChange == null) {
5541            newUser.getAdditionalInfo().put(
5542                CmsUserSettings.ADDITIONAL_INFO_LAST_PASSWORD_CHANGE,
5543                "" + System.currentTimeMillis());
5544        }
5545        getUserDriver(dbc).writeUser(dbc, newUser);
5546        // check if we need to update the password
5547        if (!OpenCms.getPasswordHandler().checkPassword(password, newUser.getPassword(), false)
5548            && OpenCms.getPasswordHandler().checkPassword(password, newUser.getPassword(), true)) {
5549            // the password does not check with the current hash algorithm but with the fall back, update the password
5550            getUserDriver(dbc).writePassword(dbc, userName, password, password);
5551        }
5552
5553        // update cache
5554        m_monitor.cacheUser(newUser);
5555
5556        // invalidate all user dependent caches
5557        m_monitor.flushCache(
5558            CmsMemoryMonitor.CacheType.ACL,
5559            CmsMemoryMonitor.CacheType.GROUP,
5560            CmsMemoryMonitor.CacheType.ORG_UNIT,
5561            CmsMemoryMonitor.CacheType.USERGROUPS,
5562            CmsMemoryMonitor.CacheType.USER_LIST,
5563            CmsMemoryMonitor.CacheType.PERMISSION,
5564            CmsMemoryMonitor.CacheType.RESOURCE_LIST);
5565
5566        // fire user modified event
5567        Map<String, Object> eventData = new HashMap<String, Object>();
5568        eventData.put(I_CmsEventListener.KEY_USER_ID, newUser.getId().toString());
5569        eventData.put(I_CmsEventListener.KEY_USER_NAME, newUser.getName());
5570        eventData.put(I_CmsEventListener.KEY_USER_ACTION, I_CmsEventListener.VALUE_USER_MODIFIED_ACTION_WRITE_USER);
5571        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_USER_MODIFIED, eventData));
5572
5573        // return the user object read from the driver
5574        return newUser;
5575    }
5576
5577    /**
5578     * Lookup and read the user or group with the given UUID.<p>
5579     *
5580     * @param dbc the current database context
5581     * @param principalId the UUID of the principal to lookup
5582     *
5583     * @return the principal (group or user) if found, otherwise <code>null</code>
5584     */
5585    public I_CmsPrincipal lookupPrincipal(CmsDbContext dbc, CmsUUID principalId) {
5586
5587        try {
5588            CmsGroup group = getUserDriver(dbc).readGroup(dbc, principalId);
5589            if (group != null) {
5590                return group;
5591            }
5592        } catch (Exception e) {
5593            // ignore this exception
5594        }
5595
5596        try {
5597            CmsUser user = readUser(dbc, principalId);
5598            if (user != null) {
5599                return user;
5600            }
5601        } catch (Exception e) {
5602            // ignore this exception
5603        }
5604
5605        return null;
5606    }
5607
5608    /**
5609     * Lookup and read the user or group with the given name.<p>
5610     *
5611     * @param dbc the current database context
5612     * @param principalName the name of the principal to lookup
5613     *
5614     * @return the principal (group or user) if found, otherwise <code>null</code>
5615     */
5616    public I_CmsPrincipal lookupPrincipal(CmsDbContext dbc, String principalName) {
5617
5618        try {
5619            CmsGroup group = getUserDriver(dbc).readGroup(dbc, principalName);
5620            if (group != null) {
5621                return group;
5622            }
5623        } catch (Exception e) {
5624            // ignore this exception
5625        }
5626
5627        try {
5628            CmsUser user = readUser(dbc, principalName);
5629            if (user != null) {
5630                return user;
5631            }
5632        } catch (Exception e) {
5633            // ignore this exception
5634        }
5635
5636        return null;
5637    }
5638
5639    /**
5640     * Mark the given resource as visited by the user.<p>
5641     *
5642     * @param dbc the database context
5643     * @param poolName the name of the database pool to use
5644     * @param resource the resource to mark as visited
5645     * @param user the user that visited the resource
5646     *
5647     * @throws CmsException if something goes wrong
5648     */
5649    public void markResourceAsVisitedBy(CmsDbContext dbc, String poolName, CmsResource resource, CmsUser user)
5650    throws CmsException {
5651
5652        getSubscriptionDriver().markResourceAsVisitedBy(dbc, poolName, resource, user);
5653    }
5654
5655    /**
5656     * Moves a resource.<p>
5657     *
5658     * You must ensure that the parent of the destination path is an absolute, valid and
5659     * existing VFS path. Relative paths from the source are not supported.<p>
5660     *
5661     * The moved resource will always be locked to the current user
5662     * after the move operation.<p>
5663     *
5664     * In case the target resource already exists, it will be overwritten with the
5665     * source resource if possible.<p>
5666     *
5667     * @param dbc the current database context
5668     * @param source the resource to move
5669     * @param destination the name of the move destination with complete path
5670     * @param internal if set nothing more than the path is modified
5671     *
5672     * @throws CmsException if something goes wrong
5673     *
5674     * @see CmsSecurityManager#moveResource(CmsRequestContext, CmsResource, String)
5675     */
5676    public void moveResource(CmsDbContext dbc, CmsResource source, String destination, boolean internal)
5677    throws CmsException {
5678
5679        CmsFolder destinationFolder = readFolder(dbc, CmsResource.getParentFolder(destination), CmsResourceFilter.ALL);
5680        m_securityManager.checkPermissions(
5681            dbc,
5682            destinationFolder,
5683            CmsPermissionSet.ACCESS_WRITE,
5684            false,
5685            CmsResourceFilter.ALL);
5686
5687        if (source.isFolder()) {
5688            m_monitor.flushCache(CmsMemoryMonitor.CacheType.HAS_ROLE, CmsMemoryMonitor.CacheType.ROLE_LIST);
5689        }
5690        getVfsDriver(dbc).moveResource(dbc, dbc.getRequestContext().getCurrentProject().getUuid(), source, destination);
5691
5692        if (!internal) {
5693            CmsResourceState newState = CmsResource.STATE_CHANGED;
5694            if (source.getState().isNew()) {
5695                newState = CmsResource.STATE_NEW;
5696            } else if (source.getState().isDeleted()) {
5697                newState = CmsResource.STATE_DELETED;
5698            }
5699            source.setState(newState);
5700            // safe since this operation always uses the ids instead of the resource path
5701            getVfsDriver(dbc).writeResourceState(
5702                dbc,
5703                dbc.currentProject(),
5704                source,
5705                CmsDriverManager.UPDATE_STRUCTURE_STATE,
5706                false);
5707            // log it
5708            log(
5709                dbc,
5710                new CmsLogEntry(
5711                    dbc,
5712                    source.getStructureId(),
5713                    CmsLogEntryType.RESOURCE_MOVED,
5714                    new String[] {source.getRootPath(), destination}),
5715                false);
5716        }
5717
5718        CmsResource destRes = readResource(dbc, destination, CmsResourceFilter.ALL);
5719        // move lock
5720        m_lockManager.moveResource(source.getRootPath(), destRes.getRootPath());
5721
5722        // flush all relevant caches
5723        m_monitor.clearAccessControlListCache();
5724        m_monitor.flushCache(
5725            CmsMemoryMonitor.CacheType.PROPERTY,
5726            CmsMemoryMonitor.CacheType.PROPERTY_LIST,
5727            CmsMemoryMonitor.CacheType.PROJECT_RESOURCES);
5728
5729        List<CmsResource> resources = new ArrayList<CmsResource>(4);
5730        // source
5731        resources.add(source);
5732        try {
5733            resources.add(readFolder(dbc, CmsResource.getParentFolder(source.getRootPath()), CmsResourceFilter.ALL));
5734        } catch (Exception e) {
5735            if (LOG.isDebugEnabled()) {
5736                LOG.debug(e);
5737            }
5738        }
5739        // destination
5740        resources.add(destRes);
5741        resources.add(destinationFolder);
5742
5743        // fire the events
5744        OpenCms.fireCmsEvent(
5745            new CmsEvent(
5746                I_CmsEventListener.EVENT_RESOURCE_MOVED,
5747                Collections.<String, Object> singletonMap(I_CmsEventListener.KEY_RESOURCES, resources)));
5748    }
5749
5750    /**
5751     * Moves a resource to the "lost and found" folder.<p>
5752     *
5753     * The method can also be used to check get the name of a resource
5754     * in the "lost and found" folder only without actually moving the
5755     * the resource. To do this, the <code>returnNameOnly</code> flag
5756     * must be set to <code>true</code>.<p>
5757     *
5758     * @param dbc the current database context
5759     * @param resource the resource to apply this operation to
5760     * @param returnNameOnly if <code>true</code>, only the name of the resource in the "lost and found"
5761     *        folder is returned, the move operation is not really performed
5762     *
5763     * @return the name of the resource inside the "lost and found" folder
5764     *
5765     * @throws CmsException if something goes wrong
5766     * @throws CmsIllegalArgumentException if the <code>resourcename</code> argument is null or of length 0
5767     *
5768     * @see CmsObject#moveToLostAndFound(String)
5769     * @see CmsObject#getLostAndFoundName(String)
5770     */
5771    public String moveToLostAndFound(CmsDbContext dbc, CmsResource resource, boolean returnNameOnly)
5772    throws CmsException, CmsIllegalArgumentException {
5773
5774        String resourcename = dbc.removeSiteRoot(resource.getRootPath());
5775
5776        String siteRoot = dbc.getRequestContext().getSiteRoot();
5777        dbc.getRequestContext().setSiteRoot("");
5778        String destination = CmsDriverManager.LOST_AND_FOUND_FOLDER + resourcename;
5779        // create the required folders if necessary
5780        try {
5781            // collect all folders...
5782            String folderPath = CmsResource.getParentFolder(destination);
5783            folderPath = folderPath.substring(1, folderPath.length() - 1); // cut out leading and trailing '/'
5784            Iterator<String> folders = CmsStringUtil.splitAsList(folderPath, '/').iterator();
5785            // ...now create them....
5786            folderPath = "/";
5787            while (folders.hasNext()) {
5788                folderPath += folders.next().toString() + "/";
5789                try {
5790                    readFolder(dbc, folderPath, CmsResourceFilter.IGNORE_EXPIRATION);
5791                } catch (Exception e1) {
5792                    if (returnNameOnly) {
5793                        // we can use the original name without risk, and we do not need to recreate the parent folders
5794                        break;
5795                    }
5796                    // the folder is not existing, so create it
5797                    createResource(
5798                        dbc,
5799                        folderPath,
5800                        CmsResourceTypeFolder.RESOURCE_TYPE_ID,
5801                        null,
5802                        new ArrayList<CmsProperty>());
5803                }
5804            }
5805            // check if this resource name does already exist
5806            // if so add a postfix to the name
5807            String des = destination;
5808            int postfix = 1;
5809            boolean found = true;
5810            while (found) {
5811                try {
5812                    // try to read the file.....
5813                    found = true;
5814                    readResource(dbc, des, CmsResourceFilter.ALL);
5815                    // ....it's there, so add a postfix and try again
5816                    String path = destination.substring(0, destination.lastIndexOf('/') + 1);
5817                    String filename = destination.substring(destination.lastIndexOf('/') + 1, destination.length());
5818
5819                    des = path;
5820
5821                    if (filename.lastIndexOf('.') > 0) {
5822                        des += filename.substring(0, filename.lastIndexOf('.'));
5823                    } else {
5824                        des += filename;
5825                    }
5826                    des += "_" + postfix;
5827                    if (filename.lastIndexOf('.') > 0) {
5828                        des += filename.substring(filename.lastIndexOf('.'), filename.length());
5829                    }
5830                    postfix++;
5831                } catch (CmsException e3) {
5832                    // the file does not exist, so we can use this filename
5833                    found = false;
5834                }
5835            }
5836            destination = des;
5837
5838            if (!returnNameOnly) {
5839                // do not use the move semantic here! to prevent links pointing to the lost & found folder
5840                copyResource(dbc, resource, destination, CmsResource.COPY_AS_SIBLING);
5841                deleteResource(dbc, resource, CmsResource.DELETE_PRESERVE_SIBLINGS);
5842            }
5843        } catch (CmsException e2) {
5844            throw e2;
5845        } finally {
5846            // set the site root to the old value again
5847            dbc.getRequestContext().setSiteRoot(siteRoot);
5848        }
5849        return destination;
5850    }
5851
5852    /**
5853     * Gets a new driver instance.<p>
5854     *
5855     * @param dbc the database context
5856     * @param configurationManager the configuration manager
5857     * @param driverName the driver name
5858     * @param successiveDrivers the list of successive drivers
5859     *
5860     * @return the driver object
5861     * @throws CmsInitException if the selected driver could not be initialized
5862     */
5863    public Object newDriverInstance(
5864        CmsDbContext dbc,
5865        CmsConfigurationManager configurationManager,
5866        String driverName,
5867        List<String> successiveDrivers) throws CmsInitException {
5868
5869        Class<?> driverClass = null;
5870        I_CmsDriver driver = null;
5871
5872        try {
5873            // try to get the class
5874            driverClass = Class.forName(driverName);
5875            if (CmsLog.INIT.isInfoEnabled()) {
5876                CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_DRIVER_START_1, driverName));
5877            }
5878
5879            // try to create a instance
5880            driver = (I_CmsDriver)driverClass.newInstance();
5881            if (CmsLog.INIT.isInfoEnabled()) {
5882                CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_DRIVER_INITIALIZING_1, driverName));
5883            }
5884
5885            // invoke the init-method of this access class
5886            driver.init(dbc, configurationManager, successiveDrivers, this);
5887            if (CmsLog.INIT.isInfoEnabled()) {
5888                CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_DRIVER_INIT_FINISHED_0));
5889            }
5890
5891        } catch (Throwable t) {
5892            CmsMessageContainer message = Messages.get().container(
5893                Messages.ERR_ERROR_INITIALIZING_DRIVER_1,
5894                driverName);
5895            if (LOG.isErrorEnabled()) {
5896                LOG.error(message.key(), t);
5897            }
5898            throw new CmsInitException(message, t);
5899        }
5900
5901        return driver;
5902    }
5903
5904    /**
5905     * Method to create a new instance of a driver.<p>
5906     *
5907     * @param configuration the configurations from the propertyfile
5908     * @param driverName the class name of the driver
5909     * @param driverPoolUrl the pool url for the driver
5910     * @return an initialized instance of the driver
5911     * @throws CmsException if something goes wrong
5912     */
5913    public Object newDriverInstance(CmsParameterConfiguration configuration, String driverName, String driverPoolUrl)
5914    throws CmsException {
5915
5916        Class<?>[] initParamClasses = {CmsParameterConfiguration.class, String.class, CmsDriverManager.class};
5917        Object[] initParams = {configuration, driverPoolUrl, this};
5918
5919        Class<?> driverClass = null;
5920        Object driver = null;
5921
5922        try {
5923            // try to get the class
5924            driverClass = Class.forName(driverName);
5925            if (CmsLog.INIT.isInfoEnabled()) {
5926                CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_DRIVER_START_1, driverName));
5927            }
5928
5929            // try to create a instance
5930            driver = driverClass.newInstance();
5931            if (CmsLog.INIT.isInfoEnabled()) {
5932                CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_DRIVER_INITIALIZING_1, driverName));
5933            }
5934
5935            // invoke the init-method of this access class
5936            driver.getClass().getMethod("init", initParamClasses).invoke(driver, initParams);
5937            if (CmsLog.INIT.isInfoEnabled()) {
5938                CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_DRIVER_INIT_FINISHED_1, driverPoolUrl));
5939            }
5940
5941        } catch (Exception exc) {
5942
5943            CmsMessageContainer message = Messages.get().container(Messages.ERR_INIT_DRIVER_MANAGER_1);
5944            if (LOG.isFatalEnabled()) {
5945                LOG.fatal(message.key(), exc);
5946            }
5947            throw new CmsDbException(message, exc);
5948
5949        }
5950
5951        return driver;
5952    }
5953
5954    /**
5955     * Method to create a new instance of a pool.<p>
5956     *
5957     * @param configuration the configurations from the propertyfile
5958     * @param poolName the configuration name of the pool
5959     *
5960     * @throws CmsInitException if the pools could not be initialized
5961     */
5962    public void newPoolInstance(CmsParameterConfiguration configuration, String poolName) throws CmsInitException {
5963
5964        PoolingDriver driver;
5965
5966        try {
5967            driver = CmsDbPool.createDriverManagerConnectionPool(configuration, poolName);
5968        } catch (Exception e) {
5969
5970            CmsMessageContainer message = Messages.get().container(Messages.ERR_INIT_CONN_POOL_1, poolName);
5971            if (LOG.isErrorEnabled()) {
5972                LOG.error(message.key(), e);
5973            }
5974            throw new CmsInitException(message, e);
5975        }
5976
5977        m_connectionPools.add(driver);
5978    }
5979
5980    /**
5981     * Publishes the given publish job.<p>
5982     *
5983     * @param cms the cms context
5984     * @param dbc the db context
5985     * @param publishList the list of resources to publish
5986     * @param report the report to write to
5987     *
5988     * @throws CmsException if something goes wrong
5989     */
5990    public void publishJob(CmsObject cms, CmsDbContext dbc, CmsPublishList publishList, I_CmsReport report)
5991    throws CmsException {
5992
5993        try {
5994            // check state and lock
5995            List<CmsResource> allResources = new ArrayList<CmsResource>(publishList.getFolderList());
5996            allResources.addAll(publishList.getDeletedFolderList());
5997            allResources.addAll(publishList.getFileList());
5998            Iterator<CmsResource> itResources = allResources.iterator();
5999            while (itResources.hasNext()) {
6000                CmsResource resource = itResources.next();
6001                try {
6002                    resource = readResource(dbc, resource.getStructureId(), CmsResourceFilter.ALL);
6003                } catch (CmsVfsResourceNotFoundException e) {
6004                    continue;
6005                }
6006                if (resource.getState().isUnchanged()) {
6007                    // remove files that were published by a concurrent job
6008                    if (LOG.isDebugEnabled()) {
6009                        LOG.debug(
6010                            Messages.get().getBundle().key(
6011                                Messages.RPT_PUBLISH_REMOVED_RESOURCE_1,
6012                                dbc.removeSiteRoot(resource.getRootPath())));
6013                    }
6014                    publishList.remove(resource);
6015                    unlockResource(dbc, resource, true, true);
6016                    continue;
6017                }
6018                CmsLock lock = m_lockManager.getLock(dbc, resource, false);
6019                if (!lock.getSystemLock().isPublish()) {
6020                    // remove files that are not locked for publishing
6021                    if (LOG.isDebugEnabled()) {
6022                        LOG.debug(
6023                            Messages.get().getBundle().key(
6024                                Messages.RPT_PUBLISH_REMOVED_RESOURCE_1,
6025                                dbc.removeSiteRoot(resource.getRootPath())));
6026                    }
6027                    publishList.remove(resource);
6028                    continue;
6029                }
6030            }
6031
6032            CmsProject onlineProject = readProject(dbc, CmsProject.ONLINE_PROJECT_ID);
6033
6034            // clear the cache
6035            m_monitor.clearCache();
6036
6037            int publishTag = getNextPublishTag(dbc);
6038            getProjectDriver(dbc).publishProject(dbc, report, onlineProject, publishList, publishTag);
6039
6040            // iterate the initialized module action instances
6041            Iterator<String> i = OpenCms.getModuleManager().getModuleNames().iterator();
6042            while (i.hasNext()) {
6043                CmsModule module = OpenCms.getModuleManager().getModule(i.next());
6044                if ((module != null) && (module.getActionInstance() != null)) {
6045                    module.getActionInstance().publishProject(cms, publishList, publishTag, report);
6046                }
6047            }
6048
6049            boolean temporaryProject = (cms.getRequestContext().getCurrentProject().getType() == CmsProject.PROJECT_TYPE_TEMPORARY);
6050            // the project was stored in the history tables for history
6051            // it will be deleted if the project_flag is PROJECT_TYPE_TEMPORARY
6052            if ((temporaryProject) && (!publishList.isDirectPublish())) {
6053                try {
6054                    getProjectDriver(dbc).deleteProject(dbc, dbc.currentProject());
6055                } catch (CmsException e) {
6056                    LOG.error(
6057                        Messages.get().getBundle().key(
6058                            Messages.LOG_DELETE_TEMP_PROJECT_FAILED_1,
6059                            cms.getRequestContext().getCurrentProject().getName()));
6060                }
6061                // if project was temporary set context to online project
6062                cms.getRequestContext().setCurrentProject(onlineProject);
6063            }
6064        } finally {
6065            // clear the cache again
6066            m_monitor.clearCache();
6067        }
6068    }
6069
6070    /**
6071     * Publishes the resources of a specified publish list.<p>
6072     *
6073     * @param cms the current request context
6074     * @param dbc the current database context
6075     * @param publishList a publish list
6076     * @param report an instance of <code>{@link I_CmsReport}</code> to print messages
6077     *
6078     * @throws CmsException if something goes wrong
6079     *
6080     * @see #fillPublishList(CmsDbContext, CmsPublishList)
6081     */
6082    public synchronized void publishProject(
6083        CmsObject cms,
6084        CmsDbContext dbc,
6085        CmsPublishList publishList,
6086        I_CmsReport report) throws CmsException {
6087
6088        // check the parent folders
6089        checkParentFolders(dbc, publishList);
6090        ensureSubResourcesOfMovedFoldersPublished(cms, dbc, publishList);
6091        OpenCms.getPublishManager().getPublishListVerifier().checkPublishList(publishList);
6092
6093        try {
6094            // fire an event that a project is to be published
6095            Map<String, Object> eventData = new HashMap<String, Object>();
6096            eventData.put(I_CmsEventListener.KEY_REPORT, report);
6097            eventData.put(I_CmsEventListener.KEY_PUBLISHLIST, publishList);
6098            eventData.put(I_CmsEventListener.KEY_PROJECTID, dbc.currentProject().getUuid());
6099            eventData.put(I_CmsEventListener.KEY_DBCONTEXT, dbc);
6100            CmsEvent beforePublishEvent = new CmsEvent(I_CmsEventListener.EVENT_BEFORE_PUBLISH_PROJECT, eventData);
6101            OpenCms.fireCmsEvent(beforePublishEvent);
6102        } catch (Throwable t) {
6103            if (report != null) {
6104                report.addError(t);
6105                report.println(t);
6106            }
6107            if (LOG.isErrorEnabled()) {
6108                LOG.error(t.getLocalizedMessage(), t);
6109            }
6110        }
6111
6112        // lock all resources with the special publish lock
6113        Iterator<CmsResource> itResources = new ArrayList<CmsResource>(publishList.getAllResources()).iterator();
6114        while (itResources.hasNext()) {
6115            CmsResource resource = itResources.next();
6116            CmsLock lock = m_lockManager.getLock(dbc, resource, false);
6117            if (lock.getSystemLock().isUnlocked() && lock.isLockableBy(dbc.currentUser())) {
6118                if (getLock(dbc, resource).getEditionLock().isNullLock()) {
6119                    lockResource(dbc, resource, CmsLockType.PUBLISH);
6120                } else {
6121                    changeLock(dbc, resource, CmsLockType.PUBLISH);
6122                }
6123            } else if (lock.getSystemLock().isPublish()) {
6124                if (LOG.isWarnEnabled()) {
6125                    LOG.warn(
6126                        Messages.get().getBundle().key(
6127                            Messages.RPT_PUBLISH_REMOVED_RESOURCE_1,
6128                            dbc.removeSiteRoot(resource.getRootPath())));
6129                }
6130                // remove files that are already waiting to be published
6131                publishList.remove(resource);
6132                continue;
6133            } else {
6134                // this is needed to fix TestPublishIsssues#testPublishScenarioE
6135                changeLock(dbc, resource, CmsLockType.PUBLISH);
6136            }
6137            // now re-check the lock state
6138            lock = m_lockManager.getLock(dbc, resource, false);
6139            if (!lock.getSystemLock().isPublish()) {
6140                if (report != null) {
6141                    report.println(
6142                        Messages.get().container(
6143                            Messages.RPT_PUBLISH_REMOVED_RESOURCE_1,
6144                            dbc.removeSiteRoot(resource.getRootPath())),
6145                        I_CmsReport.FORMAT_WARNING);
6146                }
6147                if (LOG.isWarnEnabled()) {
6148                    LOG.warn(
6149                        Messages.get().getBundle().key(
6150                            Messages.RPT_PUBLISH_REMOVED_RESOURCE_1,
6151                            dbc.removeSiteRoot(resource.getRootPath())));
6152                }
6153                // remove files that could not be locked
6154                publishList.remove(resource);
6155            }
6156        }
6157
6158        // enqueue the publish job
6159        CmsException enqueueException = null;
6160        try {
6161            m_publishEngine.enqueuePublishJob(cms, publishList, report);
6162        } catch (CmsException exc) {
6163            enqueueException = exc;
6164        }
6165
6166        // if an exception was raised, remove the publish locks
6167        // and throw the exception again
6168        if (enqueueException != null) {
6169            itResources = publishList.getAllResources().iterator();
6170            while (itResources.hasNext()) {
6171                CmsResource resource = itResources.next();
6172                CmsLock lock = m_lockManager.getLock(dbc, resource, false);
6173                if (lock.getSystemLock().isPublish()
6174                    && lock.getSystemLock().isOwnedInProjectBy(
6175                        cms.getRequestContext().getCurrentUser(),
6176                        cms.getRequestContext().getCurrentProject())) {
6177                    unlockResource(dbc, resource, true, true);
6178                }
6179            }
6180
6181            throw enqueueException;
6182        }
6183    }
6184
6185    /**
6186     * Transfers the new URL name mappings (if any) for a given resource to the online project.<p>
6187     *
6188     * @param dbc the current database context
6189     * @param res the resource whose new URL name mappings should be transferred to the online project
6190     *
6191     * @throws CmsDataAccessException if something goes wrong
6192     */
6193    public void publishUrlNameMapping(CmsDbContext dbc, CmsResource res) throws CmsDataAccessException {
6194
6195        I_CmsVfsDriver vfsDriver = getVfsDriver(dbc);
6196
6197        if (res.getState().isDeleted()) {
6198            // remove both offline and online mappings
6199            CmsUrlNameMappingFilter idFilter = CmsUrlNameMappingFilter.ALL.filterStructureId(res.getStructureId());
6200            vfsDriver.deleteUrlNameMappingEntries(dbc, true, idFilter);
6201            vfsDriver.deleteUrlNameMappingEntries(dbc, false, idFilter);
6202        } else {
6203            // copy the new entries to the online table
6204            List<CmsUrlNameMappingEntry> entries = vfsDriver.readUrlNameMappingEntries(
6205                dbc,
6206                false,
6207                CmsUrlNameMappingFilter.ALL.filterStructureId(res.getStructureId()).filterStates(
6208                    CmsUrlNameMappingEntry.MAPPING_STATUS_NEW,
6209                    CmsUrlNameMappingEntry.MAPPING_STATUS_REPLACE_ON_PUBLISH));
6210
6211            boolean isReplaceOnPublish = false;
6212            for (CmsUrlNameMappingEntry entry : entries) {
6213                isReplaceOnPublish |= entry.getState() == CmsUrlNameMappingEntry.MAPPING_STATUS_REPLACE_ON_PUBLISH;
6214            }
6215
6216            if (!entries.isEmpty()) {
6217
6218                long now = System.currentTimeMillis();
6219                if (isReplaceOnPublish) {
6220                    vfsDriver.deleteUrlNameMappingEntries(
6221                        dbc,
6222                        true,
6223                        CmsUrlNameMappingFilter.ALL.filterStructureId(res.getStructureId()));
6224                    vfsDriver.deleteUrlNameMappingEntries(
6225                        dbc,
6226                        false,
6227                        CmsUrlNameMappingFilter.ALL.filterStructureId(res.getStructureId()));
6228                }
6229
6230                for (CmsUrlNameMappingEntry entry : entries) {
6231                    CmsUrlNameMappingFilter nameFilter = CmsUrlNameMappingFilter.ALL.filterName(entry.getName());
6232                    if (!isReplaceOnPublish) { // we already handled the other case above
6233                        vfsDriver.deleteUrlNameMappingEntries(dbc, true, nameFilter);
6234                        vfsDriver.deleteUrlNameMappingEntries(dbc, false, nameFilter);
6235                    }
6236                }
6237                for (CmsUrlNameMappingEntry entry : entries) {
6238                    CmsUrlNameMappingEntry newEntry = new CmsUrlNameMappingEntry(
6239                        entry.getName(),
6240                        entry.getStructureId(),
6241                        entry.getState() == CmsUrlNameMappingEntry.MAPPING_STATUS_NEW
6242                        ? CmsUrlNameMappingEntry.MAPPING_STATUS_PUBLISHED
6243                        : CmsUrlNameMappingEntry.MAPPING_STATUS_REPLACE_ON_PUBLISH_PUBLISHED,
6244                        now,
6245                        entry.getLocale());
6246                    vfsDriver.addUrlNameMappingEntry(dbc, true, newEntry);
6247                    vfsDriver.addUrlNameMappingEntry(dbc, false, newEntry);
6248                }
6249            }
6250        }
6251    }
6252
6253    /**
6254     * Reads an access control entry from the cms.<p>
6255     *
6256     * The access control entries of a resource are readable by everyone.
6257     *
6258     * @param dbc the current database context
6259     * @param resource the resource
6260     * @param principal the id of a group or a user any other entity
6261     * @return an access control entry that defines the permissions of the entity for the given resource
6262     * @throws CmsException if something goes wrong
6263     */
6264    public CmsAccessControlEntry readAccessControlEntry(CmsDbContext dbc, CmsResource resource, CmsUUID principal)
6265    throws CmsException {
6266
6267        return getUserDriver(dbc).readAccessControlEntry(
6268            dbc,
6269            dbc.currentProject(),
6270            resource.getResourceId(),
6271            principal);
6272    }
6273
6274    /**
6275     * Finds the alias with a given path.<p>
6276     *
6277     * If no alias is found, null is returned.<p>
6278     *
6279     * @param dbc the current database context
6280     * @param project the current project
6281     * @param siteRoot the site root
6282     * @param path the path of the alias
6283     *
6284     * @return the alias with the given path
6285     *
6286     * @throws CmsException if something goes wrong
6287     */
6288
6289    public CmsAlias readAliasByPath(CmsDbContext dbc, CmsProject project, String siteRoot, String path)
6290    throws CmsException {
6291
6292        List<CmsAlias> aliases = getVfsDriver(dbc).readAliases(dbc, project, new CmsAliasFilter(siteRoot, path, null));
6293        if (aliases.isEmpty()) {
6294            return null;
6295        } else {
6296            return aliases.get(0);
6297        }
6298    }
6299
6300    /**
6301     * Reads the aliases for a given site root.<p>
6302     *
6303     * @param dbc the current database context
6304     * @param currentProject the current project
6305     * @param siteRoot the site root
6306     *
6307     * @return the list of aliases for the given site root
6308     *
6309     * @throws CmsException if something goes wrong
6310     */
6311    public List<CmsAlias> readAliasesBySite(CmsDbContext dbc, CmsProject currentProject, String siteRoot)
6312    throws CmsException {
6313
6314        return getVfsDriver(dbc).readAliases(dbc, currentProject, new CmsAliasFilter(siteRoot, null, null));
6315    }
6316
6317    /**
6318     * Reads the aliases which point to a given structure id.<p>
6319     *
6320     * @param dbc the current database context
6321     * @param project the current project
6322     * @param structureId the structure id for which we want to read the aliases
6323     *
6324     * @return the list of aliases pointing to the structure id
6325     * @throws CmsException if something goes wrong
6326     */
6327    public List<CmsAlias> readAliasesByStructureId(CmsDbContext dbc, CmsProject project, CmsUUID structureId)
6328    throws CmsException {
6329
6330        return getVfsDriver(dbc).readAliases(dbc, project, new CmsAliasFilter(null, null, structureId));
6331    }
6332
6333    /**
6334     * Reads all versions of the given resource.<br>
6335     *
6336     * This method returns a list with the history of the given resource, i.e.
6337     * the historical resource entries, independent of the project they were attached to.<br>
6338     *
6339     * The reading excludes the file content.<p>
6340     *
6341     * @param dbc the current database context
6342     * @param resource the resource to read the history for
6343     *
6344     * @return a list of file headers, as <code>{@link I_CmsHistoryResource}</code> objects
6345     *
6346     * @throws CmsException if something goes wrong
6347     */
6348    public List<I_CmsHistoryResource> readAllAvailableVersions(CmsDbContext dbc, CmsResource resource)
6349    throws CmsException {
6350
6351        // read the historical resources
6352        List<I_CmsHistoryResource> versions = getHistoryDriver(dbc).readAllAvailableVersions(
6353            dbc,
6354            resource.getStructureId());
6355        if ((versions.size() > OpenCms.getSystemInfo().getHistoryVersions())
6356            && (OpenCms.getSystemInfo().getHistoryVersions() > -1)) {
6357            return versions.subList(0, OpenCms.getSystemInfo().getHistoryVersions());
6358        }
6359        return versions;
6360    }
6361
6362    /**
6363     * Reads all property definitions for the given mapping type.<p>
6364     *
6365     * @param dbc the current database context
6366     *
6367     * @return a list with the <code>{@link CmsPropertyDefinition}</code> objects (may be empty)
6368     *
6369     * @throws CmsException if something goes wrong
6370     */
6371    public List<CmsPropertyDefinition> readAllPropertyDefinitions(CmsDbContext dbc) throws CmsException {
6372
6373        List<CmsPropertyDefinition> result = getVfsDriver(dbc).readPropertyDefinitions(
6374            dbc,
6375            dbc.currentProject().getUuid());
6376        Collections.sort(result);
6377        return result;
6378    }
6379
6380    /**
6381     * Returns all resources subscribed by the given user or group.<p>
6382     *
6383     * @param dbc the database context
6384     * @param poolName the name of the database pool to use
6385     * @param principal the principal to read the subscribed resources
6386     *
6387     * @return all resources subscribed by the given user or group
6388     *
6389     * @throws CmsException if something goes wrong
6390     */
6391    public List<CmsResource> readAllSubscribedResources(CmsDbContext dbc, String poolName, CmsPrincipal principal)
6392    throws CmsException {
6393
6394        List<CmsResource> result = getSubscriptionDriver().readAllSubscribedResources(dbc, poolName, principal);
6395        result = filterPermissions(dbc, result, CmsResourceFilter.DEFAULT);
6396        return result;
6397    }
6398
6399    /**
6400     * Selects the best url name for a given resource and locale.<p>
6401     *
6402     * @param dbc the database context
6403     * @param id the resource's structure id
6404     * @param locale the requested locale
6405     * @param defaultLocales the default locales to use if the locale isn't available
6406     *
6407     * @return the URL name which was found
6408     *
6409     * @throws CmsDataAccessException if the database operation failed
6410     */
6411    public String readBestUrlName(CmsDbContext dbc, CmsUUID id, Locale locale, List<Locale> defaultLocales)
6412    throws CmsDataAccessException {
6413
6414        List<CmsUrlNameMappingEntry> entries = getVfsDriver(dbc).readUrlNameMappingEntries(
6415            dbc,
6416            dbc.currentProject().isOnlineProject(),
6417            CmsUrlNameMappingFilter.ALL.filterStructureId(id));
6418        if (entries.isEmpty()) {
6419            return null;
6420        }
6421
6422        ArrayListMultimap<String, CmsUrlNameMappingEntry> entriesByLocale = ArrayListMultimap.create();
6423        for (CmsUrlNameMappingEntry entry : entries) {
6424            entriesByLocale.put(entry.getLocale(), entry);
6425        }
6426        List<CmsUrlNameMappingEntry> lastEntries = new ArrayList<CmsUrlNameMappingEntry>();
6427        Comparator<CmsUrlNameMappingEntry> dateChangedComparator = new UrlNameMappingComparator();
6428        for (String localeKey : entriesByLocale.keySet()) {
6429            // for each locale select the latest mapping entry
6430            CmsUrlNameMappingEntry latestEntryForLocale = Collections.max(
6431                entriesByLocale.get(localeKey),
6432                dateChangedComparator);
6433            lastEntries.add(latestEntryForLocale);
6434        }
6435        CmsLocaleManager localeManager = OpenCms.getLocaleManager();
6436        List<Locale> availableLocales = new ArrayList<Locale>();
6437        for (CmsUrlNameMappingEntry entry : lastEntries) {
6438            availableLocales.add(CmsLocaleManager.getLocale(entry.getLocale()));
6439        }
6440        Locale bestLocale = localeManager.getBestMatchingLocale(locale, defaultLocales, availableLocales);
6441        String bestLocaleStr = bestLocale.toString();
6442        for (CmsUrlNameMappingEntry entry : lastEntries) {
6443            if (entry.getLocale().equals(bestLocaleStr)) {
6444                return entry.getName();
6445            }
6446        }
6447        return null;
6448    }
6449
6450    /**
6451     * Returns the child resources of a resource, that is the resources
6452     * contained in a folder.<p>
6453     *
6454     * With the parameters <code>getFolders</code> and <code>getFiles</code>
6455     * you can control what type of resources you want in the result list:
6456     * files, folders, or both.<p>
6457     *
6458     * This method is mainly used by the workplace explorer.<p>
6459     *
6460     * @param dbc the current database context
6461     * @param resource the resource to return the child resources for
6462     * @param filter the resource filter to use
6463     * @param getFolders if true the child folders are included in the result
6464     * @param getFiles if true the child files are included in the result
6465     * @param checkPermissions if the resources should be filtered with the current user permissions
6466     *
6467     * @return a list of all child resources
6468     *
6469     * @throws CmsException if something goes wrong
6470     */
6471    public List<CmsResource> readChildResources(
6472        CmsDbContext dbc,
6473        CmsResource resource,
6474        CmsResourceFilter filter,
6475        boolean getFolders,
6476        boolean getFiles,
6477        boolean checkPermissions) throws CmsException {
6478
6479        String cacheKey = null;
6480        List<CmsResource> resourceList = null;
6481        if (m_monitor.isEnabled(CmsMemoryMonitor.CacheType.RESOURCE_LIST)) { // check this here to skip the complex cache key generation
6482            String time = "";
6483            if (checkPermissions) {
6484                // ensure correct caching if site time offset is set
6485                if ((dbc.getRequestContext() != null)
6486                    && (OpenCms.getSiteManager().getSiteForSiteRoot(dbc.getRequestContext().getSiteRoot()) != null)) {
6487                    time += OpenCms.getSiteManager().getSiteForSiteRoot(
6488                        dbc.getRequestContext().getSiteRoot()).getSiteMatcher().getTimeOffset();
6489                }
6490            }
6491            // try to get the sub resources from the cache
6492            cacheKey = getCacheKey(
6493                new String[] {
6494                    dbc.currentUser().getName(),
6495                    getFolders
6496                    ? (getFiles ? CmsCacheKey.CACHE_KEY_SUBALL : CmsCacheKey.CACHE_KEY_SUBFOLDERS)
6497                    : CmsCacheKey.CACHE_KEY_SUBFILES,
6498                    checkPermissions ? "+" + time : "-",
6499                    filter.getCacheId(),
6500                    resource.getRootPath()},
6501                dbc);
6502
6503            resourceList = m_monitor.getCachedResourceList(cacheKey);
6504        }
6505        if ((resourceList == null) || !dbc.getProjectId().isNullUUID()) {
6506            // read the result form the database
6507            resourceList = getVfsDriver(dbc).readChildResources(
6508                dbc,
6509                dbc.currentProject(),
6510                resource,
6511                getFolders,
6512                getFiles);
6513
6514            if (checkPermissions) {
6515                // apply the permission filter
6516                resourceList = filterPermissions(dbc, resourceList, filter);
6517            }
6518            // cache the sub resources
6519            if (dbc.getProjectId().isNullUUID()) {
6520                m_monitor.cacheResourceList(cacheKey, resourceList);
6521            }
6522        }
6523
6524        // we must always apply the result filter and update the context dates
6525        return updateContextDates(dbc, resourceList, filter);
6526    }
6527
6528    /**
6529     * Returns the default file for the given folder.<p>
6530     *
6531     * If the given resource is a file, then this file is returned.<p>
6532     *
6533     * Otherwise, in case of a folder:<br>
6534     * <ol>
6535     *   <li>the {@link CmsPropertyDefinition#PROPERTY_DEFAULT_FILE} is checked, and
6536     *   <li>if still no file could be found, the configured default files in the
6537     *       <code>opencms-vfs.xml</code> configuration are iterated until a match is
6538     *       found, and
6539     *   <li>if still no file could be found, <code>null</code> is retuned
6540     * </ol>
6541     *
6542     * @param dbc the database context
6543     * @param resource the folder to get the default file for
6544     * @param resourceFilter the resource filter
6545     *
6546     * @return the default file for the given folder
6547     */
6548    public CmsResource readDefaultFile(CmsDbContext dbc, CmsResource resource, CmsResourceFilter resourceFilter) {
6549
6550        // resource exists, lets check if we have a file or a folder
6551        if (resource.isFolder()) {
6552            // the resource is a folder, check if PROPERTY_DEFAULT_FILE is set on folder
6553            try {
6554                String defaultFileName = readPropertyObject(
6555                    dbc,
6556                    resource,
6557                    CmsPropertyDefinition.PROPERTY_DEFAULT_FILE,
6558                    false).getValue();
6559                // check if the default file property does not match the navigation level folder marker value
6560                if ((defaultFileName != null) && !CmsJspNavBuilder.NAVIGATION_LEVEL_FOLDER.equals(defaultFileName)) {
6561                    // property was set, so look up this file first
6562                    String folderName = CmsResource.getFolderPath(resource.getRootPath());
6563                    resource = readResource(dbc, folderName + defaultFileName, resourceFilter.addRequireFile());
6564                }
6565            } catch (CmsException e) {
6566                // ignore all other exceptions and continue the lookup process
6567                if (LOG.isDebugEnabled()) {
6568                    LOG.debug(e.getLocalizedMessage(), e);
6569                }
6570            }
6571            if (resource.isFolder()) {
6572                String folderName = CmsResource.getFolderPath(resource.getRootPath());
6573                // resource is (still) a folder, check default files specified in configuration
6574                Iterator<String> it = OpenCms.getDefaultFiles().iterator();
6575                while (it.hasNext()) {
6576                    String tmpResourceName = folderName + it.next();
6577                    try {
6578                        resource = readResource(dbc, tmpResourceName, resourceFilter.addRequireFile());
6579                        // no exception? So we have found the default file
6580                        // stop looking for default files
6581                        break;
6582                    } catch (CmsException e) {
6583                        // ignore all other exceptions and continue the lookup process
6584                        if (LOG.isDebugEnabled()) {
6585                            LOG.debug(e.getLocalizedMessage(), e);
6586                        }
6587                    }
6588                }
6589            }
6590        }
6591        if (resource.isFolder()) {
6592            // we only want files as a result for further processing
6593            resource = null;
6594        }
6595        return resource;
6596    }
6597
6598    /**
6599     * Reads all deleted (historical) resources below the given path,
6600     * including the full tree below the path, if required.<p>
6601     *
6602     * @param dbc the current db context
6603     * @param resource the parent resource to read the resources from
6604     * @param readTree <code>true</code> to read all subresources
6605     * @param isVfsManager <code>true</code> if the current user has the vfs manager role
6606     *
6607     * @return a list of <code>{@link I_CmsHistoryResource}</code> objects
6608     *
6609     * @throws CmsException if something goes wrong
6610     *
6611     * @see CmsObject#readResource(CmsUUID, int)
6612     * @see CmsObject#readResources(String, CmsResourceFilter, boolean)
6613     * @see CmsObject#readDeletedResources(String, boolean)
6614     */
6615    public List<I_CmsHistoryResource> readDeletedResources(
6616        CmsDbContext dbc,
6617        CmsResource resource,
6618        boolean readTree,
6619        boolean isVfsManager) throws CmsException {
6620
6621        Set<I_CmsHistoryResource> result = new HashSet<I_CmsHistoryResource>();
6622        List<I_CmsHistoryResource> deletedResources;
6623        dbc.getRequestContext().setAttribute("ATTR_RESOURCE_NAME", resource.getRootPath());
6624        try {
6625            deletedResources = getHistoryDriver(dbc).readDeletedResources(
6626                dbc,
6627                resource.getStructureId(),
6628                isVfsManager ? null : dbc.currentUser().getId());
6629        } finally {
6630            dbc.getRequestContext().removeAttribute("ATTR_RESOURCE_NAME");
6631        }
6632        result.addAll(deletedResources);
6633        Set<I_CmsHistoryResource> newResult = new HashSet<I_CmsHistoryResource>(result.size());
6634        I_CmsVfsDriver vfsDriver = getVfsDriver(dbc);
6635        Iterator<I_CmsHistoryResource> it = result.iterator();
6636        while (it.hasNext()) {
6637            I_CmsHistoryResource histRes = it.next();
6638            // adjust the paths
6639            try {
6640                if (vfsDriver.validateStructureIdExists(
6641                    dbc,
6642                    dbc.currentProject().getUuid(),
6643                    histRes.getStructureId())) {
6644                    newResult.add(histRes);
6645                    continue;
6646                }
6647                // adjust the path in case of deleted files
6648                String resourcePath = histRes.getRootPath();
6649                String resName = CmsResource.getName(resourcePath);
6650                String path = CmsResource.getParentFolder(resourcePath);
6651
6652                CmsUUID parentId = histRes.getParentId();
6653                try {
6654                    // first look for the path through the parent id
6655                    path = readResource(dbc, parentId, CmsResourceFilter.IGNORE_EXPIRATION).getRootPath();
6656                } catch (CmsDataAccessException e) {
6657                    // if the resource with the parent id is not found, try to get a new parent id with the path
6658                    try {
6659                        parentId = readResource(dbc, path, CmsResourceFilter.IGNORE_EXPIRATION).getStructureId();
6660                    } catch (CmsDataAccessException e1) {
6661                        // ignore, the parent folder has been completely deleted
6662                    }
6663                }
6664                resourcePath = path + resName;
6665
6666                boolean isFolder = resourcePath.endsWith("/");
6667                if (isFolder) {
6668                    newResult.add(
6669                        new CmsHistoryFolder(
6670                            histRes.getPublishTag(),
6671                            histRes.getStructureId(),
6672                            histRes.getResourceId(),
6673                            resourcePath,
6674                            histRes.getTypeId(),
6675                            histRes.getFlags(),
6676                            histRes.getProjectLastModified(),
6677                            histRes.getState(),
6678                            histRes.getDateCreated(),
6679                            histRes.getUserCreated(),
6680                            histRes.getDateLastModified(),
6681                            histRes.getUserLastModified(),
6682                            histRes.getDateReleased(),
6683                            histRes.getDateExpired(),
6684                            histRes.getVersion(),
6685                            parentId,
6686                            histRes.getResourceVersion(),
6687                            histRes.getStructureVersion()));
6688                } else {
6689                    newResult.add(
6690                        new CmsHistoryFile(
6691                            histRes.getPublishTag(),
6692                            histRes.getStructureId(),
6693                            histRes.getResourceId(),
6694                            resourcePath,
6695                            histRes.getTypeId(),
6696                            histRes.getFlags(),
6697                            histRes.getProjectLastModified(),
6698                            histRes.getState(),
6699                            histRes.getDateCreated(),
6700                            histRes.getUserCreated(),
6701                            histRes.getDateLastModified(),
6702                            histRes.getUserLastModified(),
6703                            histRes.getDateReleased(),
6704                            histRes.getDateExpired(),
6705                            histRes.getLength(),
6706                            histRes.getDateContent(),
6707                            histRes.getVersion(),
6708                            parentId,
6709                            null,
6710                            histRes.getResourceVersion(),
6711                            histRes.getStructureVersion()));
6712                }
6713            } catch (CmsDataAccessException e) {
6714                // should never happen
6715                if (LOG.isErrorEnabled()) {
6716                    LOG.error(e.getLocalizedMessage(), e);
6717                }
6718            }
6719        }
6720        if (readTree) {
6721            Iterator<I_CmsHistoryResource> itDeleted = deletedResources.iterator();
6722            while (itDeleted.hasNext()) {
6723                I_CmsHistoryResource delResource = itDeleted.next();
6724                if (delResource.isFolder()) {
6725                    newResult.addAll(readDeletedResources(dbc, (CmsFolder)delResource, readTree, isVfsManager));
6726                }
6727            }
6728            try {
6729                readResource(dbc, resource.getStructureId(), CmsResourceFilter.ALL);
6730                // resource exists, so recurse
6731                Iterator<CmsResource> itResources = readResources(
6732                    dbc,
6733                    resource,
6734                    CmsResourceFilter.ALL.addRequireFolder(),
6735                    readTree).iterator();
6736                while (itResources.hasNext()) {
6737                    CmsResource subResource = itResources.next();
6738                    if (subResource.isFolder()) {
6739                        newResult.addAll(readDeletedResources(dbc, subResource, readTree, isVfsManager));
6740                    }
6741                }
6742            } catch (Exception e) {
6743                // resource does not exists
6744                if (LOG.isDebugEnabled()) {
6745                    LOG.debug(e.getLocalizedMessage(), e);
6746                }
6747            }
6748        }
6749        List<I_CmsHistoryResource> finalRes = new ArrayList<I_CmsHistoryResource>(newResult);
6750        Collections.sort(finalRes, I_CmsResource.COMPARE_ROOT_PATH);
6751        return finalRes;
6752    }
6753
6754    /**
6755     * Reads a file resource (including it's binary content) from the VFS,
6756     * using the specified resource filter.<p>
6757     *
6758     * In case you do not need the file content,
6759     * use <code>{@link #readResource(CmsDbContext, String, CmsResourceFilter)}</code> instead.<p>
6760     *
6761     * The specified filter controls what kind of resources should be "found"
6762     * during the read operation. This will depend on the application. For example,
6763     * using <code>{@link CmsResourceFilter#DEFAULT}</code> will only return currently
6764     * "valid" resources, while using <code>{@link CmsResourceFilter#IGNORE_EXPIRATION}</code>
6765     * will ignore the date release / date expired information of the resource.<p>
6766     *
6767     * @param dbc the current database context
6768     * @param resource the base file resource (without content)
6769     * @return the file read from the VFS
6770     * @throws CmsException if operation was not successful
6771     */
6772    public CmsFile readFile(CmsDbContext dbc, CmsResource resource) throws CmsException {
6773
6774        if (resource.isFolder()) {
6775            throw new CmsVfsResourceNotFoundException(
6776                Messages.get().container(
6777                    Messages.ERR_ACCESS_FOLDER_AS_FILE_1,
6778                    dbc.removeSiteRoot(resource.getRootPath())));
6779        }
6780
6781        CmsUUID projectId = dbc.currentProject().getUuid();
6782        CmsFile file = null;
6783        if (resource instanceof I_CmsHistoryResource) {
6784            file = new CmsHistoryFile((I_CmsHistoryResource)resource);
6785            file.setContents(
6786                getHistoryDriver(dbc).readContent(
6787                    dbc,
6788                    resource.getResourceId(),
6789                    ((I_CmsHistoryResource)resource).getPublishTag()));
6790        } else {
6791            file = new CmsFile(resource);
6792            file.setContents(getVfsDriver(dbc).readContent(dbc, projectId, resource.getResourceId()));
6793        }
6794        return file;
6795    }
6796
6797    /**
6798     * Reads a folder from the VFS,
6799     * using the specified resource filter.<p>
6800     *
6801     * @param dbc the current database context
6802     * @param resourcename the name of the folder to read (full path)
6803     * @param filter the resource filter to use while reading
6804     *
6805     * @return the folder that was read
6806     *
6807     * @throws CmsDataAccessException if something goes wrong
6808     *
6809     * @see #readResource(CmsDbContext, String, CmsResourceFilter)
6810     * @see CmsObject#readFolder(String)
6811     * @see CmsObject#readFolder(String, CmsResourceFilter)
6812     */
6813    public CmsFolder readFolder(CmsDbContext dbc, String resourcename, CmsResourceFilter filter)
6814    throws CmsDataAccessException {
6815
6816        CmsResource resource = readResource(dbc, resourcename, filter);
6817
6818        return convertResourceToFolder(resource);
6819    }
6820
6821    /**
6822     * Reads the group of a project.<p>
6823     *
6824     * @param dbc the current database context
6825     * @param project the project to read from
6826     *
6827     * @return the group of a resource
6828     */
6829    public CmsGroup readGroup(CmsDbContext dbc, CmsProject project) {
6830
6831        try {
6832            return readGroup(dbc, project.getGroupId());
6833        } catch (CmsException exc) {
6834            return new CmsGroup(
6835                CmsUUID.getNullUUID(),
6836                CmsUUID.getNullUUID(),
6837                project.getGroupId() + "",
6838                "deleted group",
6839                0);
6840        }
6841    }
6842
6843    /**
6844     * Reads a group based on its id.<p>
6845     *
6846     * @param dbc the current database context
6847     * @param groupId the id of the group that is to be read
6848     *
6849     * @return the requested group
6850     *
6851     * @throws CmsException if operation was not successful
6852     */
6853    public CmsGroup readGroup(CmsDbContext dbc, CmsUUID groupId) throws CmsException {
6854
6855        CmsGroup group = null;
6856        // try to read group from cache
6857        group = m_monitor.getCachedGroup(groupId.toString());
6858        if (group == null) {
6859            group = getUserDriver(dbc).readGroup(dbc, groupId);
6860            m_monitor.cacheGroup(group);
6861        }
6862        return group;
6863    }
6864
6865    /**
6866     * Reads a group based on its name.<p>
6867     *
6868     * @param dbc the current database context
6869     * @param groupname the name of the group that is to be read
6870     *
6871     * @return the requested group
6872     *
6873     * @throws CmsDataAccessException if operation was not successful
6874     */
6875    public CmsGroup readGroup(CmsDbContext dbc, String groupname) throws CmsDataAccessException {
6876
6877        CmsGroup group = null;
6878        // try to read group from cache
6879        group = m_monitor.getCachedGroup(groupname);
6880        if (group == null) {
6881            group = getUserDriver(dbc).readGroup(dbc, groupname);
6882            m_monitor.cacheGroup(group);
6883        }
6884        return group;
6885    }
6886
6887    /**
6888     * Reads a principal (an user or group) from the historical archive based on its ID.<p>
6889     *
6890     * @param dbc the current database context
6891     * @param principalId the id of the principal to read
6892     *
6893     * @return the historical principal entry with the given id
6894     *
6895     * @throws CmsException if something goes wrong, ie. {@link CmsDbEntryNotFoundException}
6896     *
6897     * @see CmsObject#readUser(CmsUUID)
6898     * @see CmsObject#readGroup(CmsUUID)
6899     * @see CmsObject#readHistoryPrincipal(CmsUUID)
6900     */
6901    public CmsHistoryPrincipal readHistoricalPrincipal(CmsDbContext dbc, CmsUUID principalId) throws CmsException {
6902
6903        return getHistoryDriver(dbc).readPrincipal(dbc, principalId);
6904    }
6905
6906    /**
6907     * Returns the latest historical project entry with the given id.<p>
6908     *
6909     * @param dbc the current database context
6910     * @param projectId the project id
6911     *
6912     * @return the requested historical project entry
6913     *
6914     * @throws CmsException if something goes wrong
6915     */
6916    public CmsHistoryProject readHistoryProject(CmsDbContext dbc, CmsUUID projectId) throws CmsException {
6917
6918        return getHistoryDriver(dbc).readProject(dbc, projectId);
6919    }
6920
6921    /**
6922     * Returns a historical project entry.<p>
6923     *
6924     * @param dbc the current database context
6925     * @param publishTag the publish tag of the project
6926     *
6927     * @return the requested historical project entry
6928     *
6929     * @throws CmsException if something goes wrong
6930     */
6931    public CmsHistoryProject readHistoryProject(CmsDbContext dbc, int publishTag) throws CmsException {
6932
6933        return getHistoryDriver(dbc).readProject(dbc, publishTag);
6934    }
6935
6936    /**
6937     * Reads the list of all <code>{@link CmsProperty}</code> objects that belongs to the given historical resource.<p>
6938     *
6939     * @param dbc the current database context
6940     * @param historyResource the historical resource to read the properties for
6941     *
6942     * @return the list of <code>{@link CmsProperty}</code> objects
6943     *
6944     * @throws CmsException if something goes wrong
6945     */
6946    public List<CmsProperty> readHistoryPropertyObjects(CmsDbContext dbc, I_CmsHistoryResource historyResource)
6947    throws CmsException {
6948
6949        return getHistoryDriver(dbc).readProperties(dbc, historyResource);
6950    }
6951
6952    /**
6953     * Reads the structure id which is mapped to a given URL name.<p>
6954     *
6955     * @param dbc the current database context
6956     * @param name the name for which the mapped structure id should be looked up
6957     *
6958     * @return the structure id which is mapped to the given name, or null if there is no such id
6959     *
6960     * @throws CmsDataAccessException if something goes wrong
6961     */
6962    public CmsUUID readIdForUrlName(CmsDbContext dbc, String name) throws CmsDataAccessException {
6963
6964        List<CmsUrlNameMappingEntry> entries = getVfsDriver(dbc).readUrlNameMappingEntries(
6965            dbc,
6966            dbc.currentProject().isOnlineProject(),
6967            CmsUrlNameMappingFilter.ALL.filterName(name));
6968        if (entries.isEmpty()) {
6969            return null;
6970        }
6971        return entries.get(0).getStructureId();
6972    }
6973
6974    /**
6975     * Reads the locks that were saved to the database in the previous run of OpenCms.<p>
6976     *
6977     * @param dbc the current database context
6978     *
6979     * @throws CmsException if something goes wrong
6980     */
6981    public void readLocks(CmsDbContext dbc) throws CmsException {
6982
6983        m_lockManager.readLocks(dbc);
6984    }
6985
6986    /**
6987     * Reads the manager group of a project.<p>
6988     *
6989     * @param dbc the current database context
6990     * @param project the project to read from
6991     *
6992     * @return the group of a resource
6993     */
6994    public CmsGroup readManagerGroup(CmsDbContext dbc, CmsProject project) {
6995
6996        try {
6997            return readGroup(dbc, project.getManagerGroupId());
6998        } catch (CmsException exc) {
6999            // the group does not exist any more - return a dummy-group
7000            return new CmsGroup(
7001                CmsUUID.getNullUUID(),
7002                CmsUUID.getNullUUID(),
7003                project.getManagerGroupId() + "",
7004                "deleted group",
7005                0);
7006        }
7007    }
7008
7009    /**
7010     * Reads the URL name which has been most recently mapped to the given structure id, or null
7011     * if no URL name is mapped to the id.<p>
7012     *
7013     * @param dbc the current database context
7014     * @param id a structure id
7015     * @return the name which has been most recently mapped to the given structure id
7016     *
7017     * @throws CmsDataAccessException if something goes wrong
7018     */
7019    public String readNewestUrlNameForId(CmsDbContext dbc, CmsUUID id) throws CmsDataAccessException {
7020
7021        List<CmsUrlNameMappingEntry> entries = getVfsDriver(dbc).readUrlNameMappingEntries(
7022            dbc,
7023            dbc.currentProject().isOnlineProject(),
7024            CmsUrlNameMappingFilter.ALL.filterStructureId(id));
7025        if (entries.isEmpty()) {
7026            return null;
7027        }
7028
7029        Collections.sort(entries, new UrlNameMappingComparator());
7030        CmsUrlNameMappingEntry lastEntry = entries.get(entries.size() - 1);
7031        return lastEntry.getName();
7032    }
7033
7034    /**
7035     * Reads an organizational Unit based on its fully qualified name.<p>
7036     *
7037     * @param dbc the current db context
7038     * @param ouFqn the fully qualified name of the organizational Unit to be read
7039     *
7040     * @return the organizational Unit that with the provided fully qualified name
7041     *
7042     * @throws CmsException if something goes wrong
7043     */
7044    public CmsOrganizationalUnit readOrganizationalUnit(CmsDbContext dbc, String ouFqn) throws CmsException {
7045
7046        CmsOrganizationalUnit organizationalUnit = null;
7047        // try to read organizational unit from cache
7048        organizationalUnit = m_monitor.getCachedOrgUnit(ouFqn);
7049        if (organizationalUnit == null) {
7050            organizationalUnit = getUserDriver(dbc).readOrganizationalUnit(dbc, ouFqn);
7051            m_monitor.cacheOrgUnit(organizationalUnit);
7052        }
7053        return organizationalUnit;
7054    }
7055
7056    /**
7057     * Reads the owner of a project.<p>
7058     *
7059     * @param dbc the current database context
7060     * @param project the project to get the owner from
7061     *
7062     * @return the owner of a resource
7063     * @throws CmsException if something goes wrong
7064     */
7065    public CmsUser readOwner(CmsDbContext dbc, CmsProject project) throws CmsException {
7066
7067        return readUser(dbc, project.getOwnerId());
7068    }
7069
7070    /**
7071     * Reads the parent folder to a given structure id.<p>
7072     *
7073     * @param dbc the current database context
7074     * @param structureId the structure id of the child
7075     *
7076     * @return the parent folder resource
7077     *
7078     * @throws CmsDataAccessException if something goes wrong
7079     */
7080    public CmsResource readParentFolder(CmsDbContext dbc, CmsUUID structureId) throws CmsDataAccessException {
7081
7082        return getVfsDriver(dbc).readParentFolder(dbc, dbc.currentProject().getUuid(), structureId);
7083    }
7084
7085    /**
7086     * Builds a list of resources for a given path.<p>
7087     *
7088     * @param dbc the current database context
7089     * @param path the requested path
7090     * @param filter a filter object (only "includeDeleted" information is used!)
7091     *
7092     * @return list of <code>{@link CmsResource}</code>s
7093     *
7094     * @throws CmsException if something goes wrong
7095     */
7096    public List<CmsResource> readPath(CmsDbContext dbc, String path, CmsResourceFilter filter) throws CmsException {
7097
7098        // splits the path into folder and filename tokens
7099        List<String> tokens = CmsStringUtil.splitAsList(path, '/');
7100
7101        // the root folder is no token in the path but a resource which has to be added to the path
7102        int count = tokens.size() + 1;
7103        // holds the CmsResource instances in the path
7104        List<CmsResource> pathList = new ArrayList<CmsResource>(count);
7105
7106        // true if the path doesn't end with a folder
7107        boolean lastResourceIsFile = false;
7108        // number of folders in the path
7109        int folderCount = count;
7110        if (!path.endsWith("/")) {
7111            folderCount--;
7112            lastResourceIsFile = true;
7113        }
7114
7115        // read the root folder, because it's ID is required to read any sub-resources
7116        String currentResourceName = "/";
7117        StringBuffer currentPath = new StringBuffer(64);
7118        currentPath.append('/');
7119
7120        String cp = currentPath.toString();
7121        CmsUUID projectId = getProjectIdForContext(dbc);
7122
7123        // key to cache the resources
7124        String cacheKey = getCacheKey(null, false, projectId, cp);
7125        // the current resource
7126        CmsResource currentResource = m_monitor.getCachedResource(cacheKey);
7127        if ((currentResource == null) || !dbc.getProjectId().isNullUUID()) {
7128            currentResource = getVfsDriver(dbc).readFolder(dbc, projectId, cp);
7129            if (dbc.getProjectId().isNullUUID()) {
7130                m_monitor.cacheResource(cacheKey, currentResource);
7131            }
7132        }
7133
7134        pathList.add(0, currentResource);
7135
7136        if (count == 1) {
7137            // the root folder was requested- no further operations required
7138            return pathList;
7139        }
7140
7141        Iterator<String> it = tokens.iterator();
7142        currentResourceName = it.next();
7143
7144        // read the folder resources in the path /a/b/c/
7145        int i = 0;
7146        for (i = 1; i < folderCount; i++) {
7147            currentPath.append(currentResourceName);
7148            currentPath.append('/');
7149            // read the folder
7150            cp = currentPath.toString();
7151            cacheKey = getCacheKey(null, false, projectId, cp);
7152            currentResource = m_monitor.getCachedResource(cacheKey);
7153            if ((currentResource == null) || !dbc.getProjectId().isNullUUID()) {
7154                currentResource = getVfsDriver(dbc).readFolder(dbc, projectId, cp);
7155                if (dbc.getProjectId().isNullUUID()) {
7156                    m_monitor.cacheResource(cacheKey, currentResource);
7157                }
7158            }
7159
7160            pathList.add(i, currentResource);
7161
7162            if (i < (folderCount - 1)) {
7163                currentResourceName = it.next();
7164            }
7165        }
7166
7167        // read the (optional) last file resource in the path /x.html
7168        if (lastResourceIsFile) {
7169            if (it.hasNext()) {
7170                // this will only be false if a resource in the
7171                // top level root folder (e.g. "/index.html") was requested
7172                currentResourceName = it.next();
7173            }
7174            currentPath.append(currentResourceName);
7175
7176            // read the file
7177            cp = currentPath.toString();
7178            cacheKey = getCacheKey(null, false, projectId, cp);
7179            currentResource = m_monitor.getCachedResource(cacheKey);
7180            if ((currentResource == null) || !dbc.getProjectId().isNullUUID()) {
7181                currentResource = getVfsDriver(dbc).readResource(dbc, projectId, cp, filter.includeDeleted());
7182                if (dbc.getProjectId().isNullUUID()) {
7183                    m_monitor.cacheResource(cacheKey, currentResource);
7184                }
7185            }
7186
7187            pathList.add(i, currentResource);
7188        }
7189
7190        return pathList;
7191    }
7192
7193    /**
7194     * Reads a project given the projects id.<p>
7195     *
7196     * @param dbc the current database context
7197     * @param id the id of the project
7198     *
7199     * @return the project read
7200     *
7201     * @throws CmsDataAccessException if something goes wrong
7202     */
7203    public CmsProject readProject(CmsDbContext dbc, CmsUUID id) throws CmsDataAccessException {
7204
7205        CmsProject project = null;
7206        project = m_monitor.getCachedProject(id.toString());
7207        if (project == null) {
7208            project = getProjectDriver(dbc).readProject(dbc, id);
7209            m_monitor.cacheProject(project);
7210        }
7211        return project;
7212    }
7213
7214    /**
7215     * Reads a project.<p>
7216     *
7217     * Important: Since a project name can be used multiple times, this is NOT the most efficient
7218     * way to read the project. This is only a convenience for front end developing.
7219     * Reading a project by name will return the first project with that name.
7220     * All core classes must use the id version {@link #readProject(CmsDbContext, CmsUUID)} to ensure the right project is read.<p>
7221     *
7222     * @param dbc the current database context
7223     * @param name the name of the project
7224     *
7225     * @return the project read
7226     *
7227     * @throws CmsException if something goes wrong
7228     */
7229    public CmsProject readProject(CmsDbContext dbc, String name) throws CmsException {
7230
7231        CmsProject project = null;
7232        project = m_monitor.getCachedProject(name);
7233        if (project == null) {
7234            project = getProjectDriver(dbc).readProject(dbc, name);
7235            m_monitor.cacheProject(project);
7236        }
7237        return project;
7238    }
7239
7240    /**
7241     * Returns the list of all resource names that define the "view" of the given project.<p>
7242     *
7243     * @param dbc the current database context
7244     * @param project the project to get the project resources for
7245     *
7246     * @return the list of all resources, as <code>{@link String}</code> objects
7247     *              that define the "view" of the given project.
7248     *
7249     * @throws CmsException if something goes wrong
7250     */
7251    public List<String> readProjectResources(CmsDbContext dbc, CmsProject project) throws CmsException {
7252
7253        return getProjectDriver(dbc).readProjectResources(dbc, project);
7254    }
7255
7256    /**
7257     * Reads all resources of a project that match a given state from the VFS.<p>
7258     *
7259     * Possible values for the <code>state</code> parameter are:<br>
7260     * <ul>
7261     * <li><code>{@link CmsResource#STATE_CHANGED}</code>: Read all "changed" resources in the project</li>
7262     * <li><code>{@link CmsResource#STATE_NEW}</code>: Read all "new" resources in the project</li>
7263     * <li><code>{@link CmsResource#STATE_DELETED}</code>: Read all "deleted" resources in the project</li>
7264     * <li><code>{@link CmsResource#STATE_KEEP}</code>: Read all resources either "changed", "new" or "deleted" in the project</li>
7265     * </ul><p>
7266     *
7267     * @param dbc the current database context
7268     * @param projectId the id of the project to read the file resources for
7269     * @param state the resource state to match
7270     *
7271     * @return a list of <code>{@link CmsResource}</code> objects matching the filter criteria
7272     *
7273     * @throws CmsException if something goes wrong
7274     *
7275     * @see CmsObject#readProjectView(CmsUUID, CmsResourceState)
7276     */
7277    public List<CmsResource> readProjectView(CmsDbContext dbc, CmsUUID projectId, CmsResourceState state)
7278    throws CmsException {
7279
7280        List<CmsResource> resources;
7281        if (state.isNew() || state.isChanged() || state.isDeleted()) {
7282            // get all resources form the database that match the selected state
7283            resources = getVfsDriver(dbc).readResources(dbc, projectId, state, CmsDriverManager.READMODE_MATCHSTATE);
7284        } else {
7285            // get all resources form the database that are somehow changed (i.e. not unchanged)
7286            resources = getVfsDriver(dbc).readResources(
7287                dbc,
7288                projectId,
7289                CmsResource.STATE_UNCHANGED,
7290                CmsDriverManager.READMODE_UNMATCHSTATE);
7291        }
7292
7293        // filter the permissions
7294        List<CmsResource> result = filterPermissions(dbc, resources, CmsResourceFilter.ALL);
7295        // sort the result
7296        Collections.sort(result);
7297        // set the full resource names
7298        return updateContextDates(dbc, result);
7299    }
7300
7301    /**
7302     * Reads a property definition.<p>
7303     *
7304     * If no property definition with the given name is found,
7305     * <code>null</code> is returned.<p>
7306     *
7307     * @param dbc the current database context
7308     * @param name the name of the property definition to read
7309     *
7310     * @return the property definition that was read
7311     *
7312     * @throws CmsException a CmsDbEntryNotFoundException is thrown if the property definition does not exist
7313     */
7314    public CmsPropertyDefinition readPropertyDefinition(CmsDbContext dbc, String name) throws CmsException {
7315
7316        return getVfsDriver(dbc).readPropertyDefinition(dbc, name, dbc.currentProject().getUuid());
7317    }
7318
7319    /**
7320     * Reads a property object from a resource specified by a property name.<p>
7321     *
7322     * Returns <code>{@link CmsProperty#getNullProperty()}</code> if the property is not found.<p>
7323     *
7324     * @param dbc the current database context
7325     * @param resource the resource where the property is read from
7326     * @param key the property key name
7327     * @param search if <code>true</code>, the property is searched on all parent folders of the resource.
7328     *      if it's not found attached directly to the resource.
7329     *
7330     * @return the required property, or <code>{@link CmsProperty#getNullProperty()}</code> if the property was not found
7331     *
7332     * @throws CmsException if something goes wrong
7333     */
7334    public CmsProperty readPropertyObject(CmsDbContext dbc, CmsResource resource, String key, boolean search)
7335    throws CmsException {
7336
7337        // use the list reading method to obtain all properties for the resource
7338        List<CmsProperty> properties = readPropertyObjects(dbc, resource, search);
7339        // create a lookup propertry object and look this up in the result map
7340        int i = properties.indexOf(new CmsProperty(key, null, null));
7341        CmsProperty result;
7342        if (i >= 0) {
7343            // property has been found in the map
7344            result = properties.get(i);
7345        } else {
7346            // property is not defined, return NULL property
7347            result = CmsProperty.getNullProperty();
7348        }
7349        // ensure the result value is not frozen
7350        return result.cloneAsProperty();
7351    }
7352
7353    /**
7354     * Reads all property objects mapped to a specified resource from the database.<p>
7355     *
7356     * All properties in the result List will be in frozen (read only) state, so you can't change the values.<p>
7357     *
7358     * Returns an empty list if no properties are found at all.<p>
7359     *
7360     * @param dbc the current database context
7361     * @param resource the resource where the properties are read from
7362     * @param search true, if the properties should be searched on all parent folders  if not found on the resource
7363     *
7364     * @return a list of CmsProperty objects containing the structure and/or resource value
7365     *
7366     * @throws CmsException if something goes wrong
7367     *
7368     * @see CmsObject#readPropertyObjects(String, boolean)
7369     */
7370    public List<CmsProperty> readPropertyObjects(CmsDbContext dbc, CmsResource resource, boolean search)
7371    throws CmsException {
7372
7373        // check if we have the result already cached
7374        CmsUUID projectId = getProjectIdForContext(dbc);
7375        String cacheKey = getCacheKey(CACHE_ALL_PROPERTIES, search, projectId, resource.getRootPath());
7376
7377        List<CmsProperty> properties = m_monitor.getCachedPropertyList(cacheKey);
7378
7379        if ((properties == null) || !dbc.getProjectId().isNullUUID()) {
7380            // result not cached, let's look it up in the DB
7381            if (search) {
7382                boolean cont;
7383                properties = new ArrayList<CmsProperty>();
7384                List<CmsProperty> parentProperties = null;
7385
7386                do {
7387                    try {
7388                        parentProperties = readPropertyObjects(dbc, resource, false);
7389
7390                        // make sure properties from lower folders "overwrite" properties from upper folders
7391                        parentProperties.removeAll(properties);
7392                        parentProperties.addAll(properties);
7393
7394                        properties.clear();
7395                        properties.addAll(parentProperties);
7396
7397                        cont = resource.getRootPath().length() > 1;
7398                    } catch (CmsSecurityException se) {
7399                        // a security exception (probably no read permission) we return the current result
7400                        cont = false;
7401                    }
7402                    if (cont) {
7403                        // no permission check on parent folder is required since we must have "read"
7404                        // permissions to read the child resource anyway
7405                        resource = readResource(
7406                            dbc,
7407                            CmsResource.getParentFolder(resource.getRootPath()),
7408                            CmsResourceFilter.ALL);
7409                    }
7410                } while (cont);
7411            } else {
7412                properties = getVfsDriver(dbc).readPropertyObjects(dbc, dbc.currentProject(), resource);
7413                //                for (CmsProperty prop : properties) {
7414                //                    prop.setOrigin(resource.getRootPath());
7415                //                }
7416            }
7417
7418            // set all properties in the result list as frozen
7419            CmsProperty.setFrozen(properties);
7420            if (dbc.getProjectId().isNullUUID()) {
7421                // store the result in the cache if needed
7422                m_monitor.cachePropertyList(cacheKey, properties);
7423            }
7424        }
7425
7426        return new ArrayList<CmsProperty>(properties);
7427    }
7428
7429    /**
7430     * Reads the resources that were published in a publish task for a given publish history ID.<p>
7431     *
7432     * @param dbc the current database context
7433     * @param publishHistoryId unique int ID to identify each publish task in the publish history
7434     *
7435     * @return a list of <code>{@link org.opencms.db.CmsPublishedResource}</code> objects
7436     *
7437     * @throws CmsException if something goes wrong
7438     */
7439    public List<CmsPublishedResource> readPublishedResources(CmsDbContext dbc, CmsUUID publishHistoryId)
7440    throws CmsException {
7441
7442        String cacheKey = publishHistoryId.toString();
7443        List<CmsPublishedResource> resourceList = m_monitor.getCachedPublishedResources(cacheKey);
7444        if ((resourceList == null) || !dbc.getProjectId().isNullUUID()) {
7445            resourceList = getProjectDriver(dbc).readPublishedResources(dbc, publishHistoryId);
7446            // store the result in the cache
7447            if (dbc.getProjectId().isNullUUID()) {
7448                m_monitor.cachePublishedResources(cacheKey, resourceList);
7449            }
7450        }
7451        return resourceList;
7452    }
7453
7454    /**
7455     * Reads a single publish job identified by its publish history id.<p>
7456     *
7457     * @param dbc the current database context
7458     * @param publishHistoryId unique id to identify the publish job in the publish history
7459     * @return an object of type <code>{@link CmsPublishJobInfoBean}</code>
7460     *
7461     * @throws CmsException if something goes wrong
7462     */
7463    public CmsPublishJobInfoBean readPublishJob(CmsDbContext dbc, CmsUUID publishHistoryId) throws CmsException {
7464
7465        return getProjectDriver(dbc).readPublishJob(dbc, publishHistoryId);
7466    }
7467
7468    /**
7469     * Reads all available publish jobs.<p>
7470     *
7471     * @param dbc the current database context
7472     * @param startTime the start of the time range for finish time
7473     * @param endTime the end of the time range for finish time
7474     * @return a list of objects of type <code>{@link CmsPublishJobInfoBean}</code>
7475     *
7476     * @throws CmsException if something goes wrong
7477     */
7478    public List<CmsPublishJobInfoBean> readPublishJobs(CmsDbContext dbc, long startTime, long endTime)
7479    throws CmsException {
7480
7481        return getProjectDriver(dbc).readPublishJobs(dbc, startTime, endTime);
7482    }
7483
7484    /**
7485     * Reads the publish list assigned to a publish job.<p>
7486     *
7487     * @param dbc the current database context
7488     * @param publishHistoryId the history id identifying the publish job
7489     * @return the assigned publish list
7490     * @throws CmsException if something goes wrong
7491     */
7492    public CmsPublishList readPublishList(CmsDbContext dbc, CmsUUID publishHistoryId) throws CmsException {
7493
7494        return getProjectDriver(dbc).readPublishList(dbc, publishHistoryId);
7495    }
7496
7497    /**
7498     * Reads the publish report assigned to a publish job.<p>
7499     *
7500     * @param dbc the current database context
7501     * @param publishHistoryId the history id identifying the publish job
7502     * @return the content of the assigned publish report
7503     * @throws CmsException if something goes wrong
7504     */
7505    public byte[] readPublishReportContents(CmsDbContext dbc, CmsUUID publishHistoryId) throws CmsException {
7506
7507        return getProjectDriver(dbc).readPublishReportContents(dbc, publishHistoryId);
7508    }
7509
7510    /**
7511     * Reads an historical resource entry for the given resource and with the given version number.<p>
7512     *
7513     * @param dbc the current db context
7514     * @param resource the resource to be read
7515     * @param version the version number to retrieve
7516     *
7517     * @return the resource that was read
7518     *
7519     * @throws CmsException if the resource could not be read for any reason
7520     *
7521     * @see CmsObject#restoreResourceVersion(CmsUUID, int)
7522     * @see CmsObject#readResource(CmsUUID, int)
7523     */
7524    public I_CmsHistoryResource readResource(CmsDbContext dbc, CmsResource resource, int version) throws CmsException {
7525
7526        Iterator<I_CmsHistoryResource> itVersions = getHistoryDriver(dbc).readAllAvailableVersions(
7527            dbc,
7528            resource.getStructureId()).iterator();
7529        while (itVersions.hasNext()) {
7530            I_CmsHistoryResource histRes = itVersions.next();
7531            if (histRes.getVersion() == version) {
7532                return histRes;
7533            }
7534        }
7535        throw new CmsVfsResourceNotFoundException(
7536            org.opencms.db.generic.Messages.get().container(
7537                org.opencms.db.generic.Messages.ERR_HISTORY_FILE_NOT_FOUND_1,
7538                resource.getStructureId()));
7539    }
7540
7541    /**
7542     * Reads a resource from the VFS, using the specified resource filter.<p>
7543     *
7544     * @param dbc the current database context
7545     * @param structureID the structure id of the resource to read
7546     * @param filter the resource filter to use while reading
7547     *
7548     * @return the resource that was read
7549     *
7550     * @throws CmsDataAccessException if something goes wrong
7551     *
7552     * @see CmsObject#readResource(CmsUUID, CmsResourceFilter)
7553     * @see CmsObject#readResource(CmsUUID)
7554     */
7555    public CmsResource readResource(CmsDbContext dbc, CmsUUID structureID, CmsResourceFilter filter)
7556    throws CmsDataAccessException {
7557
7558        CmsUUID projectId = getProjectIdForContext(dbc);
7559        // please note: the filter will be applied in the security manager later
7560        CmsResource resource = getVfsDriver(dbc).readResource(dbc, projectId, structureID, filter.includeDeleted());
7561
7562        // context dates need to be updated
7563        updateContextDates(dbc, resource);
7564
7565        // return the resource
7566        return resource;
7567    }
7568
7569    /**
7570     * Reads a resource from the VFS, using the specified resource filter.<p>
7571     *
7572     * @param dbc the current database context
7573     * @param resourcePath the name of the resource to read (full path)
7574     * @param filter the resource filter to use while reading
7575     *
7576     * @return the resource that was read
7577     *
7578     * @throws CmsDataAccessException if something goes wrong
7579     *
7580     * @see CmsObject#readResource(String, CmsResourceFilter)
7581     * @see CmsObject#readResource(String)
7582     * @see CmsObject#readFile(CmsResource)
7583     */
7584    public CmsResource readResource(CmsDbContext dbc, String resourcePath, CmsResourceFilter filter)
7585    throws CmsDataAccessException {
7586
7587        CmsUUID projectId = getProjectIdForContext(dbc);
7588        // please note: the filter will be applied in the security manager later
7589        CmsResource resource = getVfsDriver(dbc).readResource(dbc, projectId, resourcePath, filter.includeDeleted());
7590
7591        // context dates need to be updated
7592        updateContextDates(dbc, resource);
7593
7594        // return the resource
7595        return resource;
7596    }
7597
7598    /**
7599     * Reads all resources below the given path matching the filter criteria,
7600     * including the full tree below the path only in case the <code>readTree</code>
7601     * parameter is <code>true</code>.<p>
7602     *
7603     * @param dbc the current database context
7604     * @param parent the parent path to read the resources from
7605     * @param filter the filter
7606     * @param readTree <code>true</code> to read all subresources
7607     *
7608     * @return a list of <code>{@link CmsResource}</code> objects matching the filter criteria
7609     *
7610     * @throws CmsDataAccessException if the bare reading of the resources fails
7611     * @throws CmsException if security and permission checks for the resources read fail
7612     */
7613    public List<CmsResource> readResources(
7614        CmsDbContext dbc,
7615        CmsResource parent,
7616        CmsResourceFilter filter,
7617        boolean readTree) throws CmsException, CmsDataAccessException {
7618
7619        // try to get the sub resources from the cache
7620        String cacheKey = getCacheKey(
7621            new String[] {dbc.currentUser().getName(), filter.getCacheId(), readTree ? "+" : "-", parent.getRootPath()},
7622            dbc);
7623
7624        List<CmsResource> resourceList = m_monitor.getCachedResourceList(cacheKey);
7625        if ((resourceList == null) || !dbc.getProjectId().isNullUUID()) {
7626            // read the result from the database
7627            resourceList = getVfsDriver(dbc).readResourceTree(
7628                dbc,
7629                dbc.currentProject().getUuid(),
7630                (readTree ? parent.getRootPath() : parent.getStructureId().toString()),
7631                filter.getType(),
7632                filter.getState(),
7633                filter.getModifiedAfter(),
7634                filter.getModifiedBefore(),
7635                filter.getReleaseAfter(),
7636                filter.getReleaseBefore(),
7637                filter.getExpireAfter(),
7638                filter.getExpireBefore(),
7639                (readTree ? CmsDriverManager.READMODE_INCLUDE_TREE : CmsDriverManager.READMODE_EXCLUDE_TREE)
7640                    | (filter.excludeType() ? CmsDriverManager.READMODE_EXCLUDE_TYPE : 0)
7641                    | (filter.excludeState() ? CmsDriverManager.READMODE_EXCLUDE_STATE : 0)
7642                    | ((filter.getOnlyFolders() != null)
7643                    ? (filter.getOnlyFolders().booleanValue()
7644                    ? CmsDriverManager.READMODE_ONLY_FOLDERS
7645                    : CmsDriverManager.READMODE_ONLY_FILES)
7646                    : 0));
7647
7648            // HACK: do not take care of permissions if reading organizational units
7649            if (!parent.getRootPath().startsWith("/system/orgunits/")) {
7650                // apply permission filter
7651                resourceList = filterPermissions(dbc, resourceList, filter);
7652            }
7653            // store the result in the resourceList cache
7654            if (dbc.getProjectId().isNullUUID()) {
7655                m_monitor.cacheResourceList(cacheKey, resourceList);
7656            }
7657        }
7658        // we must always apply the result filter and update the context dates
7659        return updateContextDates(dbc, resourceList, filter);
7660    }
7661
7662    /**
7663     * Returns the resources that were visited by a user set in the filter.<p>
7664     *
7665     * @param dbc the database context
7666     * @param poolName the name of the database pool to use
7667     * @param filter the filter that is used to get the visited resources
7668     *
7669     * @return the resources that were visited by a user set in the filter
7670     *
7671     * @throws CmsException if something goes wrong
7672     */
7673    public List<CmsResource> readResourcesVisitedBy(CmsDbContext dbc, String poolName, CmsVisitedByFilter filter)
7674    throws CmsException {
7675
7676        List<CmsResource> result = getSubscriptionDriver().readResourcesVisitedBy(dbc, poolName, filter);
7677        result = filterPermissions(dbc, result, CmsResourceFilter.DEFAULT);
7678        return result;
7679    }
7680
7681    /**
7682     * Reads all resources that have a value (containing the given value string) set
7683     * for the specified property (definition) in the given path.<p>
7684     *
7685     * Both individual and shared properties of a resource are checked.<p>
7686     *
7687     * If the <code>value</code> parameter is <code>null</code>, all resources having the
7688     * given property set are returned.<p>
7689     *
7690     * @param dbc the current database context
7691     * @param folder the folder to get the resources with the property from
7692     * @param propertyDefinition the name of the property (definition) to check for
7693     * @param value the string to search in the value of the property
7694     * @param filter the resource filter to apply to the result set
7695     *
7696     * @return a list of all <code>{@link CmsResource}</code> objects
7697     *          that have a value set for the specified property.
7698     *
7699     * @throws CmsException if something goes wrong
7700     */
7701    public List<CmsResource> readResourcesWithProperty(
7702        CmsDbContext dbc,
7703        CmsResource folder,
7704        String propertyDefinition,
7705        String value,
7706        CmsResourceFilter filter) throws CmsException {
7707
7708        String cacheKey;
7709        if (value == null) {
7710            cacheKey = getCacheKey(
7711                new String[] {
7712                    dbc.currentUser().getName(),
7713                    folder.getRootPath(),
7714                    propertyDefinition,
7715                    filter.getCacheId()},
7716                dbc);
7717        } else {
7718            cacheKey = getCacheKey(
7719                new String[] {
7720                    dbc.currentUser().getName(),
7721                    folder.getRootPath(),
7722                    propertyDefinition,
7723                    value,
7724                    filter.getCacheId()},
7725                dbc);
7726        }
7727        List<CmsResource> resourceList = m_monitor.getCachedResourceList(cacheKey);
7728        if ((resourceList == null) || !dbc.getProjectId().isNullUUID()) {
7729            // first read the property definition
7730            CmsPropertyDefinition propDef = readPropertyDefinition(dbc, propertyDefinition);
7731            // now read the list of resources that have a value set for the property definition
7732            resourceList = getVfsDriver(dbc).readResourcesWithProperty(
7733                dbc,
7734                dbc.currentProject().getUuid(),
7735                propDef.getId(),
7736                folder.getRootPath(),
7737                value);
7738            // apply permission filter
7739            resourceList = filterPermissions(dbc, resourceList, filter);
7740            // store the result in the resourceList cache
7741            if (dbc.getProjectId().isNullUUID()) {
7742                m_monitor.cacheResourceList(cacheKey, resourceList);
7743            }
7744        }
7745        // we must always apply the result filter and update the context dates
7746        return updateContextDates(dbc, resourceList, filter);
7747    }
7748
7749    /**
7750     * Returns the set of users that are responsible for a specific resource.<p>
7751     *
7752     * @param dbc the current database context
7753     * @param resource the resource to get the responsible users from
7754     *
7755     * @return the set of users that are responsible for a specific resource
7756     *
7757     * @throws CmsException if something goes wrong
7758     */
7759    public Set<I_CmsPrincipal> readResponsiblePrincipals(CmsDbContext dbc, CmsResource resource) throws CmsException {
7760
7761        Set<I_CmsPrincipal> result = new HashSet<I_CmsPrincipal>();
7762        Iterator<CmsAccessControlEntry> aces = getAccessControlEntries(dbc, resource, true).iterator();
7763        while (aces.hasNext()) {
7764            CmsAccessControlEntry ace = aces.next();
7765            if (ace.isResponsible()) {
7766                I_CmsPrincipal p = lookupPrincipal(dbc, ace.getPrincipal());
7767                if (p != null) {
7768                    result.add(p);
7769                }
7770            }
7771        }
7772        return result;
7773    }
7774
7775    /**
7776     * Returns the set of users that are responsible for a specific resource.<p>
7777     *
7778     * @param dbc the current database context
7779     * @param resource the resource to get the responsible users from
7780     *
7781     * @return the set of users that are responsible for a specific resource
7782     *
7783     * @throws CmsException if something goes wrong
7784     */
7785    public Set<CmsUser> readResponsibleUsers(CmsDbContext dbc, CmsResource resource) throws CmsException {
7786
7787        Set<CmsUser> result = new HashSet<CmsUser>();
7788        Iterator<I_CmsPrincipal> principals = readResponsiblePrincipals(dbc, resource).iterator();
7789        while (principals.hasNext()) {
7790            I_CmsPrincipal principal = principals.next();
7791            if (principal.isGroup()) {
7792                try {
7793                    result.addAll(getUsersOfGroup(dbc, principal.getName(), true, false, false));
7794                } catch (CmsException e) {
7795                    if (LOG.isInfoEnabled()) {
7796                        LOG.info(e);
7797                    }
7798                }
7799            } else {
7800                result.add((CmsUser)principal);
7801            }
7802        }
7803        return result;
7804    }
7805
7806    /**
7807     * Returns a List of all siblings of the specified resource,
7808     * the specified resource being always part of the result set.<p>
7809     *
7810     * The result is a list of <code>{@link CmsResource}</code> objects.<p>
7811     *
7812     * @param dbc the current database context
7813     * @param resource the resource to read the siblings for
7814     * @param filter a filter object
7815     *
7816     * @return a list of <code>{@link CmsResource}</code> Objects that
7817     *          are siblings to the specified resource,
7818     *          including the specified resource itself
7819     *
7820     * @throws CmsException if something goes wrong
7821     */
7822    public List<CmsResource> readSiblings(CmsDbContext dbc, CmsResource resource, CmsResourceFilter filter)
7823    throws CmsException {
7824
7825        List<CmsResource> siblings = getVfsDriver(dbc).readSiblings(
7826            dbc,
7827            dbc.currentProject().getUuid(),
7828            resource,
7829            filter.includeDeleted());
7830
7831        // important: there is no permission check done on the returned list of siblings
7832        // this is because of possible issues with the "publish all siblings" option,
7833        // moreover the user has read permission for the content through
7834        // the selected sibling anyway
7835        return updateContextDates(dbc, siblings, filter);
7836    }
7837
7838    /**
7839     * Returns the parameters of a resource in the table of all published template resources.<p>
7840     *
7841     * @param dbc the current database context
7842     * @param rfsName the rfs name of the resource
7843     *
7844     * @return the parameter string of the requested resource
7845     *
7846     * @throws CmsException if something goes wrong
7847     */
7848    public String readStaticExportPublishedResourceParameters(CmsDbContext dbc, String rfsName) throws CmsException {
7849
7850        return getProjectDriver(dbc).readStaticExportPublishedResourceParameters(dbc, rfsName);
7851    }
7852
7853    /**
7854     * Returns a list of all template resources which must be processed during a static export.<p>
7855     *
7856     * @param dbc the current database context
7857     * @param parameterResources flag for reading resources with parameters (1) or without (0)
7858     * @param timestamp for reading the data from the db
7859     *
7860     * @return a list of template resources as <code>{@link String}</code> objects
7861     *
7862     * @throws CmsException if something goes wrong
7863     */
7864    public List<String> readStaticExportResources(CmsDbContext dbc, int parameterResources, long timestamp)
7865    throws CmsException {
7866
7867        return getProjectDriver(dbc).readStaticExportResources(dbc, parameterResources, timestamp);
7868    }
7869
7870    /**
7871     * Returns the subscribed history resources that were deleted.<p>
7872     *
7873     * @param dbc the database context
7874     * @param poolName the name of the database pool to use
7875     * @param user the user that subscribed to the resource
7876     * @param groups the groups to check subscribed resources for
7877     * @param parent the parent resource (folder) of the deleted resources, if <code>null</code> all deleted resources will be returned
7878     * @param includeSubFolders indicates if the sub folders of the specified folder path should be considered, too
7879     * @param deletedFrom the time stamp from which the resources should have been deleted
7880     *
7881     * @return the subscribed history resources that were deleted
7882     *
7883     * @throws CmsException if something goes wrong
7884     */
7885    public List<I_CmsHistoryResource> readSubscribedDeletedResources(
7886        CmsDbContext dbc,
7887        String poolName,
7888        CmsUser user,
7889        List<CmsGroup> groups,
7890        CmsResource parent,
7891        boolean includeSubFolders,
7892        long deletedFrom) throws CmsException {
7893
7894        List<I_CmsHistoryResource> result = getSubscriptionDriver().readSubscribedDeletedResources(
7895            dbc,
7896            poolName,
7897            user,
7898            groups,
7899            parent,
7900            includeSubFolders,
7901            deletedFrom);
7902
7903        return result;
7904    }
7905
7906    /**
7907     * Returns the resources that were subscribed by a user or group set in the filter.<p>
7908     *
7909     * @param dbc the database context
7910     * @param poolName the name of the database pool to use
7911     * @param filter the filter that is used to get the subscribed resources
7912     *
7913     * @return the resources that were subscribed by a user or group set in the filter
7914     *
7915     * @throws CmsException if something goes wrong
7916     */
7917    public List<CmsResource> readSubscribedResources(CmsDbContext dbc, String poolName, CmsSubscriptionFilter filter)
7918    throws CmsException {
7919
7920        List<CmsResource> result = getSubscriptionDriver().readSubscribedResources(dbc, poolName, filter);
7921
7922        result = filterPermissions(dbc, result, CmsResourceFilter.DEFAULT);
7923        return result;
7924    }
7925
7926    /**
7927     * Reads URL name mapping entries which match the given filter.<p>
7928     *
7929     * @param dbc the database context
7930     * @param online if true, read online URL name mappings, else offline ones
7931     * @param filter the filter for matching the URL name entries
7932     *
7933     * @return the list of URL name mapping entries which match the given filter
7934     *
7935     * @throws CmsDataAccessException if something goes wrong
7936     */
7937    public List<CmsUrlNameMappingEntry> readUrlNameMappingEntries(
7938        CmsDbContext dbc,
7939        boolean online,
7940        CmsUrlNameMappingFilter filter) throws CmsDataAccessException {
7941
7942        I_CmsVfsDriver vfsDriver = getVfsDriver(dbc);
7943        return vfsDriver.readUrlNameMappingEntries(dbc, online, filter);
7944    }
7945
7946    /**
7947     * Reads the URL name mappings matching the given filter.<p>
7948     *
7949     * @param dbc the DB context to use
7950     * @param filter the filter used to select the mapping entries
7951     * @return the entries matching the given filter
7952     *
7953     * @throws CmsDataAccessException if something goes wrong
7954     */
7955    public List<CmsUrlNameMappingEntry> readUrlNameMappings(CmsDbContext dbc, CmsUrlNameMappingFilter filter)
7956    throws CmsDataAccessException {
7957
7958        List<CmsUrlNameMappingEntry> entries = getVfsDriver(dbc).readUrlNameMappingEntries(
7959            dbc,
7960            dbc.currentProject().isOnlineProject(),
7961            filter);
7962        return entries;
7963    }
7964
7965    /**
7966     * Reads the newest URL names of a resource for all locales.<p>
7967     *
7968     * @param dbc the database context
7969     * @param id the resource's structure id
7970     *
7971     * @return the url names for the locales
7972     *
7973     * @throws CmsDataAccessException if the database operation failed
7974     */
7975    public List<String> readUrlNamesForAllLocales(CmsDbContext dbc, CmsUUID id) throws CmsDataAccessException {
7976
7977        List<String> result = new ArrayList<String>();
7978        List<CmsUrlNameMappingEntry> entries = getVfsDriver(dbc).readUrlNameMappingEntries(
7979            dbc,
7980            dbc.currentProject().isOnlineProject(),
7981            CmsUrlNameMappingFilter.ALL.filterStructureId(id));
7982        ArrayListMultimap<String, CmsUrlNameMappingEntry> entriesByLocale = ArrayListMultimap.create();
7983        for (CmsUrlNameMappingEntry entry : entries) {
7984            String localeKey = entry.getLocale();
7985            entriesByLocale.put(localeKey, entry);
7986        }
7987
7988        for (String localeKey : entriesByLocale.keySet()) {
7989            List<CmsUrlNameMappingEntry> entrs = entriesByLocale.get(localeKey);
7990            CmsUrlNameMappingEntry maxEntryForLocale = Collections.max(entrs, new UrlNameMappingComparator());
7991            result.add(maxEntryForLocale.getName());
7992        }
7993        return result;
7994    }
7995
7996    /**
7997     * Returns a user object based on the id of a user.<p>
7998     *
7999     * @param dbc the current database context
8000     * @param id the id of the user to read
8001     *
8002     * @return the user read
8003     *
8004     * @throws CmsException if something goes wrong
8005     */
8006    public CmsUser readUser(CmsDbContext dbc, CmsUUID id) throws CmsException {
8007
8008        CmsUser user = m_monitor.getCachedUser(id.toString());
8009        if (user == null) {
8010            user = getUserDriver(dbc).readUser(dbc, id);
8011            m_monitor.cacheUser(user);
8012        }
8013        return user;
8014    }
8015
8016    /**
8017     * Returns a user object.<p>
8018     *
8019     * @param dbc the current database context
8020     * @param username the name of the user that is to be read
8021     *
8022     * @return user read
8023     *
8024     * @throws CmsDataAccessException if operation was not successful
8025     */
8026    public CmsUser readUser(CmsDbContext dbc, String username) throws CmsDataAccessException {
8027
8028        CmsUser user = m_monitor.getCachedUser(username);
8029        if (user == null) {
8030            user = getUserDriver(dbc).readUser(dbc, username);
8031            m_monitor.cacheUser(user);
8032        }
8033        return user;
8034    }
8035
8036    /**
8037     * Returns a user object if the password for the user is correct.<p>
8038     *
8039     * If the user/pwd pair is not valid a <code>{@link CmsException}</code> is thrown.<p>
8040     *
8041     * @param dbc the current database context
8042     * @param username the username of the user that is to be read
8043     * @param password the password of the user that is to be read
8044     *
8045     * @return user read
8046     *
8047     * @throws CmsException if operation was not successful
8048     */
8049    public CmsUser readUser(CmsDbContext dbc, String username, String password) throws CmsException {
8050
8051        // don't read user from cache here because password may have changed
8052        CmsUser user = getUserDriver(dbc).readUser(dbc, username, password, null);
8053        m_monitor.cacheUser(user);
8054        return user;
8055    }
8056
8057    /**
8058     * Removes an access control entry for a given resource and principal.<p>
8059     *
8060     * @param dbc the current database context
8061     * @param resource the resource
8062     * @param principal the id of the principal to remove the the access control entry for
8063     *
8064     * @throws CmsException if something goes wrong
8065     */
8066    public void removeAccessControlEntry(CmsDbContext dbc, CmsResource resource, CmsUUID principal)
8067    throws CmsException {
8068
8069        // remove the ace
8070        getUserDriver(dbc).removeAccessControlEntry(dbc, dbc.currentProject(), resource.getResourceId(), principal);
8071
8072        // log it
8073        log(
8074            dbc,
8075            new CmsLogEntry(
8076                dbc,
8077                resource.getStructureId(),
8078                CmsLogEntryType.RESOURCE_PERMISSIONS,
8079                new String[] {resource.getRootPath()}),
8080            false);
8081
8082        // update the "last modified" information
8083        setDateLastModified(dbc, resource, resource.getDateLastModified());
8084
8085        // clear the cache
8086        m_monitor.clearAccessControlListCache();
8087
8088        // fire a resource modification event
8089        Map<String, Object> data = new HashMap<String, Object>(2);
8090        data.put(I_CmsEventListener.KEY_RESOURCE, resource);
8091        data.put(I_CmsEventListener.KEY_CHANGE, new Integer(CHANGED_ACCESSCONTROL));
8092        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_MODIFIED, data));
8093    }
8094
8095    /**
8096     * Removes a resource from the given organizational unit.<p>
8097     *
8098     * @param dbc the current db context
8099     * @param orgUnit the organizational unit to remove the resource from
8100     * @param resource the resource that is to be removed from the organizational unit
8101     *
8102     * @throws CmsException if something goes wrong
8103     *
8104     * @see org.opencms.security.CmsOrgUnitManager#addResourceToOrgUnit(CmsObject, String, String)
8105     * @see org.opencms.security.CmsOrgUnitManager#addResourceToOrgUnit(CmsObject, String, String)
8106     */
8107    public void removeResourceFromOrgUnit(CmsDbContext dbc, CmsOrganizationalUnit orgUnit, CmsResource resource)
8108    throws CmsException {
8109
8110        m_monitor.flushCache(CmsMemoryMonitor.CacheType.HAS_ROLE, CmsMemoryMonitor.CacheType.ROLE_LIST);
8111        getUserDriver(dbc).removeResourceFromOrganizationalUnit(dbc, orgUnit, resource);
8112    }
8113
8114    /**
8115     * Removes a resource from the current project of the user.<p>
8116     *
8117     * @param dbc the current database context
8118     * @param resource the resource to apply this operation to
8119     *
8120     * @throws CmsException if something goes wrong
8121     *
8122     * @see CmsObject#copyResourceToProject(String)
8123     * @see I_CmsResourceType#copyResourceToProject(CmsObject, CmsSecurityManager, CmsResource)
8124     */
8125    public void removeResourceFromProject(CmsDbContext dbc, CmsResource resource) throws CmsException {
8126
8127        // remove the resource to the project only if the resource is already in the project
8128        if (isInsideCurrentProject(dbc, resource.getRootPath())) {
8129            // check if there are already any subfolders of this resource
8130            I_CmsProjectDriver projectDriver = getProjectDriver(dbc);
8131            if (resource.isFolder()) {
8132                List<String> projectResources = projectDriver.readProjectResources(dbc, dbc.currentProject());
8133                for (int i = 0; i < projectResources.size(); i++) {
8134                    String resname = projectResources.get(i);
8135                    if (resname.startsWith(resource.getRootPath())) {
8136                        // delete the existing project resource first
8137                        projectDriver.deleteProjectResource(dbc, dbc.currentProject().getUuid(), resname);
8138                    }
8139                }
8140            }
8141            try {
8142                projectDriver.deleteProjectResource(dbc, dbc.currentProject().getUuid(), resource.getRootPath());
8143            } catch (CmsException exc) {
8144                // if the subfolder exists already - all is ok
8145            } finally {
8146                m_monitor.flushCache(CmsMemoryMonitor.CacheType.PROJECT_RESOURCES);
8147
8148                OpenCms.fireCmsEvent(
8149                    new CmsEvent(
8150                        I_CmsEventListener.EVENT_PROJECT_MODIFIED,
8151                        Collections.<String, Object> singletonMap("project", dbc.currentProject())));
8152            }
8153        }
8154    }
8155
8156    /**
8157     * Removes the given resource to the given user's publish list.<p>
8158     *
8159     * @param dbc the database context
8160     * @param userId the user's id
8161     * @param structureIds the collection of structure IDs to remove
8162     *
8163     * @throws CmsDataAccessException if something goes wrong
8164     */
8165    public void removeResourceFromUsersPubList(CmsDbContext dbc, CmsUUID userId, Collection<CmsUUID> structureIds)
8166    throws CmsDataAccessException {
8167
8168        for (CmsUUID structureId : structureIds) {
8169            CmsLogEntry entry = new CmsLogEntry(
8170                userId,
8171                System.currentTimeMillis(),
8172                structureId,
8173                CmsLogEntryType.RESOURCE_HIDDEN,
8174                new String[] {readResource(dbc, structureId, CmsResourceFilter.ALL).getRootPath()});
8175            log(dbc, entry, true);
8176        }
8177    }
8178
8179    /**
8180     * Removes a user from a group.<p>
8181     *
8182     * @param dbc the current database context
8183     * @param username the name of the user that is to be removed from the group
8184     * @param groupname the name of the group
8185     * @param readRoles if to read roles or groups
8186     *
8187     * @throws CmsException if operation was not successful
8188     * @throws CmsIllegalArgumentException if the given user was not member in the given group
8189     * @throws CmsDbEntryNotFoundException if the given group was not found
8190     * @throws CmsSecurityException if the given user was <b>read as 'null' from the database</b>
8191     *
8192     * @see #addUserToGroup(CmsDbContext, String, String, boolean)
8193     */
8194    public void removeUserFromGroup(CmsDbContext dbc, String username, String groupname, boolean readRoles)
8195    throws CmsException, CmsIllegalArgumentException, CmsDbEntryNotFoundException, CmsSecurityException {
8196
8197        CmsGroup group = readGroup(dbc, groupname);
8198        //check if group exists
8199        if (group == null) {
8200            // the group does not exists
8201            throw new CmsDbEntryNotFoundException(Messages.get().container(Messages.ERR_UNKNOWN_GROUP_1, groupname));
8202        }
8203        if (group.isVirtual() && !readRoles) {
8204            // if removing a user from a virtual role treat it as removing the user from the role
8205            removeUserFromGroup(dbc, username, CmsRole.valueOf(group).getGroupName(), true);
8206            return;
8207        }
8208        if (group.isVirtual()) {
8209            // this is an hack so to prevent a unlimited recursive calls
8210            readRoles = false;
8211        }
8212        if ((readRoles && !group.isRole()) || (!readRoles && group.isRole())) {
8213            // we want a role but we got a group, or the other way
8214            throw new CmsDbEntryNotFoundException(Messages.get().container(Messages.ERR_UNKNOWN_GROUP_1, groupname));
8215        }
8216
8217        // test if this user is existing in the group
8218        if (!userInGroup(dbc, username, groupname, readRoles)) {
8219            // user is not in the group, throw exception
8220            throw new CmsIllegalArgumentException(
8221                Messages.get().container(Messages.ERR_USER_NOT_IN_GROUP_2, username, groupname));
8222        }
8223
8224        CmsUser user = readUser(dbc, username);
8225        //check if the user exists
8226        if (user == null) {
8227            // the user does not exists
8228            throw new CmsIllegalArgumentException(
8229                Messages.get().container(Messages.ERR_USER_NOT_IN_GROUP_2, username, groupname));
8230        }
8231
8232        if (readRoles) {
8233            CmsRole role = CmsRole.valueOf(group);
8234            // update virtual groups
8235            Iterator<CmsGroup> it = getVirtualGroupsForRole(dbc, role).iterator();
8236            while (it.hasNext()) {
8237                CmsGroup virtualGroup = it.next();
8238                if (userInGroup(dbc, username, virtualGroup.getName(), false)) {
8239                    // here we say readroles = true, to prevent an unlimited recursive calls
8240                    removeUserFromGroup(dbc, username, virtualGroup.getName(), true);
8241                }
8242            }
8243        }
8244        getUserDriver(dbc).deleteUserInGroup(dbc, user.getId(), group.getId());
8245
8246        // flush relevant caches
8247        if (readRoles) {
8248            m_monitor.flushCache(CmsMemoryMonitor.CacheType.HAS_ROLE, CmsMemoryMonitor.CacheType.ROLE_LIST);
8249        }
8250        m_monitor.flushCache(CmsMemoryMonitor.CacheType.USERGROUPS, CmsMemoryMonitor.CacheType.USER_LIST);
8251
8252        if (!dbc.getProjectId().isNullUUID()) {
8253            // user modified event is not needed
8254            return;
8255        }
8256        // fire user modified event
8257        Map<String, Object> eventData = new HashMap<String, Object>();
8258        eventData.put(I_CmsEventListener.KEY_USER_ID, user.getId().toString());
8259        eventData.put(I_CmsEventListener.KEY_USER_NAME, user.getName());
8260        eventData.put(I_CmsEventListener.KEY_GROUP_ID, group.getId().toString());
8261        eventData.put(I_CmsEventListener.KEY_GROUP_NAME, group.getName());
8262        eventData.put(
8263            I_CmsEventListener.KEY_USER_ACTION,
8264            I_CmsEventListener.VALUE_USER_MODIFIED_ACTION_REMOVE_USER_FROM_GROUP);
8265        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_USER_MODIFIED, eventData));
8266
8267    }
8268
8269    /**
8270     * Repairs broken categories.<p>
8271     *
8272     * @param dbc the database context
8273     * @param projectId the project id
8274     * @param resource the resource to repair the categories for
8275     *
8276     * @throws CmsException if something goes wrong
8277     */
8278    public void repairCategories(CmsDbContext dbc, CmsUUID projectId, CmsResource resource) throws CmsException {
8279
8280        CmsObject cms = OpenCms.initCmsObject(new CmsObject(getSecurityManager(), dbc.getRequestContext()));
8281        cms.getRequestContext().setSiteRoot("");
8282        cms.getRequestContext().setCurrentProject(readProject(dbc, projectId));
8283        CmsCategoryService.getInstance().repairRelations(cms, resource);
8284    }
8285
8286    /**
8287     * Replaces the content, type and properties of a resource.<p>
8288     *
8289     * @param dbc the current database context
8290     * @param resource the name of the resource to apply this operation to
8291     * @param type the new type of the resource
8292     * @param content the new content of the resource
8293     * @param properties the new properties of the resource
8294     *
8295     * @throws CmsException if something goes wrong
8296     *
8297     * @see CmsObject#replaceResource(String, int, byte[], List)
8298     * @see I_CmsResourceType#replaceResource(CmsObject, CmsSecurityManager, CmsResource, int, byte[], List)
8299     */
8300    public void replaceResource(
8301        CmsDbContext dbc,
8302        CmsResource resource,
8303        int type,
8304        byte[] content,
8305        List<CmsProperty> properties) throws CmsException {
8306
8307        // replace the existing with the new file content
8308        getVfsDriver(dbc).replaceResource(dbc, resource, content, type);
8309
8310        if ((properties != null) && !properties.isEmpty()) {
8311            // write the properties
8312            getVfsDriver(dbc).writePropertyObjects(dbc, dbc.currentProject(), resource, properties);
8313            m_monitor.flushCache(CmsMemoryMonitor.CacheType.PROPERTY, CmsMemoryMonitor.CacheType.PROPERTY_LIST);
8314        }
8315
8316        // update the resource state
8317        if (resource.getState().isUnchanged()) {
8318            resource.setState(CmsResource.STATE_CHANGED);
8319        }
8320        resource.setUserLastModified(dbc.currentUser().getId());
8321
8322        // log it
8323        log(
8324            dbc,
8325            new CmsLogEntry(
8326                dbc,
8327                resource.getStructureId(),
8328                CmsLogEntryType.RESOURCE_CONTENT_MODIFIED,
8329                new String[] {resource.getRootPath()}),
8330            false);
8331
8332        setDateLastModified(dbc, resource, System.currentTimeMillis());
8333
8334        getVfsDriver(dbc).writeResourceState(dbc, dbc.currentProject(), resource, UPDATE_RESOURCE, false);
8335
8336        deleteRelationsWithSiblings(dbc, resource);
8337
8338        // clear the cache
8339        m_monitor.clearResourceCache();
8340
8341        if ((properties != null) && !properties.isEmpty()) {
8342            // resource and properties were modified
8343            OpenCms.fireCmsEvent(
8344                new CmsEvent(
8345                    I_CmsEventListener.EVENT_RESOURCE_AND_PROPERTIES_MODIFIED,
8346                    Collections.<String, Object> singletonMap(I_CmsEventListener.KEY_RESOURCE, resource)));
8347        } else {
8348            // only the resource was modified
8349            Map<String, Object> data = new HashMap<String, Object>(2);
8350            data.put(I_CmsEventListener.KEY_RESOURCE, resource);
8351            data.put(I_CmsEventListener.KEY_CHANGE, new Integer(CHANGED_RESOURCE | CHANGED_CONTENT));
8352            OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_MODIFIED, data));
8353        }
8354    }
8355
8356    /**
8357     * Resets the password for a specified user.<p>
8358     *
8359     * @param dbc the current database context
8360     * @param username the name of the user
8361     * @param oldPassword the old password
8362     * @param newPassword the new password
8363     *
8364     * @throws CmsException if the user data could not be read from the database
8365     * @throws CmsSecurityException if the specified username and old password could not be verified
8366     */
8367    public void resetPassword(CmsDbContext dbc, String username, String oldPassword, String newPassword)
8368    throws CmsException, CmsSecurityException {
8369
8370        if ((oldPassword != null) && (newPassword != null)) {
8371
8372            CmsUser user = null;
8373
8374            validatePassword(newPassword);
8375
8376            // read the user as a system user to verify that the specified old password is correct
8377            try {
8378                user = getUserDriver(dbc).readUser(dbc, username, oldPassword, null);
8379            } catch (CmsDbEntryNotFoundException e) {
8380                throw new CmsDataAccessException(Messages.get().container(Messages.ERR_RESET_PASSWORD_1, username), e);
8381            }
8382
8383            if ((user == null) || user.isManaged()) {
8384                throw new CmsDataAccessException(Messages.get().container(Messages.ERR_RESET_PASSWORD_1, username));
8385            }
8386
8387            getUserDriver(dbc).writePassword(dbc, username, oldPassword, newPassword);
8388            user.getAdditionalInfo().put(
8389                CmsUserSettings.ADDITIONAL_INFO_LAST_PASSWORD_CHANGE,
8390                "" + System.currentTimeMillis());
8391            getUserDriver(dbc).writeUser(dbc, user);
8392
8393            if (!dbc.getProjectId().isNullUUID()) {
8394                // user modified event is not needed
8395                return;
8396            }
8397            // fire user modified event
8398            Map<String, Object> eventData = new HashMap<String, Object>();
8399            eventData.put(I_CmsEventListener.KEY_USER_ID, user.getId().toString());
8400            eventData.put(
8401                I_CmsEventListener.KEY_USER_ACTION,
8402                I_CmsEventListener.VALUE_USER_MODIFIED_ACTION_RESET_PASSWORD);
8403            OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_USER_MODIFIED, eventData));
8404
8405        } else if (CmsStringUtil.isEmpty(oldPassword)) {
8406            throw new CmsDataAccessException(Messages.get().container(Messages.ERR_PWD_OLD_MISSING_0));
8407        } else if (CmsStringUtil.isEmpty(newPassword)) {
8408            throw new CmsDataAccessException(Messages.get().container(Messages.ERR_PWD_NEW_MISSING_0));
8409        }
8410    }
8411
8412    /**
8413     * Restores a deleted resource identified by its structure id from the historical archive.<p>
8414     *
8415     * @param dbc the current database context
8416     * @param structureId the structure id of the resource to restore
8417     *
8418     * @throws CmsException if something goes wrong
8419     *
8420     * @see CmsObject#restoreDeletedResource(CmsUUID)
8421     */
8422    public void restoreDeletedResource(CmsDbContext dbc, CmsUUID structureId) throws CmsException {
8423
8424        // get the last version, which should be the deleted one
8425        int version = getHistoryDriver(dbc).readLastVersion(dbc, structureId);
8426        // get that version
8427        I_CmsHistoryResource histRes = getHistoryDriver(dbc).readResource(dbc, structureId, version);
8428
8429        // check the parent path
8430        CmsResource parent;
8431        try {
8432            // try to read the parent resource by id
8433            parent = getVfsDriver(dbc).readResource(dbc, dbc.currentProject().getUuid(), histRes.getParentId(), true);
8434        } catch (CmsVfsResourceNotFoundException e) {
8435            // if not found try to read the parent resource by name
8436            try {
8437                // try to read the parent resource by id
8438                parent = getVfsDriver(dbc).readResource(
8439                    dbc,
8440                    dbc.currentProject().getUuid(),
8441                    CmsResource.getParentFolder(histRes.getRootPath()),
8442                    true);
8443            } catch (CmsVfsResourceNotFoundException e1) {
8444                // if not found try to restore the parent resource
8445                restoreDeletedResource(dbc, histRes.getParentId());
8446                parent = readResource(dbc, histRes.getParentId(), CmsResourceFilter.IGNORE_EXPIRATION);
8447            }
8448        }
8449        // check write permissions
8450        m_securityManager.checkPermissions(
8451            dbc,
8452            parent,
8453            CmsPermissionSet.ACCESS_WRITE,
8454            false,
8455            CmsResourceFilter.IGNORE_EXPIRATION);
8456
8457        // check the name
8458        String path = CmsResource.getParentFolder(histRes.getRootPath()); // path
8459        String resName = CmsResource.getName(histRes.getRootPath()); // name
8460        String ext = "";
8461        if (resName.charAt(resName.length() - 1) == '/') {
8462            resName = resName.substring(0, resName.length() - 1);
8463        } else {
8464            ext = CmsFileUtil.getExtension(resName); // extension
8465        }
8466        String nameWOExt = resName.substring(0, resName.length() - ext.length()); // name without extension
8467        for (int i = 1; true; i++) {
8468            try {
8469                readResource(dbc, path + resName, CmsResourceFilter.ALL);
8470                resName = nameWOExt + "_" + i + ext;
8471                // try the next resource name with following schema: path/name_{i}.ext
8472            } catch (CmsVfsResourceNotFoundException e) {
8473                // ok, we found a not used resource name
8474                break;
8475            }
8476        }
8477
8478        // check structure id
8479        CmsUUID id = structureId;
8480        if (getVfsDriver(dbc).validateStructureIdExists(dbc, dbc.currentProject().getUuid(), structureId)) {
8481            // should never happen, but if already exists create a new one
8482            id = new CmsUUID();
8483        }
8484
8485        byte[] contents = null;
8486        boolean isFolder = true;
8487
8488        // do we need the contents?
8489        if (histRes instanceof CmsFile) {
8490            contents = ((CmsFile)histRes).getContents();
8491            if ((contents == null) || (contents.length == 0)) {
8492                contents = getHistoryDriver(dbc).readContent(dbc, histRes.getResourceId(), histRes.getPublishTag());
8493            }
8494            isFolder = false;
8495        }
8496
8497        // now read the historical properties
8498        List<CmsProperty> properties = getHistoryDriver(dbc).readProperties(dbc, histRes);
8499
8500        // create the object to create
8501        CmsResource newResource = new CmsResource(
8502            id,
8503            histRes.getResourceId(),
8504            path + resName,
8505            histRes.getTypeId(),
8506            isFolder,
8507            histRes.getFlags(),
8508            dbc.currentProject().getUuid(),
8509            CmsResource.STATE_NEW,
8510            histRes.getDateCreated(),
8511            histRes.getUserCreated(),
8512            histRes.getDateLastModified(),
8513            dbc.currentUser().getId(),
8514            histRes.getDateReleased(),
8515            histRes.getDateExpired(),
8516            histRes.getSiblingCount(),
8517            histRes.getLength(),
8518            histRes.getDateContent(),
8519            histRes.getVersion());
8520
8521        // log it
8522        log(
8523            dbc,
8524            new CmsLogEntry(
8525                dbc,
8526                newResource.getStructureId(),
8527                CmsLogEntryType.RESOURCE_RESTORE_DELETED,
8528                new String[] {newResource.getRootPath()}),
8529            false);
8530
8531        // prevent the date last modified is set to the current time
8532        newResource.setDateLastModified(newResource.getDateLastModified());
8533        // restore the resource!
8534        CmsResource resource = createResource(dbc, path + resName, newResource, contents, properties, true);
8535        // set resource state to changed
8536        newResource.setState(CmsResource.STATE_CHANGED);
8537        getVfsDriver(dbc).writeResourceState(dbc, dbc.currentProject(), newResource, UPDATE_RESOURCE_STATE, false);
8538        newResource.setState(CmsResource.STATE_NEW);
8539        // fire the event
8540        Map<String, Object> data = new HashMap<String, Object>(2);
8541        data.put(I_CmsEventListener.KEY_RESOURCE, resource);
8542        data.put(I_CmsEventListener.KEY_CHANGE, new Integer(CHANGED_RESOURCE | CHANGED_CONTENT));
8543        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_MODIFIED, data));
8544    }
8545
8546    /**
8547     * Restores a resource in the current project with a version from the historical archive.<p>
8548     *
8549     * @param dbc the current database context
8550     * @param resource the resource to restore from the archive
8551     * @param version the version number to restore from the archive
8552     *
8553     * @throws CmsException if something goes wrong
8554     *
8555     * @see CmsObject#restoreResourceVersion(CmsUUID, int)
8556     * @see I_CmsResourceType#restoreResource(CmsObject, CmsSecurityManager, CmsResource, int)
8557     */
8558    public void restoreResource(CmsDbContext dbc, CmsResource resource, int version) throws CmsException {
8559
8560        I_CmsHistoryResource historyResource = readResource(dbc, resource, version);
8561        CmsResourceState state = CmsResource.STATE_CHANGED;
8562        if (resource.getState().isNew()) {
8563            state = CmsResource.STATE_NEW;
8564        }
8565        int newVersion = resource.getVersion();
8566        if (resource.getState().isUnchanged()) {
8567            newVersion++;
8568        }
8569        CmsResource newResource = null;
8570        // is the resource a file?
8571        if (historyResource instanceof CmsFile) {
8572            // get the historical up flags
8573            int flags = historyResource.getFlags();
8574            if (resource.isLabeled()) {
8575                // set the flag for labeled links on the restored file
8576                flags |= CmsResource.FLAG_LABELED;
8577            }
8578            CmsFile newFile = new CmsFile(
8579                resource.getStructureId(),
8580                resource.getResourceId(),
8581                resource.getRootPath(),
8582                historyResource.getTypeId(),
8583                flags,
8584                dbc.currentProject().getUuid(),
8585                state,
8586                resource.getDateCreated(),
8587                historyResource.getUserCreated(),
8588                resource.getDateLastModified(),
8589                dbc.currentUser().getId(),
8590                historyResource.getDateReleased(),
8591                historyResource.getDateExpired(),
8592                resource.getSiblingCount(),
8593                historyResource.getLength(),
8594                historyResource.getDateContent(),
8595                newVersion,
8596                readFile(dbc, (CmsHistoryFile)historyResource).getContents());
8597
8598            // log it
8599            log(
8600                dbc,
8601                new CmsLogEntry(
8602                    dbc,
8603                    newFile.getStructureId(),
8604                    CmsLogEntryType.RESOURCE_HISTORY,
8605                    new String[] {newFile.getRootPath()}),
8606                false);
8607
8608            newResource = writeFile(dbc, newFile);
8609        } else {
8610            // it is a folder!
8611            newResource = new CmsFolder(
8612                resource.getStructureId(),
8613                resource.getResourceId(),
8614                resource.getRootPath(),
8615                historyResource.getTypeId(),
8616                historyResource.getFlags(),
8617                dbc.currentProject().getUuid(),
8618                state,
8619                resource.getDateCreated(),
8620                historyResource.getUserCreated(),
8621                resource.getDateLastModified(),
8622                dbc.currentUser().getId(),
8623                historyResource.getDateReleased(),
8624                historyResource.getDateExpired(),
8625                newVersion);
8626
8627            // log it
8628            log(
8629                dbc,
8630                new CmsLogEntry(
8631                    dbc,
8632                    newResource.getStructureId(),
8633                    CmsLogEntryType.RESOURCE_HISTORY,
8634                    new String[] {newResource.getRootPath()}),
8635                false);
8636
8637            writeResource(dbc, newResource);
8638        }
8639        if (newResource != null) {
8640            // now read the historical properties
8641            List<CmsProperty> historyProperties = getHistoryDriver(dbc).readProperties(dbc, historyResource);
8642            // remove all properties
8643            deleteAllProperties(dbc, newResource.getRootPath());
8644            // write them to the restored resource
8645            writePropertyObjects(dbc, newResource, historyProperties, false);
8646
8647            m_monitor.clearResourceCache();
8648        }
8649
8650        Map<String, Object> data = new HashMap<String, Object>(2);
8651        data.put(I_CmsEventListener.KEY_RESOURCE, resource);
8652        data.put(I_CmsEventListener.KEY_CHANGE, new Integer(CHANGED_RESOURCE | CHANGED_CONTENT));
8653        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_MODIFIED, data));
8654    }
8655
8656    /**
8657     * Saves a list of aliases for the same structure id, replacing any aliases for the same structure id.<p>
8658     *
8659     * @param dbc the current database context
8660     * @param project the current project
8661     * @param structureId the structure id for which the aliases should be saved
8662     * @param aliases the list of aliases to save
8663     *
8664     * @throws CmsException if something goes wrong
8665     */
8666    public void saveAliases(CmsDbContext dbc, CmsProject project, CmsUUID structureId, List<CmsAlias> aliases)
8667    throws CmsException {
8668
8669        for (CmsAlias alias : aliases) {
8670            if (!structureId.equals(alias.getStructureId())) {
8671                throw new IllegalArgumentException("Aliases to replace must have the same structure id!");
8672            }
8673        }
8674        I_CmsVfsDriver vfsDriver = getVfsDriver(dbc);
8675        vfsDriver.deleteAliases(dbc, project, new CmsAliasFilter(null, null, structureId));
8676        for (CmsAlias alias : aliases) {
8677            String aliasPath = alias.getAliasPath();
8678            if (CmsAlias.ALIAS_PATTERN.matcher(aliasPath).matches()) {
8679                vfsDriver.insertAlias(dbc, project, alias);
8680            } else {
8681                LOG.error("Invalid alias path: " + aliasPath);
8682            }
8683        }
8684    }
8685
8686    /**
8687     * Replaces the complete list of rewrite aliases for a given site root.<p>
8688     *
8689     * @param dbc the current database context
8690     * @param siteRoot the site root for which the rewrite aliases should be replaced
8691     * @param newAliases the new aliases for the given site root
8692     * @throws CmsException if something goes wrong
8693     */
8694    public void saveRewriteAliases(CmsDbContext dbc, String siteRoot, List<CmsRewriteAlias> newAliases)
8695    throws CmsException {
8696
8697        CmsRewriteAliasFilter filter = new CmsRewriteAliasFilter().setSiteRoot(siteRoot);
8698        getVfsDriver(dbc).deleteRewriteAliases(dbc, filter);
8699        getVfsDriver(dbc).insertRewriteAliases(dbc, newAliases);
8700    }
8701
8702    /**
8703     * Searches for users which fit the given criteria.<p>
8704     *
8705     * @param dbc the database context
8706     * @param searchParams the search criteria
8707     *
8708     * @return the users which fit the search criteria
8709     *
8710     * @throws CmsDataAccessException if something goes wrong
8711     */
8712    public List<CmsUser> searchUsers(CmsDbContext dbc, CmsUserSearchParameters searchParams
8713
8714    ) throws CmsDataAccessException {
8715
8716        return getUserDriver(dbc).searchUsers(dbc, searchParams);
8717    }
8718
8719    /**
8720     * Changes the "expire" date of a resource.<p>
8721     *
8722     * @param dbc the current database context
8723     * @param resource the resource to touch
8724     * @param dateExpired the new expire date of the resource
8725     *
8726     * @throws CmsDataAccessException if something goes wrong
8727     *
8728     * @see CmsObject#setDateExpired(String, long, boolean)
8729     * @see I_CmsResourceType#setDateExpired(CmsObject, CmsSecurityManager, CmsResource, long, boolean)
8730     */
8731    public void setDateExpired(CmsDbContext dbc, CmsResource resource, long dateExpired) throws CmsDataAccessException {
8732
8733        resource.setDateExpired(dateExpired);
8734        if (resource.getState().isUnchanged()) {
8735            resource.setState(CmsResource.STATE_CHANGED);
8736        }
8737        getVfsDriver(dbc).writeResourceState(dbc, dbc.currentProject(), resource, UPDATE_STRUCTURE, false);
8738
8739        // modify the last modified project reference
8740        getVfsDriver(dbc).writeResourceState(dbc, dbc.currentProject(), resource, UPDATE_RESOURCE_PROJECT, false);
8741        // log
8742        log(
8743            dbc,
8744            new CmsLogEntry(
8745                dbc,
8746                resource.getStructureId(),
8747                CmsLogEntryType.RESOURCE_DATE_EXPIRED,
8748                new String[] {resource.getRootPath()}),
8749            false);
8750
8751        // clear the cache
8752        m_monitor.clearResourceCache();
8753
8754        // fire the event
8755        Map<String, Object> data = new HashMap<String, Object>(2);
8756        data.put(I_CmsEventListener.KEY_RESOURCE, resource);
8757        data.put(I_CmsEventListener.KEY_CHANGE, new Integer(CHANGED_TIMEFRAME));
8758        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_MODIFIED, data));
8759    }
8760
8761    /**
8762     * Changes the "last modified" timestamp of a resource.<p>
8763     *
8764     * @param dbc the current database context
8765     * @param resource the resource to touch
8766     * @param dateLastModified the new last modified date of the resource
8767     *
8768     * @throws CmsDataAccessException if something goes wrong
8769     *
8770     * @see CmsObject#setDateLastModified(String, long, boolean)
8771     * @see I_CmsResourceType#setDateLastModified(CmsObject, CmsSecurityManager, CmsResource, long, boolean)
8772     */
8773    public void setDateLastModified(CmsDbContext dbc, CmsResource resource, long dateLastModified)
8774    throws CmsDataAccessException {
8775
8776        // modify the last modification date
8777        resource.setDateLastModified(dateLastModified);
8778        if (resource.getState().isUnchanged()) {
8779            resource.setState(CmsResource.STATE_CHANGED);
8780        } else if (resource.getState().isNew() && (resource.getSiblingCount() > 1)) {
8781            // in case of new resources with siblings make sure the state is correct
8782            resource.setState(CmsResource.STATE_CHANGED);
8783        }
8784        resource.setUserLastModified(dbc.currentUser().getId());
8785        getVfsDriver(dbc).writeResourceState(dbc, dbc.currentProject(), resource, UPDATE_RESOURCE, false);
8786
8787        log(
8788            dbc,
8789            new CmsLogEntry(
8790                dbc,
8791                resource.getStructureId(),
8792                CmsLogEntryType.RESOURCE_TOUCHED,
8793                new String[] {resource.getRootPath()}),
8794            false);
8795
8796        // clear the cache
8797        m_monitor.clearResourceCache();
8798
8799        // fire the event
8800        Map<String, Object> data = new HashMap<String, Object>(2);
8801        data.put(I_CmsEventListener.KEY_RESOURCE, resource);
8802        data.put(I_CmsEventListener.KEY_CHANGE, new Integer(CHANGED_LASTMODIFIED));
8803        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_MODIFIED, data));
8804    }
8805
8806    /**
8807     * Changes the "release" date of a resource.<p>
8808     *
8809     * @param dbc the current database context
8810     * @param resource the resource to touch
8811     * @param dateReleased the new release date of the resource
8812     *
8813     * @throws CmsDataAccessException if something goes wrong
8814     *
8815     * @see CmsObject#setDateReleased(String, long, boolean)
8816     * @see I_CmsResourceType#setDateReleased(CmsObject, CmsSecurityManager, CmsResource, long, boolean)
8817     */
8818    public void setDateReleased(CmsDbContext dbc, CmsResource resource, long dateReleased)
8819    throws CmsDataAccessException {
8820
8821        // modify the last modification date
8822        resource.setDateReleased(dateReleased);
8823        if (resource.getState().isUnchanged()) {
8824            resource.setState(CmsResource.STATE_CHANGED);
8825        }
8826        getVfsDriver(dbc).writeResourceState(dbc, dbc.currentProject(), resource, UPDATE_STRUCTURE, false);
8827
8828        // modify the last modified project reference
8829        getVfsDriver(dbc).writeResourceState(dbc, dbc.currentProject(), resource, UPDATE_RESOURCE_PROJECT, false);
8830        // log it
8831        log(
8832            dbc,
8833            new CmsLogEntry(
8834                dbc,
8835                resource.getStructureId(),
8836                CmsLogEntryType.RESOURCE_DATE_RELEASED,
8837                new String[] {resource.getRootPath()}),
8838            false);
8839
8840        // clear the cache
8841        m_monitor.clearResourceCache();
8842
8843        // fire the event
8844        Map<String, Object> data = new HashMap<String, Object>(2);
8845        data.put(I_CmsEventListener.KEY_RESOURCE, resource);
8846        data.put(I_CmsEventListener.KEY_CHANGE, new Integer(CHANGED_TIMEFRAME));
8847        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_MODIFIED, data));
8848    }
8849
8850    /**
8851     * Sets a new parent group for an already existing group.<p>
8852     *
8853     * @param dbc the current database context
8854     * @param groupName the name of the group that should be written
8855     * @param parentGroupName the name of the parent group to set,
8856     *                      or <code>null</code> if the parent
8857     *                      group should be deleted.
8858     *
8859     * @throws CmsException if operation was not successful
8860     * @throws CmsDataAccessException if the group with <code>groupName</code> could not be read from VFS
8861     */
8862    public void setParentGroup(CmsDbContext dbc, String groupName, String parentGroupName)
8863    throws CmsException, CmsDataAccessException {
8864
8865        CmsGroup group = readGroup(dbc, groupName);
8866        CmsUUID parentGroupId = CmsUUID.getNullUUID();
8867
8868        // if the group exists, use its id, else set to unknown.
8869        if (parentGroupName != null) {
8870            parentGroupId = readGroup(dbc, parentGroupName).getId();
8871        }
8872
8873        group.setParentId(parentGroupId);
8874
8875        // write the changes to the cms
8876        writeGroup(dbc, group);
8877    }
8878
8879    /**
8880     * Sets the password for a user.<p>
8881     *
8882     * @param dbc the current database context
8883     * @param username the name of the user
8884     * @param newPassword the new password
8885     *
8886     * @throws CmsException if operation was not successful
8887     * @throws CmsIllegalArgumentException if the user with the <code>username</code> was not found
8888     */
8889    public void setPassword(CmsDbContext dbc, String username, String newPassword)
8890    throws CmsException, CmsIllegalArgumentException {
8891
8892        validatePassword(newPassword);
8893
8894        // read the user as a system user to verify that the specified old password is correct
8895        CmsUser user = getUserDriver(dbc).readUser(dbc, username);
8896        // only continue if not found and read user from web might succeed
8897        getUserDriver(dbc).writePassword(dbc, username, null, newPassword);
8898        user.getAdditionalInfo().put(
8899            CmsUserSettings.ADDITIONAL_INFO_LAST_PASSWORD_CHANGE,
8900            "" + System.currentTimeMillis());
8901        getUserDriver(dbc).writeUser(dbc, user);
8902    }
8903
8904    /**
8905     * Marks a subscribed resource as deleted.<p>
8906     *
8907     * @param dbc the database context
8908     * @param poolName the name of the database pool to use
8909     * @param resource the subscribed resource to mark as deleted
8910     *
8911     * @throws CmsException if something goes wrong
8912     */
8913    public void setSubscribedResourceAsDeleted(CmsDbContext dbc, String poolName, CmsResource resource)
8914    throws CmsException {
8915
8916        getSubscriptionDriver().setSubscribedResourceAsDeleted(dbc, poolName, resource);
8917    }
8918
8919    /**
8920     * Moves an user to the given organizational unit.<p>
8921     *
8922     * @param dbc the current db context
8923     * @param orgUnit the organizational unit to add the resource to
8924     * @param user the user that is to be moved to the organizational unit
8925     *
8926     * @throws CmsException if something goes wrong
8927     *
8928     * @see org.opencms.security.CmsOrgUnitManager#setUsersOrganizationalUnit(CmsObject, String, String)
8929     */
8930    public void setUsersOrganizationalUnit(CmsDbContext dbc, CmsOrganizationalUnit orgUnit, CmsUser user)
8931    throws CmsException {
8932
8933        if (!getGroupsOfUser(dbc, user.getName(), false).isEmpty()) {
8934            throw new CmsDbConsistencyException(
8935                Messages.get().container(Messages.ERR_ORGUNIT_MOVE_USER_2, orgUnit.getName(), user.getName()));
8936        }
8937
8938        // move the principal
8939        getUserDriver(dbc).setUsersOrganizationalUnit(dbc, orgUnit, user);
8940        // remove the principal from cache
8941        m_monitor.clearUserCache(user);
8942
8943        if (!dbc.getProjectId().isNullUUID()) {
8944            // user modified event is not needed
8945            return;
8946        }
8947        // fire user modified event
8948        Map<String, Object> eventData = new HashMap<String, Object>();
8949        eventData.put(I_CmsEventListener.KEY_USER_ID, user.getId().toString());
8950        eventData.put(I_CmsEventListener.KEY_OU_NAME, user.getOuFqn());
8951        eventData.put(I_CmsEventListener.KEY_USER_ACTION, I_CmsEventListener.VALUE_USER_MODIFIED_ACTION_SET_OU);
8952        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_USER_MODIFIED, eventData));
8953    }
8954
8955    /**
8956     * Subscribes the user or group to the resource.<p>
8957     *
8958     * @param dbc the database context
8959     * @param poolName the name of the database pool to use
8960     * @param principal the principal that subscribes to the resource
8961     * @param resource the resource to subscribe to
8962     *
8963     * @throws CmsException if something goes wrong
8964     */
8965    public void subscribeResourceFor(CmsDbContext dbc, String poolName, CmsPrincipal principal, CmsResource resource)
8966    throws CmsException {
8967
8968        getSubscriptionDriver().subscribeResourceFor(dbc, poolName, principal, resource);
8969    }
8970
8971    /**
8972     * Undelete the resource.<p>
8973     *
8974     * @param dbc the current database context
8975     * @param resource the name of the resource to apply this operation to
8976     *
8977     * @throws CmsException if something goes wrong
8978     *
8979     * @see CmsObject#undeleteResource(String, boolean)
8980     * @see I_CmsResourceType#undelete(CmsObject, CmsSecurityManager, CmsResource, boolean)
8981     */
8982    public void undelete(CmsDbContext dbc, CmsResource resource) throws CmsException {
8983
8984        if (!resource.getState().isDeleted()) {
8985            throw new CmsVfsException(
8986                Messages.get().container(
8987                    Messages.ERR_UNDELETE_FOR_RESOURCE_DELETED_1,
8988                    dbc.removeSiteRoot(resource.getRootPath())));
8989        }
8990
8991        // set the state to changed
8992        resource.setState(CmsResourceState.STATE_CHANGED);
8993        // perform the changes
8994        updateState(dbc, resource, false);
8995        // log it
8996        log(dbc, new CmsLogEntry(
8997            dbc,
8998            resource.getStructureId(),
8999            CmsLogEntryType.RESOURCE_UNDELETED,
9000            new String[] {resource.getRootPath()}), false);
9001        // clear the cache
9002        m_monitor.clearResourceCache();
9003
9004        // fire change event
9005        Map<String, Object> data = new HashMap<String, Object>(2);
9006        data.put(I_CmsEventListener.KEY_RESOURCE, resource);
9007        data.put(I_CmsEventListener.KEY_CHANGE, new Integer(CHANGED_RESOURCE));
9008        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_MODIFIED, data));
9009    }
9010
9011    /**
9012     * Undos all changes in the resource by restoring the version from the
9013     * online project to the current offline project.<p>
9014     *
9015     * @param dbc the current database context
9016     * @param resource the name of the resource to apply this operation to
9017     * @param mode the undo mode, one of the <code>{@link org.opencms.file.CmsResource.CmsResourceUndoMode}#UNDO_XXX</code> constants
9018     *      please note that the recursive flag is ignored at this level
9019     *
9020     * @throws CmsException if something goes wrong
9021     *
9022     * @see CmsObject#undoChanges(String, CmsResource.CmsResourceUndoMode)
9023     * @see I_CmsResourceType#undoChanges(CmsObject, CmsSecurityManager, CmsResource, CmsResource.CmsResourceUndoMode)
9024     */
9025    public void undoChanges(CmsDbContext dbc, CmsResource resource, CmsResource.CmsResourceUndoMode mode)
9026    throws CmsException {
9027
9028        if (resource.getState().isNew()) {
9029            // undo changes is impossible on a new resource
9030            throw new CmsVfsException(Messages.get().container(Messages.ERR_UNDO_CHANGES_FOR_RESOURCE_NEW_0));
9031        }
9032
9033        // we need this for later use
9034        CmsProject onlineProject = readProject(dbc, CmsProject.ONLINE_PROJECT_ID);
9035        // read the resource from the online project
9036        CmsResource onlineResource = getVfsDriver(dbc).readResource(
9037            dbc,
9038            CmsProject.ONLINE_PROJECT_ID,
9039            resource.getStructureId(),
9040            true);
9041
9042        CmsResource onlineResourceByPath = null;
9043        try {
9044            // this is needed to figure out if a moved resource overwrote a deleted one
9045            onlineResourceByPath = getVfsDriver(dbc).readResource(
9046                dbc,
9047                CmsProject.ONLINE_PROJECT_ID,
9048                resource.getRootPath(),
9049                true);
9050
9051            // force undo move operation if needed
9052            if (!mode.isUndoMove() && !onlineResourceByPath.getRootPath().equals(onlineResource.getRootPath())) {
9053                mode = mode.includeMove();
9054            }
9055        } catch (Exception e) {
9056            // ok
9057        }
9058
9059        boolean moved = !onlineResource.getRootPath().equals(resource.getRootPath());
9060        // undo move operation if required
9061        if (moved && mode.isUndoMove()) {
9062            moveResource(dbc, resource, onlineResource.getRootPath(), true);
9063            if ((onlineResourceByPath != null)
9064                && !onlineResourceByPath.getRootPath().equals(onlineResource.getRootPath())) {
9065                // was moved over deleted, so the deleted file has to be undone
9066                undoContentChanges(dbc, onlineProject, null, onlineResourceByPath, CmsResource.STATE_UNCHANGED, true);
9067            }
9068        }
9069        // undo content changes
9070        CmsResourceState newState = CmsResource.STATE_UNCHANGED;
9071        if (moved && !mode.isUndoMove()) {
9072            newState = CmsResource.STATE_CHANGED;
9073        }
9074        undoContentChanges(dbc, onlineProject, resource, onlineResource, newState, moved && mode.isUndoMove());
9075        // because undoContentChanges deletes the offline resource internally, we have
9076        // to write an entry to the log table to prevent the resource from appearing in the
9077        // user's publish list.
9078        log(
9079            dbc,
9080            new CmsLogEntry(
9081                dbc,
9082                resource.getStructureId(),
9083                CmsLogEntryType.RESOURCE_CHANGES_UNDONE,
9084                new String[] {resource.getRootPath()}),
9085            true);
9086
9087    }
9088
9089    /**
9090     * Unlocks all resources in the given project.<p>
9091     *
9092     * @param project the project to unlock the resources in
9093     */
9094    public void unlockProject(CmsProject project) {
9095
9096        // unlock all resources in the project
9097        m_lockManager.removeResourcesInProject(project.getUuid(), false);
9098        m_monitor.clearResourceCache();
9099        m_monitor.flushCache(CmsMemoryMonitor.CacheType.PROJECT, CmsMemoryMonitor.CacheType.PERMISSION);
9100    }
9101
9102    /**
9103     * Unlocks a resource.<p>
9104     *
9105     * @param dbc the current database context
9106     * @param resource the resource to unlock
9107     * @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
9108     * @param removeSystemLock <code>true</code>, if you also want to remove system locks
9109     *
9110     * @throws CmsException if something goes wrong
9111     *
9112     * @see CmsObject#unlockResource(String)
9113     * @see I_CmsResourceType#unlockResource(CmsObject, CmsSecurityManager, CmsResource)
9114     */
9115    public void unlockResource(CmsDbContext dbc, CmsResource resource, boolean force, boolean removeSystemLock)
9116    throws CmsException {
9117
9118        // update the resource cache
9119        m_monitor.clearResourceCache();
9120
9121        // now update lock status
9122        m_lockManager.removeResource(dbc, resource, force, removeSystemLock);
9123
9124        // we must also clear the permission cache
9125        m_monitor.flushCache(CmsMemoryMonitor.CacheType.PERMISSION);
9126
9127        // fire resource modification event
9128        Map<String, Object> data = new HashMap<String, Object>(2);
9129        data.put(I_CmsEventListener.KEY_RESOURCE, resource);
9130        data.put(I_CmsEventListener.KEY_CHANGE, new Integer(NOTHING_CHANGED));
9131        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_MODIFIED, data));
9132    }
9133
9134    /**
9135     * Unsubscribes all deleted resources that were deleted before the specified time stamp.<p>
9136     *
9137     * @param dbc the database context
9138     * @param poolName the name of the database pool to use
9139     * @param deletedTo the time stamp to which the resources have been deleted
9140     *
9141     * @throws CmsException if something goes wrong
9142     */
9143    public void unsubscribeAllDeletedResources(CmsDbContext dbc, String poolName, long deletedTo) throws CmsException {
9144
9145        getSubscriptionDriver().unsubscribeAllDeletedResources(dbc, poolName, deletedTo);
9146    }
9147
9148    /**
9149     * Unsubscribes the principal from all resources.<p>
9150     *
9151     * @param dbc the database context
9152     * @param poolName the name of the database pool to use
9153     * @param principal the principal that unsubscribes from all resources
9154     *
9155     * @throws CmsException if something goes wrong
9156     */
9157    public void unsubscribeAllResourcesFor(CmsDbContext dbc, String poolName, CmsPrincipal principal)
9158    throws CmsException {
9159
9160        getSubscriptionDriver().unsubscribeAllResourcesFor(dbc, poolName, principal);
9161
9162    }
9163
9164    /**
9165     * Unsubscribes the principal from the resource.<p>
9166     *
9167     * @param dbc the database context
9168     * @param poolName the name of the database pool to use
9169     * @param principal the principal that unsubscribes from the resource
9170     * @param resource the resource to unsubscribe from
9171     *
9172     * @throws CmsException if something goes wrong
9173     */
9174    public void unsubscribeResourceFor(CmsDbContext dbc, String poolName, CmsPrincipal principal, CmsResource resource)
9175    throws CmsException {
9176
9177        getSubscriptionDriver().unsubscribeResourceFor(dbc, poolName, principal, resource);
9178    }
9179
9180    /**
9181     * Unsubscribes all groups and users from the resource.<p>
9182     *
9183     * @param dbc the database context
9184     * @param poolName the name of the database pool to use
9185     * @param resource the resource to unsubscribe all groups and users from
9186     *
9187     * @throws CmsException if something goes wrong
9188     */
9189    public void unsubscribeResourceForAll(CmsDbContext dbc, String poolName, CmsResource resource) throws CmsException {
9190
9191        getSubscriptionDriver().unsubscribeResourceForAll(dbc, poolName, resource);
9192    }
9193
9194    /**
9195     * Update the export points.<p>
9196     *
9197     * All files and folders "inside" an export point are written.<p>
9198     *
9199     * @param dbc the current database context
9200     */
9201    public void updateExportPoints(CmsDbContext dbc) {
9202
9203        try {
9204            // read the export points and return immediately if there are no export points at all
9205            Set<CmsExportPoint> exportPoints = new HashSet<CmsExportPoint>();
9206            exportPoints.addAll(OpenCms.getExportPoints());
9207            exportPoints.addAll(OpenCms.getModuleManager().getExportPoints());
9208            if (exportPoints.size() == 0) {
9209                if (LOG.isWarnEnabled()) {
9210                    LOG.warn(Messages.get().getBundle().key(Messages.LOG_NO_EXPORT_POINTS_CONFIGURED_0));
9211                }
9212                return;
9213            }
9214
9215            // create the driver to write the export points
9216            CmsExportPointDriver exportPointDriver = new CmsExportPointDriver(exportPoints);
9217
9218            // the export point hash table contains RFS export paths keyed by their internal VFS paths
9219            Iterator<String> i = exportPointDriver.getExportPointPaths().iterator();
9220            I_CmsVfsDriver vfsDriver = getVfsDriver(dbc);
9221            while (i.hasNext()) {
9222                String currentExportPoint = i.next();
9223
9224                // print some report messages
9225                if (LOG.isInfoEnabled()) {
9226                    LOG.info(Messages.get().getBundle().key(Messages.LOG_WRITE_EXPORT_POINT_1, currentExportPoint));
9227                }
9228
9229                try {
9230                    CmsResourceFilter filter = CmsResourceFilter.DEFAULT;
9231                    List<CmsResource> resources = vfsDriver.readResourceTree(
9232                        dbc,
9233                        CmsProject.ONLINE_PROJECT_ID,
9234                        currentExportPoint,
9235                        filter.getType(),
9236                        filter.getState(),
9237                        filter.getModifiedAfter(),
9238                        filter.getModifiedBefore(),
9239                        filter.getReleaseAfter(),
9240                        filter.getReleaseBefore(),
9241                        filter.getExpireAfter(),
9242                        filter.getExpireBefore(),
9243                        CmsDriverManager.READMODE_INCLUDE_TREE
9244                            | (filter.excludeType() ? CmsDriverManager.READMODE_EXCLUDE_TYPE : 0)
9245                            | (filter.excludeState() ? CmsDriverManager.READMODE_EXCLUDE_STATE : 0));
9246
9247                    Iterator<CmsResource> j = resources.iterator();
9248                    while (j.hasNext()) {
9249                        CmsResource currentResource = j.next();
9250
9251                        if (currentResource.isFolder()) {
9252                            // export the folder
9253                            exportPointDriver.createFolder(currentResource.getRootPath(), currentExportPoint);
9254                        } else {
9255                            // try to create the exportpoint folder
9256                            exportPointDriver.createFolder(currentExportPoint, currentExportPoint);
9257                            byte[] onlineContent = vfsDriver.readContent(
9258                                dbc,
9259                                CmsProject.ONLINE_PROJECT_ID,
9260                                currentResource.getResourceId());
9261                            // export the file content online
9262                            exportPointDriver.writeFile(
9263                                currentResource.getRootPath(),
9264                                currentExportPoint,
9265                                onlineContent);
9266                        }
9267                    }
9268                } catch (CmsException e) {
9269                    // there might exist export points without corresponding resources in the VFS
9270                    // -> ignore exceptions which are not "resource not found" exception quiet here
9271                    if (e instanceof CmsVfsResourceNotFoundException) {
9272                        if (LOG.isErrorEnabled()) {
9273                            LOG.error(Messages.get().getBundle().key(Messages.LOG_UPDATE_EXORT_POINTS_ERROR_0), e);
9274                        }
9275                    }
9276                }
9277            }
9278        } catch (Exception e) {
9279            if (LOG.isErrorEnabled()) {
9280                LOG.error(Messages.get().getBundle().key(Messages.LOG_UPDATE_EXORT_POINTS_ERROR_0), e);
9281            }
9282        }
9283    }
9284
9285    /**
9286     * Logs everything that has not been written to DB jet.<p>
9287     *
9288     * @param dbc the current db context
9289     *
9290     * @throws CmsDataAccessException if something goes wrong
9291     */
9292    public void updateLog(CmsDbContext dbc) throws CmsDataAccessException {
9293
9294        synchronized (m_publishListUpdateLock) {
9295
9296            if (m_log.isEmpty()) {
9297                return;
9298            }
9299
9300            List<CmsLogEntry> log = new ArrayList<CmsLogEntry>(m_log);
9301            m_log.clear();
9302            String logTableEnabledStr = (String)OpenCms.getRuntimeProperty(PARAM_LOG_TABLE_ENABLED);
9303            if (Boolean.parseBoolean(logTableEnabledStr)) { // defaults to 'false' if value not set
9304                m_projectDriver.log(dbc, log);
9305            }
9306            CmsLogToPublishListChangeConverter converter = new CmsLogToPublishListChangeConverter();
9307            for (CmsLogEntry entry : log) {
9308                converter.add(entry);
9309            }
9310            m_projectDriver.deleteUserPublishListEntries(dbc, converter.getPublishListDeletions());
9311            m_projectDriver.writeUserPublishListEntries(dbc, converter.getPublishListAdditions());
9312        }
9313    }
9314
9315    /**
9316     * Updates/Creates the given relations for the given resource.<p>
9317     *
9318     * @param dbc the db context
9319     * @param resource the resource to update the relations for
9320     * @param links the links to consider for updating
9321     *
9322     * @throws CmsException if something goes wrong
9323     *
9324     * @see CmsSecurityManager#updateRelationsForResource(CmsRequestContext, CmsResource, List)
9325     */
9326    public void updateRelationsForResource(CmsDbContext dbc, CmsResource resource, List<CmsLink> links)
9327    throws CmsException {
9328
9329        deleteRelationsWithSiblings(dbc, resource);
9330
9331        // build the links again only if needed
9332        if ((links == null) || links.isEmpty()) {
9333            return;
9334        }
9335        // the set of written relations
9336        Set<CmsRelation> writtenRelations = new HashSet<CmsRelation>();
9337
9338        // create new relation information
9339        I_CmsVfsDriver vfsDriver = getVfsDriver(dbc);
9340        Iterator<CmsLink> itLinks = links.iterator();
9341        while (itLinks.hasNext()) {
9342            CmsLink link = itLinks.next();
9343            if (link.isInternal()) { // only update internal links
9344                if (CmsStringUtil.isEmptyOrWhitespaceOnly(link.getTarget())) {
9345                    // only an anchor
9346                    continue;
9347                }
9348                CmsUUID targetId = link.getStructureId();
9349                String destPath = link.getTarget();
9350
9351                if (targetId != null) {
9352                    // the link target may not be a VFS path even if the link id is a structure id,
9353                    // so if possible, we read the resource for the id and set the relation target to its
9354                    // real root path.
9355                    try {
9356                        CmsResource destRes = readResource(dbc, targetId, CmsResourceFilter.ALL);
9357                        destPath = destRes.getRootPath();
9358                    } catch (CmsVfsResourceNotFoundException e) {
9359                        // ignore
9360                    }
9361                }
9362
9363                CmsRelation originalRelation = new CmsRelation(
9364                    resource.getStructureId(),
9365                    resource.getRootPath(),
9366                    link.getStructureId(),
9367                    destPath,
9368                    link.getType());
9369
9370                // do not write twice the same relation
9371                if (writtenRelations.contains(originalRelation)) {
9372                    continue;
9373                }
9374                writtenRelations.add(originalRelation);
9375
9376                // TODO: it would be good to have the link locale to make the relation just to the right sibling
9377                // create the relations in content for all siblings
9378                Iterator<CmsResource> itSiblings = readSiblings(dbc, resource, CmsResourceFilter.ALL).iterator();
9379                while (itSiblings.hasNext()) {
9380                    CmsResource sibling = itSiblings.next();
9381                    CmsRelation relation = new CmsRelation(
9382                        sibling.getStructureId(),
9383                        sibling.getRootPath(),
9384                        originalRelation.getTargetId(),
9385                        originalRelation.getTargetPath(),
9386                        link.getType());
9387                    vfsDriver.createRelation(dbc, dbc.currentProject().getUuid(), relation);
9388                }
9389            }
9390        }
9391    }
9392
9393    /**
9394     * Returns <code>true</code> if a user is member of the given group.<p>
9395     *
9396     * @param dbc the current database context
9397     * @param username the name of the user to check
9398     * @param groupname the name of the group to check
9399     * @param readRoles if to read roles or groups
9400     *
9401     * @return <code>true</code>, if the user is in the group, <code>false</code> otherwise
9402     *
9403     * @throws CmsException if something goes wrong
9404     */
9405    public boolean userInGroup(CmsDbContext dbc, String username, String groupname, boolean readRoles)
9406    throws CmsException {
9407
9408        List<CmsGroup> groups = getGroupsOfUser(dbc, username, readRoles);
9409        for (int i = 0; i < groups.size(); i++) {
9410            CmsGroup group = groups.get(i);
9411            if (groupname.equals(group.getName()) || groupname.substring(1).equals(group.getName())) {
9412                return true;
9413            }
9414        }
9415        return false;
9416    }
9417
9418    /**
9419     * This method checks if a new password follows the rules for
9420     * new passwords, which are defined by a Class implementing the
9421     * <code>{@link org.opencms.security.I_CmsPasswordHandler}</code>
9422     * interface and configured in the opencms.properties file.<p>
9423     *
9424     * If this method throws no exception the password is valid.<p>
9425     *
9426     * @param password the new password that has to be checked
9427     *
9428     * @throws CmsSecurityException if the password is not valid
9429     */
9430    public void validatePassword(String password) throws CmsSecurityException {
9431
9432        OpenCms.getPasswordHandler().validatePassword(password);
9433    }
9434
9435    /**
9436     * Validates the relations for the given resources.<p>
9437     *
9438     * @param dbc the database context
9439     * @param publishList the resources to validate during publishing
9440     * @param report a report to write the messages to
9441     *
9442     * @return a map with lists of invalid links
9443     *          (<code>{@link org.opencms.relations.CmsRelation}}</code> objects)
9444     *          keyed by root paths
9445     *
9446     * @throws Exception if something goes wrong
9447     */
9448    public Map<String, List<CmsRelation>> validateRelations(
9449        CmsDbContext dbc,
9450        CmsPublishList publishList,
9451        I_CmsReport report) throws Exception {
9452
9453        return m_htmlLinkValidator.validateResources(dbc, publishList, report);
9454    }
9455
9456    /**
9457     * Writes an access control entries to a given resource.<p>
9458     *
9459     * @param dbc the current database context
9460     * @param resource the resource
9461     * @param ace the entry to write
9462     *
9463     * @throws CmsException if something goes wrong
9464     */
9465    public void writeAccessControlEntry(CmsDbContext dbc, CmsResource resource, CmsAccessControlEntry ace)
9466    throws CmsException {
9467
9468        // write the new ace
9469        getUserDriver(dbc).writeAccessControlEntry(dbc, dbc.currentProject(), ace);
9470
9471        // log it
9472        log(
9473            dbc,
9474            new CmsLogEntry(
9475                dbc,
9476                resource.getStructureId(),
9477                CmsLogEntryType.RESOURCE_PERMISSIONS,
9478                new String[] {resource.getRootPath()}),
9479            false);
9480
9481        // update the "last modified" information
9482        setDateLastModified(dbc, resource, resource.getDateLastModified());
9483
9484        // clear the cache
9485        m_monitor.clearAccessControlListCache();
9486
9487        // fire a resource modification event
9488        Map<String, Object> data = new HashMap<String, Object>(2);
9489        data.put(I_CmsEventListener.KEY_RESOURCE, resource);
9490        data.put(I_CmsEventListener.KEY_CHANGE, new Integer(CHANGED_ACCESSCONTROL));
9491        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_MODIFIED, data));
9492    }
9493
9494    /**
9495     * Writes all export points into the file system for the publish task
9496     * specified by trhe given publish history ID.<p>
9497     *
9498     * @param dbc the current database context
9499     * @param report an I_CmsReport instance to print output message, or null to write messages to the log file
9500     * @param publishHistoryId ID to identify the publish task in the publish history
9501     */
9502    public void writeExportPoints(CmsDbContext dbc, I_CmsReport report, CmsUUID publishHistoryId) {
9503
9504        boolean printReportHeaders = false;
9505        List<CmsPublishedResource> publishedResources = null;
9506        try {
9507            // read the "published resources" for the specified publish history ID
9508            publishedResources = getProjectDriver(dbc).readPublishedResources(dbc, publishHistoryId);
9509        } catch (CmsException e) {
9510            if (LOG.isErrorEnabled()) {
9511                LOG.error(
9512                    Messages.get().getBundle().key(Messages.ERR_READ_PUBLISHED_RESOURCES_FOR_ID_1, publishHistoryId),
9513                    e);
9514            }
9515        }
9516        if ((publishedResources == null) || publishedResources.isEmpty()) {
9517            if (LOG.isWarnEnabled()) {
9518                LOG.warn(Messages.get().getBundle().key(Messages.LOG_EMPTY_PUBLISH_HISTORY_1, publishHistoryId));
9519            }
9520            return;
9521        }
9522
9523        // read the export points and return immediately if there are no export points at all
9524        Set<CmsExportPoint> exportPoints = new HashSet<CmsExportPoint>();
9525        exportPoints.addAll(OpenCms.getExportPoints());
9526        exportPoints.addAll(OpenCms.getModuleManager().getExportPoints());
9527        if (exportPoints.size() == 0) {
9528            if (LOG.isWarnEnabled()) {
9529                LOG.warn(Messages.get().getBundle().key(Messages.LOG_NO_EXPORT_POINTS_CONFIGURED_0));
9530            }
9531            return;
9532        }
9533
9534        // create the driver to write the export points
9535        CmsExportPointDriver exportPointDriver = new CmsExportPointDriver(exportPoints);
9536
9537        // the report may be null if the export point write was started by an event
9538        if (report == null) {
9539            if (dbc.getRequestContext() != null) {
9540                report = new CmsLogReport(dbc.getRequestContext().getLocale(), getClass());
9541            } else {
9542                report = new CmsLogReport(CmsLocaleManager.getDefaultLocale(), getClass());
9543            }
9544        }
9545
9546        // iterate over all published resources to export them
9547        I_CmsVfsDriver vfsDriver = getVfsDriver(dbc);
9548        Iterator<CmsPublishedResource> i = publishedResources.iterator();
9549        while (i.hasNext()) {
9550            CmsPublishedResource currentPublishedResource = i.next();
9551            String currentExportPoint = exportPointDriver.getExportPoint(currentPublishedResource.getRootPath());
9552
9553            if (currentExportPoint != null) {
9554                if (!printReportHeaders) {
9555                    report.println(
9556                        Messages.get().container(Messages.RPT_EXPORT_POINTS_WRITE_BEGIN_0),
9557                        I_CmsReport.FORMAT_HEADLINE);
9558                    printReportHeaders = true;
9559                }
9560
9561                // print report message
9562                if (currentPublishedResource.getState().isDeleted()) {
9563                    report.print(
9564                        Messages.get().container(Messages.RPT_EXPORT_POINTS_DELETE_0),
9565                        I_CmsReport.FORMAT_NOTE);
9566                } else {
9567                    report.print(Messages.get().container(Messages.RPT_EXPORT_POINTS_WRITE_0), I_CmsReport.FORMAT_NOTE);
9568                }
9569                report.print(
9570                    org.opencms.report.Messages.get().container(
9571                        org.opencms.report.Messages.RPT_ARGUMENT_1,
9572                        currentPublishedResource.getRootPath()));
9573                report.print(org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_DOTS_0));
9574
9575                if (currentPublishedResource.isFolder()) {
9576                    // export the folder
9577                    if (currentPublishedResource.getState().isDeleted()) {
9578                        exportPointDriver.deleteResource(currentPublishedResource.getRootPath(), currentExportPoint);
9579                    } else {
9580                        exportPointDriver.createFolder(currentPublishedResource.getRootPath(), currentExportPoint);
9581                    }
9582                    report.println(
9583                        org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_OK_0),
9584                        I_CmsReport.FORMAT_OK);
9585                } else {
9586                    // export the file
9587                    try {
9588                        if (currentPublishedResource.getState().isDeleted()) {
9589                            exportPointDriver.deleteResource(
9590                                currentPublishedResource.getRootPath(),
9591                                currentExportPoint);
9592                        } else {
9593                            // read the file content online
9594                            byte[] onlineContent = vfsDriver.readContent(
9595                                dbc,
9596                                CmsProject.ONLINE_PROJECT_ID,
9597                                currentPublishedResource.getResourceId());
9598                            exportPointDriver.writeFile(
9599                                currentPublishedResource.getRootPath(),
9600                                currentExportPoint,
9601                                onlineContent);
9602                        }
9603                        report.println(
9604                            org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_OK_0),
9605                            I_CmsReport.FORMAT_OK);
9606                    } catch (CmsException e) {
9607                        if (LOG.isErrorEnabled()) {
9608                            LOG.error(
9609                                Messages.get().getBundle().key(
9610                                    Messages.LOG_WRITE_EXPORT_POINT_ERROR_1,
9611                                    currentPublishedResource.getRootPath()),
9612                                e);
9613                        }
9614                        report.println(
9615                            org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_FAILED_0),
9616                            I_CmsReport.FORMAT_ERROR);
9617                    }
9618                }
9619            }
9620        }
9621        if (printReportHeaders) {
9622            report.println(
9623                Messages.get().container(Messages.RPT_EXPORT_POINTS_WRITE_END_0),
9624                I_CmsReport.FORMAT_HEADLINE);
9625        }
9626    }
9627
9628    /**
9629     * Writes a resource to the OpenCms VFS, including it's content.<p>
9630     *
9631     * Applies only to resources of type <code>{@link CmsFile}</code>
9632     * i.e. resources that have a binary content attached.<p>
9633     *
9634     * Certain resource types might apply content validation or transformation rules
9635     * before the resource is actually written to the VFS. The returned result
9636     * might therefore be a modified version from the provided original.<p>
9637     *
9638     * @param dbc the current database context
9639     * @param resource the resource to apply this operation to
9640     *
9641     * @return the written resource (may have been modified)
9642     *
9643     * @throws CmsException if something goes wrong
9644     *
9645     * @see CmsObject#writeFile(CmsFile)
9646     * @see I_CmsResourceType#writeFile(CmsObject, CmsSecurityManager, CmsFile)
9647     */
9648    public CmsFile writeFile(CmsDbContext dbc, CmsFile resource) throws CmsException {
9649
9650        resource.setUserLastModified(dbc.currentUser().getId());
9651        resource.setContents(resource.getContents()); // to be sure the content date is updated
9652
9653        getVfsDriver(dbc).writeResource(dbc, dbc.currentProject().getUuid(), resource, UPDATE_RESOURCE_STATE);
9654
9655        byte[] contents = resource.getContents();
9656        getVfsDriver(dbc).writeContent(dbc, resource.getResourceId(), contents);
9657        // log it
9658        log(
9659            dbc,
9660            new CmsLogEntry(
9661                dbc,
9662                resource.getStructureId(),
9663                CmsLogEntryType.RESOURCE_CONTENT_MODIFIED,
9664                new String[] {resource.getRootPath()}),
9665            false);
9666
9667        // read the file back from db
9668        resource = new CmsFile(readResource(dbc, resource.getStructureId(), CmsResourceFilter.ALL));
9669        resource.setContents(contents);
9670
9671        deleteRelationsWithSiblings(dbc, resource);
9672
9673        // update the cache
9674        m_monitor.clearResourceCache();
9675
9676        Map<String, Object> data = new HashMap<String, Object>(2);
9677        data.put(I_CmsEventListener.KEY_RESOURCE, resource);
9678        data.put(I_CmsEventListener.KEY_CHANGE, new Integer(CHANGED_CONTENT));
9679        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_MODIFIED, data));
9680
9681        return resource;
9682    }
9683
9684    /**
9685     * Writes an already existing group.<p>
9686     *
9687     * The group id has to be a valid OpenCms group id.<br>
9688     *
9689     * The group with the given id will be completely overridden
9690     * by the given data.<p>
9691     *
9692     * @param dbc the current database context
9693     * @param group the group that should be written
9694     *
9695     * @throws CmsException if operation was not successful
9696     */
9697    public void writeGroup(CmsDbContext dbc, CmsGroup group) throws CmsException {
9698
9699        CmsGroup oldGroup = readGroup(dbc, group.getName());
9700        m_monitor.uncacheGroup(oldGroup);
9701        getUserDriver(dbc).writeGroup(dbc, group);
9702        m_monitor.cacheGroup(group);
9703
9704        if (!dbc.getProjectId().isNullUUID()) {
9705            // group modified event is not needed
9706            return;
9707        }
9708        // fire group modified event
9709        Map<String, Object> eventData = new HashMap<String, Object>();
9710        eventData.put(I_CmsEventListener.KEY_GROUP_ID, group.getId().toString());
9711        eventData.put(I_CmsEventListener.KEY_GROUP_NAME, oldGroup.getName());
9712        eventData.put(I_CmsEventListener.KEY_USER_ACTION, I_CmsEventListener.VALUE_GROUP_MODIFIED_ACTION_WRITE);
9713        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_GROUP_MODIFIED, eventData));
9714    }
9715
9716    /**
9717     * Creates an historical entry of the current project.<p>
9718     *
9719     * @param dbc the current database context
9720     * @param publishTag the version
9721     * @param publishDate the date of publishing
9722     *
9723     * @throws CmsDataAccessException if operation was not successful
9724     */
9725    public void writeHistoryProject(CmsDbContext dbc, int publishTag, long publishDate) throws CmsDataAccessException {
9726
9727        getHistoryDriver(dbc).writeProject(dbc, publishTag, publishDate);
9728    }
9729
9730    /**
9731     * Writes the locks that are currently stored in-memory to the database to allow restoring them
9732     * in future server startups.<p>
9733     *
9734     * This overwrites the locks previously stored in the underlying database table.<p>
9735     *
9736     * @param dbc the current database context
9737     *
9738     * @throws CmsException if something goes wrong
9739     */
9740    public void writeLocks(CmsDbContext dbc) throws CmsException {
9741
9742        m_lockManager.writeLocks(dbc);
9743    }
9744
9745    /**
9746     * Writes an already existing organizational unit.<p>
9747     *
9748     * The organizational unit id has to be a valid OpenCms organizational unit id.<br>
9749     *
9750     * The organizational unit with the given id will be completely overridden
9751     * by the given data.<p>
9752     *
9753     * @param dbc the current db context
9754     * @param organizationalUnit the organizational unit that should be written
9755     *
9756     * @throws CmsException if operation was not successful
9757     *
9758     * @see org.opencms.security.CmsOrgUnitManager#writeOrganizationalUnit(CmsObject, CmsOrganizationalUnit)
9759     */
9760    public void writeOrganizationalUnit(CmsDbContext dbc, CmsOrganizationalUnit organizationalUnit)
9761    throws CmsException {
9762
9763        m_monitor.uncacheOrgUnit(organizationalUnit);
9764        getUserDriver(dbc).writeOrganizationalUnit(dbc, organizationalUnit);
9765
9766        // create a publish list for the 'virtual' publish event
9767        CmsResource ouRes = readResource(dbc, organizationalUnit.getId(), CmsResourceFilter.DEFAULT);
9768        CmsPublishList pl = new CmsPublishList(ouRes, false);
9769        pl.add(ouRes, false);
9770
9771        getProjectDriver(dbc).writePublishHistory(
9772            dbc,
9773            pl.getPublishHistoryId(),
9774            new CmsPublishedResource(ouRes, -1, CmsResourceState.STATE_NEW));
9775
9776        // fire the 'virtual' publish event
9777        Map<String, Object> eventData = new HashMap<String, Object>();
9778        eventData.put(I_CmsEventListener.KEY_PUBLISHID, pl.getPublishHistoryId().toString());
9779        eventData.put(I_CmsEventListener.KEY_PROJECTID, dbc.currentProject().getUuid());
9780        eventData.put(I_CmsEventListener.KEY_DBCONTEXT, dbc);
9781        CmsEvent afterPublishEvent = new CmsEvent(I_CmsEventListener.EVENT_PUBLISH_PROJECT, eventData);
9782        OpenCms.fireCmsEvent(afterPublishEvent);
9783
9784        m_monitor.cacheOrgUnit(organizationalUnit);
9785    }
9786
9787    /**
9788     * Writes an already existing project.<p>
9789     *
9790     * The project id has to be a valid OpenCms project id.<br>
9791     *
9792     * The project with the given id will be completely overridden
9793     * by the given data.<p>
9794     *
9795     * @param dbc the current database context
9796     * @param project the project that should be written
9797     *
9798     * @throws CmsException if operation was not successful
9799     */
9800    public void writeProject(CmsDbContext dbc, CmsProject project) throws CmsException {
9801
9802        m_monitor.uncacheProject(project);
9803        getProjectDriver(dbc).writeProject(dbc, project);
9804        m_monitor.cacheProject(project);
9805    }
9806
9807    /**
9808     * Writes a new project into the PROJECT_LASTMODIFIED field of a resource record.<p>
9809     *
9810     * @param dbc the current database context
9811     * @param resource the resource which should be modified
9812     * @param projectId the project id to write
9813     *
9814     * @throws CmsDataAccessException if the database access fails
9815     */
9816    public void writeProjectLastModified(CmsDbContext dbc, CmsResource resource, CmsUUID projectId)
9817    throws CmsDataAccessException {
9818
9819        I_CmsVfsDriver vfsDriver = getVfsDriver(dbc);
9820        vfsDriver.writeLastModifiedProjectId(dbc, dbc.currentProject(), projectId, resource);
9821    }
9822
9823    /**
9824     * Writes a property for a specified resource.<p>
9825     *
9826     * @param dbc the current database context
9827     * @param resource the resource to write the property for
9828     * @param property the property to write
9829     *
9830     * @throws CmsException if something goes wrong
9831     *
9832     * @see CmsObject#writePropertyObject(String, CmsProperty)
9833     * @see I_CmsResourceType#writePropertyObject(CmsObject, CmsSecurityManager, CmsResource, CmsProperty)
9834     */
9835    public void writePropertyObject(CmsDbContext dbc, CmsResource resource, CmsProperty property) throws CmsException {
9836
9837        try {
9838            if (property == CmsProperty.getNullProperty()) {
9839                // skip empty or null properties
9840                return;
9841            }
9842
9843            // test if and what state should be updated
9844            // 0: none, 1: structure, 2: resource
9845            int updateState = getUpdateState(dbc, resource, Collections.singletonList(property));
9846
9847            // write the property
9848            getVfsDriver(dbc).writePropertyObject(dbc, dbc.currentProject(), resource, property);
9849
9850            if (updateState > 0) {
9851                updateState(dbc, resource, updateState == 2);
9852            }
9853            // log it
9854            log(dbc, new CmsLogEntry(
9855                dbc,
9856                resource.getStructureId(),
9857                CmsLogEntryType.RESOURCE_PROPERTIES,
9858                new String[] {resource.getRootPath()}), false);
9859
9860        } finally {
9861            // update the driver manager cache
9862            m_monitor.clearResourceCache();
9863            m_monitor.flushCache(CmsMemoryMonitor.CacheType.PROPERTY, CmsMemoryMonitor.CacheType.PROPERTY_LIST);
9864
9865            // fire an event that a property of a resource has been modified
9866            Map<String, Object> data = new HashMap<String, Object>();
9867            data.put(I_CmsEventListener.KEY_RESOURCE, resource);
9868            data.put("property", property);
9869            OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_PROPERTY_MODIFIED, data));
9870        }
9871    }
9872
9873    /**
9874     * Writes a list of properties for a specified resource.<p>
9875     *
9876     * Code calling this method has to ensure that the no properties
9877     * <code>a, b</code> are contained in the specified list so that <code>a.equals(b)</code>,
9878     * otherwise an exception is thrown.<p>
9879     *
9880     * @param dbc the current database context
9881     * @param resource the resource to write the properties for
9882     * @param properties the list of properties to write
9883     * @param updateState if <code>true</code> the state of the resource will be updated
9884     *
9885     * @throws CmsException if something goes wrong
9886     *
9887     * @see CmsObject#writePropertyObjects(String, List)
9888     * @see I_CmsResourceType#writePropertyObjects(CmsObject, CmsSecurityManager, CmsResource, List)
9889     */
9890    public void writePropertyObjects(
9891        CmsDbContext dbc,
9892        CmsResource resource,
9893        List<CmsProperty> properties,
9894        boolean updateState) throws CmsException {
9895
9896        if ((properties == null) || (properties.size() == 0)) {
9897            // skip empty or null lists
9898            return;
9899        }
9900
9901        try {
9902            // the specified list must not contain two or more equal property objects
9903            for (int i = 0, n = properties.size(); i < n; i++) {
9904                Set<String> keyValidationSet = new HashSet<String>();
9905                CmsProperty property = properties.get(i);
9906                if (!keyValidationSet.contains(property.getName())) {
9907                    keyValidationSet.add(property.getName());
9908                } else {
9909                    throw new CmsVfsException(
9910                        Messages.get().container(Messages.ERR_VFS_INVALID_PROPERTY_LIST_1, property.getName()));
9911                }
9912            }
9913
9914            // test if and what state should be updated
9915            // 0: none, 1: structure, 2: resource
9916            int updateStateValue = 0;
9917            if (updateState) {
9918                updateStateValue = getUpdateState(dbc, resource, properties);
9919            }
9920            I_CmsVfsDriver vfsDriver = getVfsDriver(dbc);
9921            for (int i = 0; i < properties.size(); i++) {
9922                // write the property
9923                CmsProperty property = properties.get(i);
9924                vfsDriver.writePropertyObject(dbc, dbc.currentProject(), resource, property);
9925            }
9926
9927            if (updateStateValue > 0) {
9928                // update state
9929                updateState(dbc, resource, (updateStateValue == 2));
9930            }
9931
9932            if (updateState) {
9933                // log it
9934                log(dbc, new CmsLogEntry(
9935                    dbc,
9936                    resource.getStructureId(),
9937                    CmsLogEntryType.RESOURCE_PROPERTIES,
9938                    new String[] {resource.getRootPath()}), false);
9939            }
9940        } finally {
9941            // update the driver manager cache
9942            m_monitor.clearResourceCache();
9943            m_monitor.flushCache(CmsMemoryMonitor.CacheType.PROPERTY, CmsMemoryMonitor.CacheType.PROPERTY_LIST);
9944
9945            // fire an event that the properties of a resource have been modified
9946            OpenCms.fireCmsEvent(
9947                new CmsEvent(
9948                    I_CmsEventListener.EVENT_RESOURCE_AND_PROPERTIES_MODIFIED,
9949                    Collections.<String, Object> singletonMap(I_CmsEventListener.KEY_RESOURCE, resource)));
9950        }
9951    }
9952
9953    /**
9954     * Updates a publish job.<p>
9955     *
9956     * @param dbc the current database context
9957     * @param publishJob the publish job to update
9958     *
9959     * @throws CmsException if something goes wrong
9960     */
9961    public void writePublishJob(CmsDbContext dbc, CmsPublishJobInfoBean publishJob) throws CmsException {
9962
9963        getProjectDriver(dbc).writePublishJob(dbc, publishJob);
9964    }
9965
9966    /**
9967     * Writes the publish report for a publish job.<p>
9968     *
9969     * @param dbc the current database context
9970     * @param publishJob the publish job
9971     * @throws CmsException if something goes wrong
9972     */
9973    public void writePublishReport(CmsDbContext dbc, CmsPublishJobInfoBean publishJob) throws CmsException {
9974
9975        CmsPublishReport report = (CmsPublishReport)publishJob.removePublishReport();
9976
9977        if (report != null) {
9978            getProjectDriver(dbc).writePublishReport(dbc, publishJob.getPublishHistoryId(), report.getContents());
9979        }
9980    }
9981
9982    /**
9983     * Writes a resource to the OpenCms VFS.<p>
9984     *
9985     * @param dbc the current database context
9986     * @param resource the resource to write
9987     *
9988     * @throws CmsException if something goes wrong
9989     */
9990    public void writeResource(CmsDbContext dbc, CmsResource resource) throws CmsException {
9991
9992        // access was granted - write the resource
9993        resource.setUserLastModified(dbc.currentUser().getId());
9994        CmsUUID projectId = ((dbc.getProjectId() == null) || dbc.getProjectId().isNullUUID())
9995        ? dbc.currentProject().getUuid()
9996        : dbc.getProjectId();
9997
9998        getVfsDriver(dbc).writeResource(dbc, projectId, resource, UPDATE_RESOURCE_STATE);
9999
10000        // make sure the written resource has the state correctly set
10001        if (resource.getState().isUnchanged()) {
10002            resource.setState(CmsResource.STATE_CHANGED);
10003        }
10004
10005        // delete in content relations if the new type is not parseable
10006        if (!(OpenCms.getResourceManager().getResourceType(resource.getTypeId()) instanceof I_CmsLinkParseable)) {
10007            deleteRelationsWithSiblings(dbc, resource);
10008        }
10009
10010        // update the cache
10011        m_monitor.clearResourceCache();
10012        Map<String, Object> data = new HashMap<String, Object>(2);
10013        data.put(I_CmsEventListener.KEY_RESOURCE, resource);
10014        data.put(I_CmsEventListener.KEY_CHANGE, new Integer(CHANGED_RESOURCE));
10015        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_MODIFIED, data));
10016    }
10017
10018    /**
10019     * Inserts an entry in the published resource table.<p>
10020     *
10021     * This is done during static export.<p>
10022     *
10023     * @param dbc the current database context
10024     * @param resourceName The name of the resource to be added to the static export
10025     * @param linkType the type of resource exported (0= non-parameter, 1=parameter)
10026     * @param linkParameter the parameters added to the resource
10027     * @param timestamp a time stamp for writing the data into the db
10028     *
10029     * @throws CmsException if something goes wrong
10030     */
10031    public void writeStaticExportPublishedResource(
10032        CmsDbContext dbc,
10033        String resourceName,
10034        int linkType,
10035        String linkParameter,
10036        long timestamp) throws CmsException {
10037
10038        getProjectDriver(dbc).writeStaticExportPublishedResource(dbc, resourceName, linkType, linkParameter, timestamp);
10039    }
10040
10041    /**
10042     * Adds a new url name mapping for a structure id.<p>
10043     *
10044     * Instead of taking the name directly, this method takes an iterator of strings
10045     * which generates candidate URL names on-the-fly. The first generated name which is
10046     * not already mapped to another structure id will be chosen for the new URL name mapping.
10047     *
10048     * @param dbc the current database context
10049     * @param nameSeq the sequence of URL name candidates
10050     * @param structureId the structure id to which the url name should be mapped
10051     * @param locale the locale for which the mapping should be written
10052     * @param replaceOnPublish name mappings for which this is set will replace all other mappings for the same resource on publishing
10053     *
10054     * @return the actual name which was mapped to the structure id
10055     *
10056     * @throws CmsDataAccessException if something goes wrong
10057     */
10058    public String writeUrlNameMapping(
10059        CmsDbContext dbc,
10060        Iterator<String> nameSeq,
10061        CmsUUID structureId,
10062        String locale,
10063        boolean replaceOnPublish) throws CmsDataAccessException {
10064
10065        String bestName = findBestNameForUrlNameMapping(dbc, nameSeq, structureId, locale);
10066        addOrReplaceUrlNameMapping(dbc, bestName, structureId, locale, replaceOnPublish);
10067        return bestName;
10068    }
10069
10070    /**
10071     * Updates the user information. <p>
10072     *
10073     * The user id has to be a valid OpenCms user id.<br>
10074     *
10075     * The user with the given id will be completely overridden
10076     * by the given data.<p>
10077     *
10078     * @param dbc the current database context
10079     * @param user the user to be updated
10080     *
10081     * @throws CmsException if operation was not successful
10082     */
10083    public void writeUser(CmsDbContext dbc, CmsUser user) throws CmsException {
10084
10085        CmsUser oldUser = readUser(dbc, user.getId());
10086        m_monitor.clearUserCache(oldUser);
10087        getUserDriver(dbc).writeUser(dbc, user);
10088        m_monitor.flushCache(CmsMemoryMonitor.CacheType.USERGROUPS, CmsMemoryMonitor.CacheType.USER_LIST);
10089
10090        if (!dbc.getProjectId().isNullUUID()) {
10091            // user modified event is not needed
10092            return;
10093        }
10094        // fire user modified event
10095        Map<String, Object> eventData = new HashMap<String, Object>();
10096        eventData.put(I_CmsEventListener.KEY_USER_ID, user.getId().toString());
10097        eventData.put(I_CmsEventListener.KEY_USER_NAME, oldUser.getName());
10098        eventData.put(I_CmsEventListener.KEY_USER_ACTION, I_CmsEventListener.VALUE_USER_MODIFIED_ACTION_WRITE_USER);
10099        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_USER_MODIFIED, eventData));
10100    }
10101
10102    /**
10103     * Adds or replaces a new url name mapping in the offline project.<p>
10104     *
10105     * @param dbc the current database context
10106     * @param name the URL name of the mapping
10107     * @param structureId the structure id of the mapping
10108     * @param locale the locale of the mapping
10109     * @param replaceOnPublish if the mapping shoudl replace previous URL name mappings when published
10110     *
10111     * @throws CmsDataAccessException if something goes wrong
10112     */
10113    protected void addOrReplaceUrlNameMapping(
10114        CmsDbContext dbc,
10115        String name,
10116        CmsUUID structureId,
10117        String locale,
10118        boolean replaceOnPublish) throws CmsDataAccessException {
10119
10120        getVfsDriver(dbc).deleteUrlNameMappingEntries(
10121            dbc,
10122            false,
10123            CmsUrlNameMappingFilter.ALL.filterStructureId(structureId).filterLocale(locale).filterStates(
10124                CmsUrlNameMappingEntry.MAPPING_STATUS_NEW,
10125                CmsUrlNameMappingEntry.MAPPING_STATUS_REPLACE_ON_PUBLISH));
10126        CmsUrlNameMappingEntry newEntry = new CmsUrlNameMappingEntry(
10127            name,
10128            structureId,
10129            replaceOnPublish
10130            ? CmsUrlNameMappingEntry.MAPPING_STATUS_REPLACE_ON_PUBLISH
10131            : CmsUrlNameMappingEntry.MAPPING_STATUS_NEW,
10132            System.currentTimeMillis(),
10133            locale);
10134        getVfsDriver(dbc).addUrlNameMappingEntry(dbc, false, newEntry);
10135    }
10136
10137    /**
10138     * Converts a resource to a folder (if possible).<p>
10139     *
10140     * @param resource the resource to convert
10141     * @return the converted resource
10142     *
10143     * @throws CmsVfsResourceNotFoundException if the resource is not a folder
10144     */
10145    protected CmsFolder convertResourceToFolder(CmsResource resource) throws CmsVfsResourceNotFoundException {
10146
10147        if (resource.isFolder()) {
10148            return new CmsFolder(resource);
10149        }
10150
10151        throw new CmsVfsResourceNotFoundException(
10152            Messages.get().container(Messages.ERR_ACCESS_FILE_AS_FOLDER_1, resource.getRootPath()));
10153    }
10154
10155    /**
10156     * Helper method for creating a driver from configuration data.<p>
10157     *
10158     * @param dbc the db context
10159     * @param configManager the configuration manager
10160     * @param config the configuration
10161     * @param driverChainKey the configuration key under which the driver chain is stored
10162     * @param suffix the suffix to append to a driver chain entry to get the key for the driver class
10163     *
10164     * @return the newly created driver
10165     */
10166    protected Object createDriver(
10167        CmsDbContext dbc,
10168        CmsConfigurationManager configManager,
10169        CmsParameterConfiguration config,
10170        String driverChainKey,
10171        String suffix) {
10172
10173        // read the vfs driver class properties and initialize a new instance
10174        List<String> drivers = config.getList(driverChainKey);
10175        String driverKey = drivers.get(0) + suffix;
10176        String driverName = config.get(driverKey);
10177        drivers = (drivers.size() > 1) ? drivers.subList(1, drivers.size()) : null;
10178        if (driverName == null) {
10179            CmsLog.INIT.error(Messages.get().getBundle().key(Messages.INIT_DRIVER_FAILED_1, driverKey));
10180        }
10181        return newDriverInstance(dbc, configManager, driverName, drivers);
10182    }
10183
10184    /**
10185     * Deletes all relations for the given resource and all its siblings.<p>
10186     *
10187     * @param dbc the current database context
10188     * @param resource the resource to delete the resource for
10189     *
10190     * @throws CmsException if something goes wrong
10191     */
10192    protected void deleteRelationsWithSiblings(CmsDbContext dbc, CmsResource resource) throws CmsException {
10193
10194        // get all siblings
10195        List<CmsResource> siblings;
10196        if (resource.getSiblingCount() > 1) {
10197            siblings = readSiblings(dbc, resource, CmsResourceFilter.ALL);
10198        } else {
10199            siblings = new ArrayList<CmsResource>();
10200            siblings.add(resource);
10201        }
10202        // clean the relations in content for all siblings
10203        I_CmsVfsDriver vfsDriver = getVfsDriver(dbc);
10204        Iterator<CmsResource> it = siblings.iterator();
10205        while (it.hasNext()) {
10206            CmsResource sibling = it.next();
10207            // clean the relation information for this sibling
10208            vfsDriver.deleteRelations(
10209                dbc,
10210                dbc.currentProject().getUuid(),
10211                sibling,
10212                CmsRelationFilter.TARGETS.filterDefinedInContent());
10213        }
10214    }
10215
10216    /**
10217     * Tries to add sub-resources of moved folders to the publish list and throws an exception if the publish list still does
10218     * not contain some  sub-resources of the moved folders.<p>
10219     *
10220     * @param cms the current CMS context
10221     * @param dbc the current database context
10222     * @param pubList the publish list
10223     * @throws CmsException if something goes wrong
10224     */
10225    protected void ensureSubResourcesOfMovedFoldersPublished(CmsObject cms, CmsDbContext dbc, CmsPublishList pubList)
10226    throws CmsException {
10227
10228        List<CmsResource> topMovedFolders = pubList.getTopMovedFolders(cms);
10229        Iterator<CmsResource> folderIt = topMovedFolders.iterator();
10230        while (folderIt.hasNext()) {
10231            CmsResource folder = folderIt.next();
10232            addSubResources(dbc, pubList, folder);
10233        }
10234        List<CmsResource> missingSubResources = pubList.getMissingSubResources(cms, topMovedFolders);
10235        if (missingSubResources.isEmpty()) {
10236            return;
10237        }
10238
10239        StringBuffer pathBuffer = new StringBuffer();
10240
10241        for (CmsResource missing : missingSubResources) {
10242            pathBuffer.append(missing.getRootPath());
10243            pathBuffer.append(" ");
10244        }
10245        throw new CmsVfsException(
10246            Messages.get().container(Messages.RPT_CHILDREN_OF_MOVED_FOLDER_NOT_PUBLISHED_1, pathBuffer.toString()));
10247
10248    }
10249
10250    /**
10251     * Tries to find the best name for an URL name mapping for the given structure id.<p>
10252     *
10253     * @param dbc the database context
10254     * @param nameSeq the sequence of name candidates
10255     * @param structureId the structure id to which an URL name should be mapped
10256     * @param locale the locale for which the URL name should be mapped
10257     *
10258     * @return the selected URL name candidate
10259     *
10260     * @throws CmsDataAccessException if something goes wrong
10261     */
10262    protected String findBestNameForUrlNameMapping(
10263        CmsDbContext dbc,
10264        Iterator<String> nameSeq,
10265        CmsUUID structureId,
10266        String locale) throws CmsDataAccessException {
10267
10268        String newName;
10269        boolean alreadyInUse;
10270        do {
10271            newName = nameSeq.next();
10272            alreadyInUse = false;
10273            CmsUrlNameMappingFilter filter = CmsUrlNameMappingFilter.ALL.filterName(newName);
10274            List<CmsUrlNameMappingEntry> entriesWithSameName = getVfsDriver(dbc).readUrlNameMappingEntries(
10275                dbc,
10276                false,
10277                filter);
10278            for (CmsUrlNameMappingEntry entry : entriesWithSameName) {
10279                boolean sameId = entry.getStructureId().equals(structureId);
10280                if (!sameId) {
10281                    // name already used for other resource, or for different locale of the same resource
10282                    alreadyInUse = true;
10283                    break;
10284                }
10285            }
10286        } while (alreadyInUse);
10287        return newName;
10288    }
10289
10290    /**
10291     * Helper method for finding the 'best' URL name to use for a new URL name mapping.<p>
10292     *
10293     * Since the name given as a parameter may be already used, this method will try to append numeric suffixes
10294     * to the name to find a mapping name which is not used.<p>
10295     *
10296     * @param dbc the current database context
10297     * @param name the name of the mapping
10298     * @param structureId the structure id to which the name is mapped
10299     *
10300     * @return the best name which was found for the new mapping
10301     *
10302     * @throws CmsDataAccessException if something goes wrong
10303     */
10304    protected String findBestNameForUrlNameMapping(CmsDbContext dbc, String name, CmsUUID structureId)
10305    throws CmsDataAccessException {
10306
10307        List<CmsUrlNameMappingEntry> entriesStartingWithName = getVfsDriver(dbc).readUrlNameMappingEntries(
10308            dbc,
10309            false,
10310            CmsUrlNameMappingFilter.ALL.filterNamePattern(name + "%").filterRejectStructureId(structureId));
10311        Set<String> usedNames = new HashSet<String>();
10312        for (CmsUrlNameMappingEntry entry : entriesStartingWithName) {
10313            usedNames.add(entry.getName());
10314        }
10315        int counter = 0;
10316        String numberedName;
10317        do {
10318            numberedName = getNumberedName(name, counter);
10319            counter += 1;
10320        } while (usedNames.contains(numberedName));
10321        return numberedName;
10322    }
10323
10324    /**
10325     * Returns the lock manager instance.<p>
10326     *
10327     * @return the lock manager instance
10328     */
10329    protected CmsLockManager getLockManager() {
10330
10331        return m_lockManager;
10332    }
10333
10334    /**
10335     * Adds a numeric suffix to the end of a string, unless the number passed as a parameter is 0.<p>
10336     *
10337     * @param name the base name
10338     * @param number the number from which to form the suffix
10339     *
10340     * @return the concatenation of the base name and possibly the numeric suffix
10341     */
10342    protected String getNumberedName(String name, int number) {
10343
10344        if (number == 0) {
10345            return name;
10346        }
10347        PrintfFormat fmt = new PrintfFormat("%0.6d");
10348        return name + "_" + fmt.sprintf(number);
10349    }
10350
10351    /**
10352     * Resets the resources in a project to their online state.<p>
10353     *
10354     * @param dbc the database context
10355     * @param projectId the project id
10356     * @param modifiedFiles the modified files
10357     * @param modifiedFolders the modified folders
10358     * @throws CmsException if something goes wrong
10359     * @throws CmsSecurityException if we don't have the permissions
10360     * @throws CmsDataAccessException if something goes wrong with the database
10361     */
10362    protected void resetResourcesInProject(
10363        CmsDbContext dbc,
10364        CmsUUID projectId,
10365        List<CmsResource> modifiedFiles,
10366        List<CmsResource> modifiedFolders) throws CmsException, CmsSecurityException, CmsDataAccessException {
10367
10368        // all resources inside the project have to be be reset to their online state.
10369        // 1. step: delete all new files
10370        for (int i = 0; i < modifiedFiles.size(); i++) {
10371            CmsResource currentFile = modifiedFiles.get(i);
10372            if (currentFile.getState().isNew()) {
10373                CmsLock lock = getLock(dbc, currentFile);
10374                if (lock.isNullLock()) {
10375                    // lock the resource
10376                    lockResource(dbc, currentFile, CmsLockType.EXCLUSIVE);
10377                } else if (!lock.isOwnedBy(dbc.currentUser()) || !lock.isInProject(dbc.currentProject())) {
10378                    changeLock(dbc, currentFile, CmsLockType.EXCLUSIVE);
10379                }
10380                // delete the properties
10381                getVfsDriver(dbc).deletePropertyObjects(
10382                    dbc,
10383                    projectId,
10384                    currentFile,
10385                    CmsProperty.DELETE_OPTION_DELETE_STRUCTURE_AND_RESOURCE_VALUES);
10386                // delete the file
10387                getVfsDriver(dbc).removeFile(dbc, dbc.currentProject().getUuid(), currentFile);
10388                // remove the access control entries
10389                getUserDriver(dbc).removeAccessControlEntries(dbc, dbc.currentProject(), currentFile.getResourceId());
10390                // fire the corresponding event
10391                OpenCms.fireCmsEvent(
10392                    new CmsEvent(
10393                        I_CmsEventListener.EVENT_RESOURCE_AND_PROPERTIES_MODIFIED,
10394                        Collections.<String, Object> singletonMap(I_CmsEventListener.KEY_RESOURCE, currentFile)));
10395            }
10396        }
10397
10398        // 2. step: delete all new folders
10399        for (int i = 0; i < modifiedFolders.size(); i++) {
10400            CmsResource currentFolder = modifiedFolders.get(i);
10401            if (currentFolder.getState().isNew()) {
10402                // delete the properties
10403                getVfsDriver(dbc).deletePropertyObjects(
10404                    dbc,
10405                    projectId,
10406                    currentFolder,
10407                    CmsProperty.DELETE_OPTION_DELETE_STRUCTURE_AND_RESOURCE_VALUES);
10408                // delete the folder
10409                getVfsDriver(dbc).removeFolder(dbc, dbc.currentProject(), currentFolder);
10410                // remove the access control entries
10411                getUserDriver(dbc).removeAccessControlEntries(dbc, dbc.currentProject(), currentFolder.getResourceId());
10412                // fire the corresponding event
10413                OpenCms.fireCmsEvent(
10414                    new CmsEvent(
10415                        I_CmsEventListener.EVENT_RESOURCE_AND_PROPERTIES_MODIFIED,
10416                        Collections.<String, Object> singletonMap(I_CmsEventListener.KEY_RESOURCE, currentFolder)));
10417            }
10418        }
10419
10420        // 3. step: undo changes on all changed or deleted folders
10421        for (int i = 0; i < modifiedFolders.size(); i++) {
10422            CmsResource currentFolder = modifiedFolders.get(i);
10423            if ((currentFolder.getState().isChanged()) || (currentFolder.getState().isDeleted())) {
10424                CmsLock lock = getLock(dbc, currentFolder);
10425                if (lock.isNullLock()) {
10426                    // lock the resource
10427                    lockResource(dbc, currentFolder, CmsLockType.EXCLUSIVE);
10428                } else if (!lock.isOwnedBy(dbc.currentUser()) || !lock.isInProject(dbc.currentProject())) {
10429                    changeLock(dbc, currentFolder, CmsLockType.EXCLUSIVE);
10430                }
10431                // undo all changes in the folder
10432                undoChanges(dbc, currentFolder, CmsResource.UNDO_CONTENT);
10433                // fire the corresponding event
10434                OpenCms.fireCmsEvent(new CmsEvent(
10435                    I_CmsEventListener.EVENT_RESOURCE_AND_PROPERTIES_MODIFIED,
10436                    Collections.<String, Object> singletonMap(I_CmsEventListener.KEY_RESOURCE, currentFolder)));
10437            }
10438        }
10439
10440        // 4. step: undo changes on all changed or deleted files
10441        for (int i = 0; i < modifiedFiles.size(); i++) {
10442            CmsResource currentFile = modifiedFiles.get(i);
10443            if (currentFile.getState().isChanged() || currentFile.getState().isDeleted()) {
10444                CmsLock lock = getLock(dbc, currentFile);
10445                if (lock.isNullLock()) {
10446                    // lock the resource
10447                    lockResource(dbc, currentFile, CmsLockType.EXCLUSIVE);
10448                } else if (!lock.isOwnedInProjectBy(dbc.currentUser(), dbc.currentProject())) {
10449                    if (lock.isLockableBy(dbc.currentUser())) {
10450                        changeLock(dbc, currentFile, CmsLockType.EXCLUSIVE);
10451                    }
10452                }
10453                // undo all changes in the file
10454                undoChanges(dbc, currentFile, CmsResource.UNDO_CONTENT);
10455                // fire the corresponding event
10456                OpenCms.fireCmsEvent(new CmsEvent(
10457                    I_CmsEventListener.EVENT_RESOURCE_AND_PROPERTIES_MODIFIED,
10458                    Collections.<String, Object> singletonMap(I_CmsEventListener.KEY_RESOURCE, currentFile)));
10459            }
10460        }
10461    }
10462
10463    /**
10464     * Counts the total number of users which fit the given criteria.<p>
10465     *
10466     * @param dbc the database context
10467     * @param searchParams the user search criteria
10468     *
10469     * @return the total number of users matching the criteria
10470     *
10471     * @throws CmsDataAccessException if something goes wrong
10472     */
10473    long countUsers(CmsDbContext dbc, CmsUserSearchParameters searchParams) throws CmsDataAccessException {
10474
10475        return getUserDriver(dbc).countUsers(dbc, searchParams);
10476    }
10477
10478    /**
10479     * Adds all sub-resources of the given resource to the publish list.<p>
10480     *
10481     * @param dbc the database context
10482     * @param publishList the publish list
10483     * @param directPublishResource the resource to get the sub-resources for
10484     *
10485     * @throws CmsDataAccessException if something goes wrong accessing the database
10486     */
10487    private void addSubResources(CmsDbContext dbc, CmsPublishList publishList, CmsResource directPublishResource)
10488    throws CmsDataAccessException {
10489
10490        int flags = CmsDriverManager.READMODE_INCLUDE_TREE | CmsDriverManager.READMODE_EXCLUDE_STATE;
10491        if (!directPublishResource.getState().isDeleted()) {
10492            // fix for org.opencms.file.TestPublishIssues#testPublishFolderWithDeletedFileFromOtherProject
10493            flags = flags | CmsDriverManager.READMODE_INCLUDE_PROJECT;
10494        }
10495
10496        // add all sub resources of the folder
10497        List<CmsResource> folderList = getVfsDriver(dbc).readResourceTree(
10498            dbc,
10499            dbc.currentProject().getUuid(),
10500            directPublishResource.getRootPath(),
10501            CmsDriverManager.READ_IGNORE_TYPE,
10502            CmsResource.STATE_UNCHANGED,
10503            CmsDriverManager.READ_IGNORE_TIME,
10504            CmsDriverManager.READ_IGNORE_TIME,
10505            CmsDriverManager.READ_IGNORE_TIME,
10506            CmsDriverManager.READ_IGNORE_TIME,
10507            CmsDriverManager.READ_IGNORE_TIME,
10508            CmsDriverManager.READ_IGNORE_TIME,
10509            flags | CmsDriverManager.READMODE_ONLY_FOLDERS);
10510
10511        publishList.addAll(filterResources(dbc, publishList, folderList), true);
10512
10513        List<CmsResource> fileList = getVfsDriver(dbc).readResourceTree(
10514            dbc,
10515            dbc.currentProject().getUuid(),
10516            directPublishResource.getRootPath(),
10517            CmsDriverManager.READ_IGNORE_TYPE,
10518            CmsResource.STATE_UNCHANGED,
10519            CmsDriverManager.READ_IGNORE_TIME,
10520            CmsDriverManager.READ_IGNORE_TIME,
10521            CmsDriverManager.READ_IGNORE_TIME,
10522            CmsDriverManager.READ_IGNORE_TIME,
10523            CmsDriverManager.READ_IGNORE_TIME,
10524            CmsDriverManager.READ_IGNORE_TIME,
10525            flags | CmsDriverManager.READMODE_ONLY_FILES);
10526
10527        publishList.addAll(filterResources(dbc, publishList, fileList), true);
10528    }
10529
10530    /**
10531     * Helper method to check whether we should bother with reading the group for a given role in a given OU.<p>
10532     *
10533     * This is important because webuser OUs don't have most role groups, and their absence is not cached, so we want to avoid reading them.
10534     *
10535     * @param ou the OU
10536     * @param role the role
10537     * @return true if we should read the role in the OU
10538     */
10539    private boolean canReadRoleInOu(CmsOrganizationalUnit ou, CmsRole role) {
10540
10541        if (ou.hasFlagWebuser() && !role.getRoleName().equals(CmsRole.ACCOUNT_MANAGER.getRoleName())) {
10542            return false;
10543        }
10544        return true;
10545    }
10546
10547    /**
10548     * Checks the parent of a resource during publishing.<p>
10549     *
10550     * @param dbc the current database context
10551     * @param deletedFolders a list of deleted folders
10552     * @param res a resource to check the parent for
10553     *
10554     * @return <code>true</code> if the parent resource will be deleted during publishing
10555     */
10556    private boolean checkDeletedParentFolder(CmsDbContext dbc, List<CmsResource> deletedFolders, CmsResource res) {
10557
10558        String parentPath = CmsResource.getParentFolder(res.getRootPath());
10559
10560        if (parentPath == null) {
10561            // resource has no parent
10562            return false;
10563        }
10564
10565        CmsResource parent;
10566        try {
10567            parent = readResource(dbc, parentPath, CmsResourceFilter.ALL);
10568        } catch (Exception e) {
10569            // failure: if we cannot read the parent, we should not publish the resource
10570            return false;
10571        }
10572
10573        if (!parent.getState().isDeleted()) {
10574            // parent is not deleted
10575            return false;
10576        }
10577
10578        for (int j = 0; j < deletedFolders.size(); j++) {
10579            if ((deletedFolders.get(j)).getStructureId().equals(parent.getStructureId())) {
10580                // parent is deleted, and it will get published
10581                return true;
10582            }
10583        }
10584
10585        // parent is new, but it will not get published
10586        return false;
10587    }
10588
10589    /**
10590     * Checks that no one of the resources to be published has a 'new' parent (that has not been published yet).<p>
10591     *
10592     * @param dbc the db context
10593     * @param publishList the publish list to check
10594     *
10595     * @throws CmsVfsException if there is a resource to be published with a 'new' parent
10596     */
10597    private void checkParentFolders(CmsDbContext dbc, CmsPublishList publishList) throws CmsVfsException {
10598
10599        boolean directPublish = publishList.isDirectPublish();
10600        // if we direct publish a file, check if all parent folders are already published
10601        if (directPublish) {
10602            // first get the names of all parent folders
10603            Iterator<CmsResource> it = publishList.getDirectPublishResources().iterator();
10604            List<String> parentFolderNames = new ArrayList<String>();
10605            while (it.hasNext()) {
10606                CmsResource res = it.next();
10607                String parentFolderName = CmsResource.getParentFolder(res.getRootPath());
10608                if (parentFolderName != null) {
10609                    parentFolderNames.add(parentFolderName);
10610                }
10611            }
10612            // remove duplicate parent folder names
10613            parentFolderNames = CmsFileUtil.removeRedundancies(parentFolderNames);
10614            String parentFolderName = null;
10615            try {
10616                I_CmsVfsDriver vfsDriver = getVfsDriver(dbc);
10617                // now check all folders if they exist in the online project
10618                Iterator<String> parentIt = parentFolderNames.iterator();
10619                while (parentIt.hasNext()) {
10620                    parentFolderName = parentIt.next();
10621                    vfsDriver.readFolder(dbc, CmsProject.ONLINE_PROJECT_ID, parentFolderName);
10622                }
10623            } catch (CmsException e) {
10624                throw new CmsVfsException(
10625                    Messages.get().container(Messages.RPT_PARENT_FOLDER_NOT_PUBLISHED_1, parentFolderName));
10626            }
10627        }
10628    }
10629
10630    /**
10631     * Checks the parent of a resource during publishing.<p>
10632     *
10633     * @param dbc the current database context
10634     * @param folderList a list of folders
10635     * @param res a resource to check the parent for
10636     *
10637     * @return true if the resource should be published
10638     */
10639    private boolean checkParentResource(CmsDbContext dbc, List<CmsResource> folderList, CmsResource res) {
10640
10641        String parentPath = CmsResource.getParentFolder(res.getRootPath());
10642
10643        if (parentPath == null) {
10644            // resource has no parent
10645            return true;
10646        }
10647
10648        CmsResource parent;
10649        try {
10650            parent = readResource(dbc, parentPath, CmsResourceFilter.ALL);
10651        } catch (Exception e) {
10652            // failure: if we cannot read the parent, we should not publish the resource
10653            return false;
10654        }
10655
10656        if (!parent.getState().isNew()) {
10657            // parent is already published
10658            return true;
10659        }
10660
10661        for (int j = 0; j < folderList.size(); j++) {
10662            if (folderList.get(j).getStructureId().equals(parent.getStructureId())) {
10663                // parent is new, but it will get published
10664                return true;
10665            }
10666        }
10667
10668        // parent is new, but it will not get published
10669        return false;
10670    }
10671
10672    /**
10673     * Copies all relations from the source resource to the target resource.<p>
10674     *
10675     * @param dbc the database context
10676     * @param source the source
10677     * @param target the target
10678     *
10679     * @throws CmsException if something goes wrong
10680     */
10681    private void copyRelations(CmsDbContext dbc, CmsResource source, CmsResource target) throws CmsException {
10682
10683        // copy relations all relations
10684        CmsObject cms = new CmsObject(getSecurityManager(), dbc.getRequestContext());
10685        Iterator<CmsRelation> itRelations = getRelationsForResource(
10686            dbc,
10687            source,
10688            CmsRelationFilter.TARGETS.filterNotDefinedInContent()).iterator();
10689        while (itRelations.hasNext()) {
10690            CmsRelation relation = itRelations.next();
10691            try {
10692                CmsResource relTarget = relation.getTarget(cms, CmsResourceFilter.ALL);
10693                addRelationToResource(dbc, target, relTarget, relation.getType(), true);
10694            } catch (CmsVfsResourceNotFoundException e) {
10695                // ignore this broken relation
10696                if (LOG.isWarnEnabled()) {
10697                    LOG.warn(e.getLocalizedMessage(), e);
10698                }
10699            }
10700        }
10701        // repair categories
10702        repairCategories(dbc, getProjectIdForContext(dbc), target);
10703    }
10704
10705    /**
10706     * Filters the given list of resources, removes all resources where the current user
10707     * does not have READ permissions, plus the filter is applied.<p>
10708     *
10709     * @param dbc the current database context
10710     * @param resourceList a list of CmsResources
10711     * @param filter the resource filter to use
10712     *
10713     * @return the filtered list of resources
10714     *
10715     * @throws CmsException in case errors testing the permissions
10716     */
10717    private List<CmsResource> filterPermissions(
10718        CmsDbContext dbc,
10719        List<CmsResource> resourceList,
10720        CmsResourceFilter filter) throws CmsException {
10721
10722        if (filter.requireTimerange()) {
10723            // never check time range here - this must be done later in #updateContextDates(...)
10724            filter = filter.addExcludeTimerange();
10725        }
10726        ArrayList<CmsResource> result = new ArrayList<CmsResource>(resourceList.size());
10727        for (int i = 0; i < resourceList.size(); i++) {
10728            // check the permission of all resources
10729            CmsResource currentResource = resourceList.get(i);
10730            if (m_securityManager.hasPermissions(
10731                dbc,
10732                currentResource,
10733                CmsPermissionSet.ACCESS_READ,
10734                true,
10735                filter).isAllowed()) {
10736                // only return resources where permission was granted
10737                result.add(currentResource);
10738            }
10739        }
10740        // return the result
10741        return result;
10742    }
10743
10744    /**
10745     * Returns a filtered list of resources for publishing.<p>
10746     * Contains all resources, which are not locked
10747     * and which have a parent folder that is already published or will be published, too.<p>
10748     *
10749     * @param dbc the current database context
10750     * @param publishList the filling publish list
10751     * @param resourceList the list of resources to filter
10752     *
10753     * @return a filtered list of resources
10754     */
10755    private List<CmsResource> filterResources(
10756        CmsDbContext dbc,
10757        CmsPublishList publishList,
10758        List<CmsResource> resourceList) {
10759
10760        List<CmsResource> result = new ArrayList<CmsResource>();
10761
10762        // local folder list for adding new publishing subfolders
10763        // this solves the {@link org.opencms.file.TestPublishIssues#testPublishScenarioD} problem.
10764        List<CmsResource> newFolderList = new ArrayList<CmsResource>(
10765            publishList == null ? resourceList : publishList.getFolderList());
10766
10767        for (int i = 0; i < resourceList.size(); i++) {
10768            CmsResource res = resourceList.get(i);
10769            try {
10770                CmsLock lock = getLock(dbc, res);
10771                if (lock.isPublish()) {
10772                    // if already enqueued
10773                    continue;
10774                }
10775                if (!lock.isLockableBy(dbc.currentUser())) {
10776                    // checks if there is a shared lock and if the resource is deleted
10777                    // this solves the {@link org.opencms.file.TestPublishIssues#testPublishScenarioE} problem.
10778                    if (lock.isShared() && (publishList != null)) {
10779                        if (!res.getState().isDeleted()
10780                            || !checkDeletedParentFolder(dbc, publishList.getDeletedFolderList(), res)) {
10781                            continue;
10782                        }
10783                    } else {
10784                        // don't add locked resources
10785                        continue;
10786                    }
10787                }
10788                if (!"/".equals(res.getRootPath()) && !checkParentResource(dbc, newFolderList, res)) {
10789                    continue;
10790                }
10791                // check permissions
10792                try {
10793                    m_securityManager.checkPermissions(
10794                        dbc,
10795                        res,
10796                        CmsPermissionSet.ACCESS_DIRECT_PUBLISH,
10797                        false,
10798                        CmsResourceFilter.ALL);
10799                } catch (CmsException e) {
10800                    // skip if not enough permissions
10801                    continue;
10802                }
10803                if (res.isFolder()) {
10804                    newFolderList.add(res);
10805                }
10806                result.add(res);
10807            } catch (Exception e) {
10808                // should never happen
10809                LOG.error(e.getLocalizedMessage(), e);
10810            }
10811        }
10812        return result;
10813    }
10814
10815    /**
10816     * Returns a filtered list of sibling resources for publishing.<p>
10817     *
10818     * Contains all siblings of the given resources, which are not locked
10819     * and which have a parent folder that is already published or will be published, too.<p>
10820     *
10821     * @param dbc the current database context
10822     * @param publishList the unfinished publish list
10823     * @param resourceList the list of siblings to filter
10824     *
10825     * @return a filtered list of sibling resources for publishing
10826     */
10827    private List<CmsResource> filterSiblings(
10828        CmsDbContext dbc,
10829        CmsPublishList publishList,
10830        Collection<CmsResource> resourceList) {
10831
10832        List<CmsResource> result = new ArrayList<CmsResource>();
10833
10834        // removed internal extendible folder list, since iterated (sibling) resources are files in any case, never folders
10835
10836        for (CmsResource res : resourceList) {
10837            try {
10838                CmsLock lock = getLock(dbc, res);
10839                if (lock.isPublish()) {
10840                    // if already enqueued
10841                    continue;
10842                }
10843                if (!lock.isLockableBy(dbc.currentUser())) {
10844                    // checks if there is a shared lock and if the resource is deleted
10845                    // this solves the {@link org.opencms.file.TestPublishIssues#testPublishScenarioE} problem.
10846                    if (lock.isShared() && (publishList != null)) {
10847                        if (!res.getState().isDeleted()
10848                            || !checkDeletedParentFolder(dbc, publishList.getDeletedFolderList(), res)) {
10849                            continue;
10850                        }
10851                    } else {
10852                        // don't add locked resources
10853                        continue;
10854                    }
10855                }
10856                if (!"/".equals(res.getRootPath()) && !checkParentResource(dbc, publishList.getFolderList(), res)) {
10857                    // don't add resources that have no parent in the online project
10858                    continue;
10859                }
10860                // check permissions
10861                try {
10862                    m_securityManager.checkPermissions(
10863                        dbc,
10864                        res,
10865                        CmsPermissionSet.ACCESS_DIRECT_PUBLISH,
10866                        false,
10867                        CmsResourceFilter.ALL);
10868                } catch (CmsException e) {
10869                    // skip if not enough permissions
10870                    continue;
10871                }
10872                result.add(res);
10873            } catch (Exception e) {
10874                // should never happen
10875                LOG.error(e.getLocalizedMessage(), e);
10876            }
10877        }
10878        return result;
10879    }
10880
10881    /**
10882     * Returns the access control list of a given resource.<p>
10883     *
10884     * @param dbc the current database context
10885     * @param resource the resource
10886     * @param forFolder should be true if resource is a folder
10887     * @param depth the depth to include non-inherited access entries, also
10888     * @param inheritedOnly flag indicates to collect inherited permissions only
10889     *
10890     * @return the access control list of the resource
10891     *
10892     * @throws CmsException if something goes wrong
10893     */
10894    private CmsAccessControlList getAccessControlList(
10895        CmsDbContext dbc,
10896        CmsResource resource,
10897        boolean inheritedOnly,
10898        boolean forFolder,
10899        int depth) throws CmsException {
10900
10901        String cacheKey = getCacheKey(
10902            new String[] {
10903                inheritedOnly ? "+" : "-",
10904                forFolder ? "+" : "-",
10905                Integer.toString(depth),
10906                resource.getStructureId().toString()},
10907            dbc);
10908
10909        CmsAccessControlList acl = m_monitor.getCachedACL(cacheKey);
10910
10911        // return the cached acl if already available
10912        if ((acl != null) && dbc.getProjectId().isNullUUID()) {
10913            return acl;
10914        }
10915
10916        List<CmsAccessControlEntry> aces = getUserDriver(dbc).readAccessControlEntries(
10917            dbc,
10918            dbc.currentProject(),
10919            resource.getResourceId(),
10920            (depth > 1) || ((depth > 0) && forFolder));
10921
10922        // sort the list of aces
10923        boolean overwriteAll = sortAceList(aces);
10924
10925        // if no 'overwrite all' ace was found
10926        if (!overwriteAll) {
10927            // get the acl of the parent
10928            CmsResource parentResource = null;
10929            try {
10930                // try to recurse over the id
10931                parentResource = getVfsDriver(dbc).readParentFolder(
10932                    dbc,
10933                    dbc.currentProject().getUuid(),
10934                    resource.getStructureId());
10935            } catch (CmsVfsResourceNotFoundException e) {
10936                // should never happen, but try with the path
10937                String parentPath = CmsResource.getParentFolder(resource.getRootPath());
10938                if (parentPath != null) {
10939                    parentResource = getVfsDriver(dbc).readFolder(dbc, dbc.currentProject().getUuid(), parentPath);
10940                }
10941            }
10942            if (parentResource != null) {
10943                acl = (CmsAccessControlList)getAccessControlList(
10944                    dbc,
10945                    parentResource,
10946                    inheritedOnly,
10947                    forFolder,
10948                    depth + 1).clone();
10949            }
10950        }
10951        if (acl == null) {
10952            acl = new CmsAccessControlList();
10953        }
10954
10955        if (!((depth == 0) && inheritedOnly)) {
10956            Iterator<CmsAccessControlEntry> itAces = aces.iterator();
10957            while (itAces.hasNext()) {
10958                CmsAccessControlEntry acEntry = itAces.next();
10959                if (depth > 0) {
10960                    acEntry.setFlags(CmsAccessControlEntry.ACCESS_FLAGS_INHERITED);
10961                }
10962
10963                acl.add(acEntry);
10964
10965                // if the overwrite flag is set, reset the allowed permissions to the permissions of this entry
10966                // denied permissions are kept or extended
10967                if ((acEntry.getFlags() & CmsAccessControlEntry.ACCESS_FLAGS_OVERWRITE) > 0) {
10968                    acl.setAllowedPermissions(acEntry);
10969                }
10970            }
10971        }
10972        if (dbc.getProjectId().isNullUUID()) {
10973            m_monitor.cacheACL(cacheKey, acl);
10974        }
10975        return acl;
10976    }
10977
10978    /**
10979     * Return a cache key build from the provided information.<p>
10980     *
10981     * @param prefix a prefix for the key
10982     * @param flag a boolean flag for the key (only used if prefix is not null)
10983     * @param projectId the project for which to generate the key
10984     * @param resource the resource for which to generate the key
10985     *
10986     * @return String a cache key build from the provided information
10987     */
10988    private String getCacheKey(String prefix, boolean flag, CmsUUID projectId, String resource) {
10989
10990        StringBuffer b = new StringBuffer(64);
10991        if (prefix != null) {
10992            b.append(prefix);
10993            b.append(flag ? '+' : '-');
10994        }
10995        b.append(CmsProject.isOnlineProject(projectId) ? '+' : '-');
10996        return b.append(resource).toString();
10997    }
10998
10999    /**
11000     * Return a cache key build from the provided information.<p>
11001     *
11002     * @param keys an array of keys to generate the cache key from
11003     * @param dbc the database context for which to generate the key
11004     *
11005     * @return String a cache key build from the provided information
11006     */
11007    private String getCacheKey(String[] keys, CmsDbContext dbc) {
11008
11009        if (!dbc.getProjectId().isNullUUID()) {
11010            return "";
11011        }
11012        StringBuffer b = new StringBuffer(64);
11013        int len = keys.length;
11014        if (len > 0) {
11015            for (int i = 0; i < len; i++) {
11016                b.append(keys[i]);
11017                b.append('_');
11018            }
11019        }
11020        if (dbc.currentProject().isOnlineProject()) {
11021            b.append("+");
11022        } else {
11023            b.append("-");
11024        }
11025        return b.toString();
11026    }
11027
11028    /**
11029     * Returns the correct project id.<p>
11030     *
11031     * @param dbc the database context
11032     *
11033     * @return the correct project id
11034     */
11035    private CmsUUID getProjectIdForContext(CmsDbContext dbc) {
11036
11037        CmsUUID projectId = dbc.getProjectId();
11038        if (projectId.isNullUUID()) {
11039            projectId = dbc.currentProject().getUuid();
11040        }
11041        return projectId;
11042    }
11043
11044    /**
11045     * Returns if and what state needs to be updated.<p>
11046     *
11047     * @param dbc the db context
11048     * @param resource the resource
11049     * @param properties the properties to check
11050     *
11051     * @return 0: none, 1: structure, 2: resource
11052     *
11053     * @throws CmsDataAccessException if something goes wrong
11054     */
11055    private int getUpdateState(CmsDbContext dbc, CmsResource resource, List<CmsProperty> properties)
11056    throws CmsDataAccessException {
11057
11058        int updateState = 0;
11059        I_CmsVfsDriver vfsDriver = getVfsDriver(dbc);
11060        Iterator<CmsProperty> it = properties.iterator();
11061        while (it.hasNext() && (updateState < 2)) {
11062            CmsProperty property = it.next();
11063
11064            // read existing property
11065            CmsProperty existingProperty = vfsDriver.readPropertyObject(
11066                dbc,
11067                property.getName(),
11068                dbc.currentProject(),
11069                resource);
11070
11071            // check the shared property
11072            if (property.getResourceValue() != null) {
11073                if (property.isDeleteResourceValue()) {
11074                    if (existingProperty.getResourceValue() != null) {
11075                        updateState = 2; // deleted
11076                    }
11077                } else {
11078                    if (existingProperty.getResourceValue() == null) {
11079                        updateState = 2; // created
11080                    } else {
11081                        if (!property.getResourceValue().equals(existingProperty.getResourceValue())) {
11082                            updateState = 2; // updated
11083                        }
11084                    }
11085                }
11086            }
11087            if (updateState == 0) {
11088                // check the individual property only if needed
11089                if (property.getStructureValue() != null) {
11090                    if (property.isDeleteStructureValue()) {
11091                        if (existingProperty.getStructureValue() != null) {
11092                            updateState = 1; // deleted
11093                        }
11094                    } else {
11095                        if (existingProperty.getStructureValue() == null) {
11096                            updateState = 1; // created
11097                        } else {
11098                            if (!property.getStructureValue().equals(existingProperty.getStructureValue())) {
11099                                updateState = 1; // updated
11100                            }
11101                        }
11102                    }
11103                }
11104            }
11105        }
11106        return updateState;
11107    }
11108
11109    /**
11110     * Returns all groups that are virtualizing the given role in the given ou.<p>
11111     *
11112     * @param dbc the database context
11113     * @param role the role
11114     *
11115     * @return all groups that are virtualizing the given role (or a child of it)
11116     *
11117     * @throws CmsException if something goes wrong
11118     */
11119    private List<CmsGroup> getVirtualGroupsForRole(CmsDbContext dbc, CmsRole role) throws CmsException {
11120
11121        Set<Integer> roleFlags = new HashSet<Integer>();
11122        // add role flag
11123        Integer flags = new Integer(role.getVirtualGroupFlags());
11124        roleFlags.add(flags);
11125        // collect all child role flags
11126        Iterator<CmsRole> itChildRoles = role.getChildren(true).iterator();
11127        while (itChildRoles.hasNext()) {
11128            CmsRole child = itChildRoles.next();
11129            flags = new Integer(child.getVirtualGroupFlags());
11130            roleFlags.add(flags);
11131        }
11132        // iterate all groups matching the flags
11133        List<CmsGroup> groups = new ArrayList<CmsGroup>();
11134        Iterator<CmsGroup> it = getGroups(dbc, readOrganizationalUnit(dbc, role.getOuFqn()), false, false).iterator();
11135        while (it.hasNext()) {
11136            CmsGroup group = it.next();
11137            if (group.isVirtual()) {
11138                CmsRole r = CmsRole.valueOf(group);
11139                if (roleFlags.contains(new Integer(r.getVirtualGroupFlags()))) {
11140                    groups.add(group);
11141                }
11142            }
11143        }
11144        return groups;
11145    }
11146
11147    /**
11148     * Returns a list of users in a group.<p>
11149     *
11150     * @param dbc the current database context
11151     * @param ouFqn the organizational unit to get the users from
11152     * @param groupname the name of the group to list users from
11153     * @param includeOtherOuUsers include users of other organizational units
11154     * @param directUsersOnly if set only the direct assigned users will be returned,
11155     *                        if not also indirect users, ie. members of parent roles,
11156     *                        this parameter only works with roles
11157     * @param readRoles if to read roles or groups
11158     *
11159     * @return all <code>{@link CmsUser}</code> objects in the group
11160     *
11161     * @throws CmsException if operation was not successful
11162     */
11163    private List<CmsUser> internalUsersOfGroup(
11164        CmsDbContext dbc,
11165        String ouFqn,
11166        String groupname,
11167        boolean includeOtherOuUsers,
11168        boolean directUsersOnly,
11169        boolean readRoles) throws CmsException {
11170
11171        CmsGroup group = readGroup(dbc, groupname); // check that the group really exists
11172        if ((group == null) || (!((!readRoles && !group.isRole()) || (readRoles && group.isRole())))) {
11173            throw new CmsDbEntryNotFoundException(Messages.get().container(Messages.ERR_UNKNOWN_GROUP_1, groupname));
11174        }
11175
11176        String prefix = "_" + includeOtherOuUsers + "_" + directUsersOnly + "_" + ouFqn;
11177        String cacheKey = m_keyGenerator.getCacheKeyForGroupUsers(prefix, dbc, group);
11178        List<CmsUser> allUsers = m_monitor.getCachedUserList(cacheKey);
11179        if (allUsers == null) {
11180            Set<CmsUser> users = new HashSet<CmsUser>(
11181                getUserDriver(dbc).readUsersOfGroup(dbc, groupname, includeOtherOuUsers));
11182            if (readRoles && !directUsersOnly) {
11183                CmsRole role = CmsRole.valueOf(group);
11184                if (role.getParentRole() != null) {
11185                    try {
11186                        String parentGroup = role.getParentRole().getGroupName();
11187                        readGroup(dbc, parentGroup);
11188                        // iterate the parent roles
11189                        users.addAll(internalUsersOfGroup(
11190                            dbc,
11191                            ouFqn,
11192                            parentGroup,
11193                            includeOtherOuUsers,
11194                            directUsersOnly,
11195                            readRoles));
11196                    } catch (CmsDbEntryNotFoundException e) {
11197                        // ignore, this may happen while deleting an orgunit
11198                        if (LOG.isDebugEnabled()) {
11199                            LOG.debug(e.getLocalizedMessage(), e);
11200                        }
11201                    }
11202                }
11203                String parentOu = CmsOrganizationalUnit.getParentFqn(group.getOuFqn());
11204                if (parentOu != null) {
11205                    // iterate the parent ou's
11206                    users.addAll(
11207                        internalUsersOfGroup(
11208                            dbc,
11209                            ouFqn,
11210                            parentOu + group.getSimpleName(),
11211                            includeOtherOuUsers,
11212                            directUsersOnly,
11213                            readRoles));
11214                }
11215            } else if (!readRoles && !directUsersOnly) {
11216                List<CmsGroup> groups = getChildren(dbc, group, false);
11217                for (CmsGroup parentGroup : groups) {
11218                    try {
11219                        // iterate the parent groups
11220                        users.addAll(internalUsersOfGroup(
11221                            dbc,
11222                            ouFqn,
11223                            parentGroup.getName(),
11224                            includeOtherOuUsers,
11225                            directUsersOnly,
11226                            readRoles));
11227                    } catch (CmsDbEntryNotFoundException e) {
11228                        // ignore, this may happen while deleting an orgunit
11229                        if (LOG.isDebugEnabled()) {
11230                            LOG.debug(e.getLocalizedMessage(), e);
11231                        }
11232                    }
11233                }
11234            }
11235            // filter users from other ous
11236            if (!includeOtherOuUsers) {
11237                Iterator<CmsUser> itUsers = users.iterator();
11238                while (itUsers.hasNext()) {
11239                    CmsUser user = itUsers.next();
11240                    if (!user.getOuFqn().equals(ouFqn)) {
11241                        itUsers.remove();
11242                    }
11243                }
11244            }
11245
11246            // make user list unmodifiable for caching
11247            allUsers = Collections.unmodifiableList(new ArrayList<CmsUser>(users));
11248            if (dbc.getProjectId().isNullUUID()) {
11249                m_monitor.cacheUserList(cacheKey, allUsers);
11250            }
11251        }
11252        return allUsers;
11253    }
11254
11255    /**
11256     * Reads all resources that are inside and changed in a specified project.<p>
11257     *
11258     * @param dbc the current database context
11259     * @param projectId the ID of the project
11260     * @param mode one of the {@link CmsReadChangedProjectResourceMode} constants
11261     *
11262     * @return a List with all resources inside the specified project
11263     *
11264     * @throws CmsException if something goes wrong
11265     */
11266    private List<CmsResource> readChangedResourcesInsideProject(
11267        CmsDbContext dbc,
11268        CmsUUID projectId,
11269        CmsReadChangedProjectResourceMode mode) throws CmsException {
11270
11271        String cacheKey = projectId + "_" + mode.toString();
11272        List<CmsResource> result = m_monitor.getCachedProjectResources(cacheKey);
11273        if (result != null) {
11274            return result;
11275        }
11276        List<String> projectResources = readProjectResources(dbc, readProject(dbc, projectId));
11277        result = new ArrayList<CmsResource>();
11278        String currentProjectResource = null;
11279        List<CmsResource> resources = new ArrayList<CmsResource>();
11280        CmsResource currentResource = null;
11281        CmsLock currentLock = null;
11282
11283        for (int i = 0; i < projectResources.size(); i++) {
11284            // read all resources that are inside the project by visiting each project resource
11285            currentProjectResource = projectResources.get(i);
11286
11287            try {
11288                currentResource = readResource(dbc, currentProjectResource, CmsResourceFilter.ALL);
11289
11290                if (currentResource.isFolder()) {
11291                    resources.addAll(readResources(dbc, currentResource, CmsResourceFilter.ALL, true));
11292                } else {
11293                    resources.add(currentResource);
11294                }
11295            } catch (CmsException e) {
11296                // the project resource probably doesn't exist (anymore)...
11297                if (!(e instanceof CmsVfsResourceNotFoundException)) {
11298                    throw e;
11299                }
11300            }
11301        }
11302
11303        for (int j = 0; j < resources.size(); j++) {
11304            currentResource = resources.get(j);
11305            currentLock = getLock(dbc, currentResource).getEditionLock();
11306
11307            if (!currentResource.getState().isUnchanged()) {
11308                if ((currentLock.isNullLock() && (currentResource.getProjectLastModified().equals(projectId)))
11309                    || (currentLock.isOwnedBy(dbc.currentUser()) && (currentLock.getProjectId().equals(projectId)))) {
11310                    // add only resources that are
11311                    // - inside the project,
11312                    // - changed in the project,
11313                    // - either unlocked, or locked for the current user in the project
11314                    if ((mode == RCPRM_FILES_AND_FOLDERS_MODE)
11315                        || (currentResource.isFolder() && (mode == RCPRM_FOLDERS_ONLY_MODE))
11316                        || (currentResource.isFile() && (mode == RCPRM_FILES_ONLY_MODE))) {
11317                        result.add(currentResource);
11318                    }
11319                }
11320            }
11321        }
11322
11323        resources.clear();
11324        resources = null;
11325
11326        m_monitor.cacheProjectResources(cacheKey, result);
11327        return result;
11328    }
11329
11330    /**
11331     * Sorts the given list of {@link CmsAccessControlEntry} objects.<p>
11332     *
11333     * The the 'all others' ace in first place, the 'overwrite all' ace in second.<p>
11334     *
11335     * @param aces the list of ACEs to sort
11336     *
11337     * @return <code>true</code> if the list contains the 'overwrite all' ace
11338     */
11339    private boolean sortAceList(List<CmsAccessControlEntry> aces) {
11340
11341        // sort the list of entries
11342        Collections.sort(aces, CmsAccessControlEntry.COMPARATOR_ACE);
11343        // after sorting just the first 2 positions come in question
11344        for (int i = 0; i < Math.min(aces.size(), 2); i++) {
11345            CmsAccessControlEntry acEntry = aces.get(i);
11346            if (acEntry.getPrincipal().equals(CmsAccessControlEntry.PRINCIPAL_OVERWRITE_ALL_ID)) {
11347                return true;
11348            }
11349        }
11350        return false;
11351    }
11352
11353    /**
11354     * All permissions and resources attributes of the principal
11355     * are transfered to a replacement principal.<p>
11356     *
11357     * @param dbc the current database context
11358     * @param project the current project
11359     * @param principalId the id of the principal to be replaced
11360     * @param replacementId the user to be transfered
11361     * @param withACEs flag to signal if the ACEs should also be transfered or just deleted
11362     *
11363     * @throws CmsException if operation was not successful
11364     */
11365    private void transferPrincipalResources(
11366        CmsDbContext dbc,
11367        CmsProject project,
11368        CmsUUID principalId,
11369        CmsUUID replacementId,
11370        boolean withACEs) throws CmsException {
11371
11372        // get all resources for the given user including resources associated by ACEs or attributes
11373        I_CmsUserDriver userDriver = getUserDriver(dbc);
11374        I_CmsVfsDriver vfsDriver = getVfsDriver(dbc);
11375        Set<CmsResource> resources = getResourcesForPrincipal(dbc, project, principalId, null, true);
11376        Iterator<CmsResource> it = resources.iterator();
11377        while (it.hasNext()) {
11378            CmsResource resource = it.next();
11379            // check resource attributes
11380            boolean attrModified = false;
11381            CmsUUID createdUser = null;
11382            if (resource.getUserCreated().equals(principalId)) {
11383                createdUser = replacementId;
11384                attrModified = true;
11385            }
11386            CmsUUID lastModUser = null;
11387            if (resource.getUserLastModified().equals(principalId)) {
11388                lastModUser = replacementId;
11389                attrModified = true;
11390            }
11391            if (attrModified) {
11392                vfsDriver.transferResource(dbc, project, resource, createdUser, lastModUser);
11393                // clear the cache
11394                m_monitor.clearResourceCache();
11395            }
11396            boolean aceModified = false;
11397            // check aces
11398            if (withACEs) {
11399                Iterator<CmsAccessControlEntry> itAces = userDriver.readAccessControlEntries(
11400                    dbc,
11401                    project,
11402                    resource.getResourceId(),
11403                    false).iterator();
11404                while (itAces.hasNext()) {
11405                    CmsAccessControlEntry ace = itAces.next();
11406                    if (ace.getPrincipal().equals(principalId)) {
11407                        CmsAccessControlEntry newAce = new CmsAccessControlEntry(
11408                            ace.getResource(),
11409                            replacementId,
11410                            ace.getAllowedPermissions(),
11411                            ace.getDeniedPermissions(),
11412                            ace.getFlags());
11413                        // write the new ace
11414                        userDriver.writeAccessControlEntry(dbc, project, newAce);
11415                        aceModified = true;
11416                    }
11417                }
11418                if (aceModified) {
11419                    // clear the cache
11420                    m_monitor.clearAccessControlListCache();
11421                }
11422            }
11423            if (attrModified || aceModified) {
11424                // fire the event
11425                Map<String, Object> data = new HashMap<String, Object>(2);
11426                data.put(I_CmsEventListener.KEY_RESOURCE, resource);
11427                data.put(
11428                    I_CmsEventListener.KEY_CHANGE,
11429                    new Integer(((attrModified) ? CHANGED_RESOURCE : 0) | ((aceModified) ? CHANGED_ACCESSCONTROL : 0)));
11430                OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_MODIFIED, data));
11431            }
11432        }
11433    }
11434
11435    /**
11436     * Undoes all content changes of a resource.<p>
11437     *
11438     * @param dbc the database context
11439     * @param onlineProject the online project
11440     * @param offlineResource the offline resource, or <code>null</code> if deleted
11441     * @param onlineResource the online resource
11442     * @param newState the new resource state
11443     * @param moveUndone is a move operation on the same resource has been made
11444     *
11445     * @throws CmsException if something goes wrong
11446     */
11447    private void undoContentChanges(
11448        CmsDbContext dbc,
11449        CmsProject onlineProject,
11450        CmsResource offlineResource,
11451        CmsResource onlineResource,
11452        CmsResourceState newState,
11453        boolean moveUndone) throws CmsException {
11454
11455        String path = ((moveUndone || (offlineResource == null))
11456        ? onlineResource.getRootPath()
11457        : offlineResource.getRootPath());
11458
11459        // change folder or file?
11460        I_CmsUserDriver userDriver = getUserDriver(dbc);
11461        I_CmsVfsDriver vfsDriver = getVfsDriver(dbc);
11462        if (onlineResource.isFolder()) {
11463            CmsFolder restoredFolder = new CmsFolder(
11464                onlineResource.getStructureId(),
11465                onlineResource.getResourceId(),
11466                path,
11467                onlineResource.getTypeId(),
11468                onlineResource.getFlags(),
11469                dbc.currentProject().getUuid(),
11470                newState,
11471                onlineResource.getDateCreated(),
11472                onlineResource.getUserCreated(),
11473                onlineResource.getDateLastModified(),
11474                onlineResource.getUserLastModified(),
11475                onlineResource.getDateReleased(),
11476                onlineResource.getDateExpired(),
11477                onlineResource.getVersion()); // version number does not matter since it will be computed later
11478
11479            // write the folder in the offline project
11480            // this sets a flag so that the folder date is not set to the current time
11481            restoredFolder.setDateLastModified(onlineResource.getDateLastModified());
11482
11483            // write the folder
11484            vfsDriver.writeResource(dbc, dbc.currentProject().getUuid(), restoredFolder, NOTHING_CHANGED);
11485
11486            // restore the properties from the online project
11487            vfsDriver.deletePropertyObjects(
11488                dbc,
11489                dbc.currentProject().getUuid(),
11490                restoredFolder,
11491                CmsProperty.DELETE_OPTION_DELETE_STRUCTURE_AND_RESOURCE_VALUES);
11492
11493            List<CmsProperty> propertyInfos = vfsDriver.readPropertyObjects(dbc, onlineProject, onlineResource);
11494            vfsDriver.writePropertyObjects(dbc, dbc.currentProject(), restoredFolder, propertyInfos);
11495
11496            // restore the access control entries from the online project
11497            userDriver.removeAccessControlEntries(dbc, dbc.currentProject(), onlineResource.getResourceId());
11498            ListIterator<CmsAccessControlEntry> aceList = userDriver.readAccessControlEntries(
11499                dbc,
11500                onlineProject,
11501                onlineResource.getResourceId(),
11502                false).listIterator();
11503
11504            while (aceList.hasNext()) {
11505                CmsAccessControlEntry ace = aceList.next();
11506                userDriver.createAccessControlEntry(
11507                    dbc,
11508                    dbc.currentProject(),
11509                    onlineResource.getResourceId(),
11510                    ace.getPrincipal(),
11511                    ace.getPermissions().getAllowedPermissions(),
11512                    ace.getPermissions().getDeniedPermissions(),
11513                    ace.getFlags());
11514            }
11515        } else {
11516            byte[] onlineContent = vfsDriver.readContent(
11517                dbc,
11518                CmsProject.ONLINE_PROJECT_ID,
11519                onlineResource.getResourceId());
11520
11521            CmsFile restoredFile = new CmsFile(
11522                onlineResource.getStructureId(),
11523                onlineResource.getResourceId(),
11524                path,
11525                onlineResource.getTypeId(),
11526                onlineResource.getFlags(),
11527                dbc.currentProject().getUuid(),
11528                newState,
11529                onlineResource.getDateCreated(),
11530                onlineResource.getUserCreated(),
11531                onlineResource.getDateLastModified(),
11532                onlineResource.getUserLastModified(),
11533                onlineResource.getDateReleased(),
11534                onlineResource.getDateExpired(),
11535                0,
11536                onlineResource.getLength(),
11537                onlineResource.getDateContent(),
11538                onlineResource.getVersion(), // version number does not matter since it will be computed later
11539                onlineContent);
11540
11541            // write the file in the offline project
11542            // this sets a flag so that the file date is not set to the current time
11543            restoredFile.setDateLastModified(onlineResource.getDateLastModified());
11544
11545            // collect the old properties
11546            List<CmsProperty> properties = vfsDriver.readPropertyObjects(dbc, onlineProject, onlineResource);
11547
11548            if (offlineResource != null) {
11549                // bug fix 1020: delete all properties (inclum_rejectStructureIdded shared),
11550                // shared properties will be recreated by the next call of #createResource(...)
11551                vfsDriver.deletePropertyObjects(
11552                    dbc,
11553                    dbc.currentProject().getUuid(),
11554                    onlineResource,
11555                    CmsProperty.DELETE_OPTION_DELETE_STRUCTURE_AND_RESOURCE_VALUES);
11556
11557                // implementation notes:
11558                // undo changes can become complex e.g. if a resource was deleted, and then
11559                // another resource was copied over the deleted file as a sibling
11560                // therefore we must "clean" delete the offline resource, and then create
11561                // an new resource with the create method
11562                // note that this does NOT apply to folders, since a folder cannot be replaced
11563                // like a resource anyway
11564                deleteResource(dbc, offlineResource, CmsResource.DELETE_PRESERVE_SIBLINGS);
11565            }
11566            CmsResource res = createResource(
11567                dbc,
11568                restoredFile.getRootPath(),
11569                restoredFile,
11570                restoredFile.getContents(),
11571                properties,
11572                false);
11573
11574            // copy the access control entries from the online project
11575            if (offlineResource != null) {
11576                userDriver.removeAccessControlEntries(dbc, dbc.currentProject(), onlineResource.getResourceId());
11577            }
11578            ListIterator<CmsAccessControlEntry> aceList = userDriver.readAccessControlEntries(
11579                dbc,
11580                onlineProject,
11581                onlineResource.getResourceId(),
11582                false).listIterator();
11583
11584            while (aceList.hasNext()) {
11585                CmsAccessControlEntry ace = aceList.next();
11586                userDriver.createAccessControlEntry(
11587                    dbc,
11588                    dbc.currentProject(),
11589                    res.getResourceId(),
11590                    ace.getPrincipal(),
11591                    ace.getPermissions().getAllowedPermissions(),
11592                    ace.getPermissions().getDeniedPermissions(),
11593                    ace.getFlags());
11594            }
11595
11596            vfsDriver.deleteUrlNameMappingEntries(
11597                dbc,
11598                false,
11599                CmsUrlNameMappingFilter.ALL.filterStructureId(res.getStructureId()).filterStates(
11600                    CmsUrlNameMappingEntry.MAPPING_STATUS_NEW,
11601                    CmsUrlNameMappingEntry.MAPPING_STATUS_REPLACE_ON_PUBLISH));
11602            // restore the state to unchanged
11603            res.setState(newState);
11604            m_vfsDriver.writeResourceState(dbc, dbc.currentProject(), res, UPDATE_ALL, false);
11605        }
11606
11607        // delete all offline relations
11608        if (offlineResource != null) {
11609            vfsDriver.deleteRelations(dbc, dbc.currentProject().getUuid(), offlineResource, CmsRelationFilter.TARGETS);
11610        }
11611        // get online relations
11612        List<CmsRelation> relations = vfsDriver.readRelations(
11613            dbc,
11614            CmsProject.ONLINE_PROJECT_ID,
11615            onlineResource,
11616            CmsRelationFilter.TARGETS);
11617        // write offline relations
11618        Iterator<CmsRelation> itRelations = relations.iterator();
11619        while (itRelations.hasNext()) {
11620            CmsRelation relation = itRelations.next();
11621            vfsDriver.createRelation(dbc, dbc.currentProject().getUuid(), relation);
11622        }
11623
11624        // update the cache
11625        m_monitor.clearResourceCache();
11626        m_monitor.flushCache(CmsMemoryMonitor.CacheType.PROPERTY, CmsMemoryMonitor.CacheType.PROPERTY_LIST);
11627
11628        if ((offlineResource == null) || offlineResource.getRootPath().equals(onlineResource.getRootPath())) {
11629            log(
11630                dbc,
11631                new CmsLogEntry(
11632                    dbc,
11633                    onlineResource.getStructureId(),
11634                    CmsLogEntryType.RESOURCE_RESTORED,
11635                    new String[] {onlineResource.getRootPath()}),
11636                false);
11637        } else {
11638            log(
11639                dbc,
11640                new CmsLogEntry(
11641                    dbc,
11642                    offlineResource.getStructureId(),
11643                    CmsLogEntryType.RESOURCE_MOVE_RESTORED,
11644                    new String[] {offlineResource.getRootPath(), onlineResource.getRootPath()}),
11645                false);
11646        }
11647        if (offlineResource != null) {
11648            OpenCms.fireCmsEvent(
11649                new CmsEvent(
11650                    I_CmsEventListener.EVENT_RESOURCE_AND_PROPERTIES_MODIFIED,
11651                    Collections.<String, Object> singletonMap(I_CmsEventListener.KEY_RESOURCE, offlineResource)));
11652        } else {
11653            OpenCms.fireCmsEvent(
11654                new CmsEvent(
11655                    I_CmsEventListener.EVENT_RESOURCE_AND_PROPERTIES_MODIFIED,
11656                    Collections.<String, Object> singletonMap(I_CmsEventListener.KEY_RESOURCE, onlineResource)));
11657        }
11658    }
11659
11660    /**
11661     * Updates the current users context dates with the given resource.<p>
11662     *
11663     * This checks the date information of the resource based on
11664     * {@link CmsResource#getDateLastModified()} as well as
11665     * {@link CmsResource#getDateReleased()} and {@link CmsResource#getDateExpired()}.
11666     * The current users request context is updated with the the "latest" dates found.<p>
11667     *
11668     * This is required in order to ensure proper setting of <code>"last-modified"</code> http headers
11669     * and also for expiration of cached elements in the Flex cache.
11670     * Consider the following use case: Page A is generated from resources x, y and z.
11671     * If either x, y or z has an expiration / release date set, then page A must expire at a certain point
11672     * in time. This is ensured by the context date check here.<p>
11673     *
11674     * @param dbc the current database context
11675     * @param resource the resource to get the date information from
11676     */
11677    private void updateContextDates(CmsDbContext dbc, CmsResource resource) {
11678
11679        CmsFlexRequestContextInfo info = dbc.getFlexRequestContextInfo();
11680        if (info != null) {
11681            info.updateFromResource(resource);
11682        }
11683    }
11684
11685    /**
11686     * Updates the current users context dates with each {@link CmsResource} object in the given list.<p>
11687     *
11688     * The given input list is returned unmodified.<p>
11689     *
11690     * Please see {@link #updateContextDates(CmsDbContext, CmsResource)} for an explanation of what this method does.<p>
11691     *
11692     * @param dbc the current database context
11693     * @param resourceList a list of {@link CmsResource} objects
11694     *
11695     * @return the original list of CmsResources with the full resource name set
11696     */
11697    private List<CmsResource> updateContextDates(CmsDbContext dbc, List<CmsResource> resourceList) {
11698
11699        CmsFlexRequestContextInfo info = dbc.getFlexRequestContextInfo();
11700        if (info != null) {
11701            for (int i = 0; i < resourceList.size(); i++) {
11702                CmsResource resource = resourceList.get(i);
11703                info.updateFromResource(resource);
11704            }
11705        }
11706        return resourceList;
11707    }
11708
11709    /**
11710     * Returns a List of {@link CmsResource} objects generated when applying the given filter to the given list,
11711     * also updates the current users context dates with each {@link CmsResource} object in the given list,
11712     * also applies the selected resource filter to all resources in the list and returns the remaining resources.<p>
11713     *
11714     * Please see {@link #updateContextDates(CmsDbContext, CmsResource)} for an explanation of what this method does.<p>
11715     *
11716     * @param dbc the current database context
11717     * @param resourceList a list of {@link CmsResource} objects
11718     * @param filter the resource filter to use
11719     *
11720     * @return a List of {@link CmsResource} objects generated when applying the given filter to the given list
11721     */
11722    private List<CmsResource> updateContextDates(
11723        CmsDbContext dbc,
11724        List<CmsResource> resourceList,
11725        CmsResourceFilter filter) {
11726
11727        if (CmsResourceFilter.ALL == filter) {
11728            // if there is no filter required, then use the simpler method that does not apply the filter
11729            return new ArrayList<CmsResource>(updateContextDates(dbc, resourceList));
11730        }
11731
11732        CmsFlexRequestContextInfo info = dbc.getFlexRequestContextInfo();
11733        List<CmsResource> result = new ArrayList<CmsResource>(resourceList.size());
11734        for (int i = 0; i < resourceList.size(); i++) {
11735            CmsResource resource = resourceList.get(i);
11736            if (filter.isValid(dbc.getRequestContext(), resource)) {
11737                result.add(resource);
11738            }
11739            // must also include "invalid" resources for the update of context dates
11740            // since a resource may be invalid because of release / expiration date
11741            if (info != null) {
11742                info.updateFromResource(resource);
11743            }
11744        }
11745        return result;
11746    }
11747
11748    /**
11749     * Updates the state of a resource, depending on the <code>resourceState</code> parameter.<p>
11750     *
11751     * @param dbc the db context
11752     * @param resource the resource
11753     * @param resourceState if <code>true</code> the resource state will be updated, if not just the structure state.
11754     *
11755     * @throws CmsDataAccessException if something goes wrong
11756     */
11757    private void updateState(CmsDbContext dbc, CmsResource resource, boolean resourceState)
11758    throws CmsDataAccessException {
11759
11760        CmsUUID projectId = ((dbc.getProjectId() == null) || dbc.getProjectId().isNullUUID())
11761        ? dbc.currentProject().getUuid()
11762        : dbc.getProjectId();
11763        resource.setUserLastModified(dbc.currentUser().getId());
11764        if (resourceState) {
11765            // update the whole resource state
11766            getVfsDriver(dbc).writeResource(dbc, projectId, resource, UPDATE_RESOURCE_STATE);
11767        } else {
11768            // update the structure state
11769            getVfsDriver(dbc).writeResource(dbc, projectId, resource, UPDATE_STRUCTURE_STATE);
11770        }
11771    }
11772
11773}