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