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