001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (c) Alkacon Software GmbH & Co. KG (https://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: https://www.alkacon.com
019 *
020 * For further information about OpenCms, please see the
021 * project website: https://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.storage.I_CmsStorageDelivery;
040import org.opencms.db.timing.CmsDefaultProfilingHandler;
041import org.opencms.db.timing.CmsProfilingInvocationHandler;
042import org.opencms.db.urlname.CmsUrlNameMappingEntry;
043import org.opencms.db.urlname.CmsUrlNameMappingFilter;
044import org.opencms.db.userpublishlist.A_CmsLogPublishListConverter;
045import org.opencms.db.userpublishlist.CmsLogPublishListConverterAllUsers;
046import org.opencms.db.userpublishlist.CmsLogPublishListConverterCurrentUser;
047import org.opencms.file.CmsDataAccessException;
048import org.opencms.file.CmsFile;
049import org.opencms.file.CmsFolder;
050import org.opencms.file.CmsGroup;
051import org.opencms.file.CmsObject;
052import org.opencms.file.CmsProject;
053import org.opencms.file.CmsProperty;
054import org.opencms.file.CmsPropertyDefinition;
055import org.opencms.file.CmsRequestContext;
056import org.opencms.file.CmsResource;
057import org.opencms.file.CmsResourceFilter;
058import org.opencms.file.CmsStoredContentInfo;
059import org.opencms.file.CmsUser;
060import org.opencms.file.CmsUserSearchParameters;
061import org.opencms.file.CmsVfsException;
062import org.opencms.file.CmsVfsResourceAlreadyExistsException;
063import org.opencms.file.CmsVfsResourceNotFoundException;
064import org.opencms.file.I_CmsFileContentStreamHandler;
065import org.opencms.file.I_CmsResource;
066import org.opencms.file.history.CmsHistoryFile;
067import org.opencms.file.history.CmsHistoryFolder;
068import org.opencms.file.history.CmsHistoryPrincipal;
069import org.opencms.file.history.CmsHistoryProject;
070import org.opencms.file.history.I_CmsHistoryResource;
071import org.opencms.file.quota.CmsFolderSizeEntry;
072import org.opencms.file.quota.CmsFolderSizeOptions;
073import org.opencms.file.types.CmsResourceTypeFolder;
074import org.opencms.file.types.CmsResourceTypeJsp;
075import org.opencms.file.types.I_CmsResourceType;
076import org.opencms.flex.CmsFlexRequestContextInfo;
077import org.opencms.gwt.shared.alias.CmsAliasImportResult;
078import org.opencms.gwt.shared.alias.CmsAliasImportStatus;
079import org.opencms.gwt.shared.alias.CmsAliasMode;
080import org.opencms.i18n.CmsLocaleManager;
081import org.opencms.i18n.CmsMessageContainer;
082import org.opencms.jsp.CmsJspNavBuilder;
083import org.opencms.lock.CmsLock;
084import org.opencms.lock.CmsLockException;
085import org.opencms.lock.CmsLockFilter;
086import org.opencms.lock.CmsLockManager;
087import org.opencms.lock.CmsLockType;
088import org.opencms.main.CmsEvent;
089import org.opencms.main.CmsException;
090import org.opencms.main.CmsIllegalArgumentException;
091import org.opencms.main.CmsIllegalStateException;
092import org.opencms.main.CmsInitException;
093import org.opencms.main.CmsLog;
094import org.opencms.main.CmsMultiException;
095import org.opencms.main.I_CmsEventListener;
096import org.opencms.main.OpenCms;
097import org.opencms.module.CmsModule;
098import org.opencms.monitor.CmsMemoryMonitor;
099import org.opencms.monitor.CmsMemoryMonitor.CacheType;
100import org.opencms.publish.CmsPublishEngine;
101import org.opencms.publish.CmsPublishJobInfoBean;
102import org.opencms.publish.CmsPublishReport;
103import org.opencms.relations.CmsCategoryService;
104import org.opencms.relations.CmsLink;
105import org.opencms.relations.CmsRelation;
106import org.opencms.relations.CmsRelationFilter;
107import org.opencms.relations.CmsRelationSystemValidator;
108import org.opencms.relations.CmsRelationType;
109import org.opencms.relations.CmsRelationType.CopyBehavior;
110import org.opencms.relations.I_CmsLinkParseable;
111import org.opencms.report.CmsLogReport;
112import org.opencms.report.I_CmsReport;
113import org.opencms.security.CmsAccessControlEntry;
114import org.opencms.security.CmsAccessControlList;
115import org.opencms.security.CmsAuthentificationException;
116import org.opencms.security.CmsOrganizationalUnit;
117import org.opencms.security.CmsPasswordEncryptionException;
118import org.opencms.security.CmsPermissionSet;
119import org.opencms.security.CmsPermissionSetCustom;
120import org.opencms.security.CmsPrincipal;
121import org.opencms.security.CmsRole;
122import org.opencms.security.CmsSecurityException;
123import org.opencms.security.I_CmsPermissionHandler;
124import org.opencms.security.I_CmsPermissionHandler.LockCheck;
125import org.opencms.security.I_CmsPrincipal;
126import org.opencms.security.twofactor.CmsSecondFactorInfo;
127import org.opencms.security.twofactor.CmsSecondFactorSetupException;
128import org.opencms.security.twofactor.CmsTwoFactorAuthenticationHandler;
129import org.opencms.site.CmsSiteMatcher;
130import org.opencms.util.CmsFileUtil;
131import org.opencms.util.CmsPath;
132import org.opencms.util.CmsStringUtil;
133import org.opencms.util.CmsUUID;
134import org.opencms.util.PrintfFormat;
135import org.opencms.workflow.CmsDefaultWorkflowManager;
136import org.opencms.workplace.threads.A_CmsProgressThread;
137
138import java.io.ByteArrayInputStream;
139import java.io.IOException;
140import java.io.OutputStream;
141import java.lang.reflect.Proxy;
142import java.util.ArrayList;
143import java.util.Arrays;
144import java.util.Collection;
145import java.util.Collections;
146import java.util.Comparator;
147import java.util.Date;
148import java.util.HashMap;
149import java.util.HashSet;
150import java.util.Iterator;
151import java.util.List;
152import java.util.ListIterator;
153import java.util.Locale;
154import java.util.Map;
155import java.util.Map.Entry;
156import java.util.Set;
157import java.util.TreeSet;
158import java.util.concurrent.ConcurrentMap;
159import java.util.concurrent.ExecutionException;
160import java.util.concurrent.TimeUnit;
161import java.util.function.Predicate;
162import java.util.function.Supplier;
163import java.util.regex.Pattern;
164import java.util.regex.PatternSyntaxException;
165import java.util.stream.Collectors;
166
167import org.apache.commons.logging.Log;
168
169import com.google.common.cache.Cache;
170import com.google.common.cache.CacheBuilder;
171import com.google.common.collect.ArrayListMultimap;
172import com.google.common.collect.Maps;
173import com.google.common.collect.Multimap;
174
175/**
176 * The OpenCms driver manager.<p>
177 *
178 * @since 6.0.0
179 */
180public final class CmsDriverManager implements I_CmsEventListener {
181
182    /**
183     * Enum for distinguishing between login modes.
184     */
185    public static enum LoginUserMode {
186        /** 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). */
187        checkOnly,
188
189        /** Normal login process. */
190        standard
191    }
192
193    /**
194     * Resource list which additionally knows whether it should be cacheable in the resource list cache or not.
195     */
196    public static class ResourceListWithCacheability extends ArrayList<CmsResource> {
197
198        /** Serial version id. */
199        private static final long serialVersionUID = 1L;
200
201        /** True if the list should be cacheable. */
202        private boolean m_cacheable = true;
203
204        /**
205         * Creates a new instance.
206         */
207        public ResourceListWithCacheability() {
208
209            super();
210        }
211
212        /**
213         * Creates a new instance.
214         * @param initialCapacity the initial capacity
215         */
216        public ResourceListWithCacheability(int initialCapacity) {
217
218            super(initialCapacity);
219        }
220
221        /**
222         * Returns true if the resource list is cacheable.
223         *
224         * @return true if the list is cacheable
225         */
226        public boolean isCacheable() {
227
228            return m_cacheable;
229        }
230
231        /**
232         * Enables/disables cacheability for the resource list.
233         * @param cacheable true if the list should be cacheable
234         */
235        public void setCacheable(boolean cacheable) {
236
237            m_cacheable = cacheable;
238        }
239
240    }
241
242    /**
243     * Special key class for caching the resource OU data with a Guava LoadingCache.<p>
244     *
245     * In principle, the actual cache key is just the current project, but because of how cache loaders work,
246     * the key must contain everything that varies between calls and is required to load the value. So we also store the DB context
247     * for use by the cache loader. The project (offline/online) must still be stored, because the DB context gets invalidated
248     * eventually, i.e. its project id gets nulled.
249     */
250    public static class ResourceOUCacheKey {
251
252        /** The actual cache key. */
253        private String m_actualKey;
254
255        /** The DB context. */
256        private CmsDbContext m_dbc;
257
258        /** The driver manager to use. */
259        private CmsDriverManager m_driverManager;
260
261        /**
262         * Creates a new instance.
263         *
264         * @param driverManager the driver manager to use
265         * @param dbc the current DB context
266         */
267        public ResourceOUCacheKey(CmsDriverManager driverManager, CmsDbContext dbc) {
268
269            m_dbc = dbc;
270            m_driverManager = driverManager;
271            m_actualKey = CmsProject.ONLINE_PROJECT_ID.equals(dbc.currentProject().getId()) ? "ONLINE" : "OFFLINE";
272        }
273
274        /**
275         * @see java.lang.Object#equals(java.lang.Object)
276         */
277        @Override
278        public boolean equals(Object obj) {
279
280            return (obj instanceof ResourceOUCacheKey)
281                && ((ResourceOUCacheKey)obj).getActualKey().equals(getActualKey());
282        }
283
284        /**
285         * Gets the stored DB context.<p>
286         *
287         * Note that the DB contex returned by this may have been invalidated!
288         *
289         * @return the stored DB context
290         */
291        public CmsDbContext getDbContext() {
292
293            return m_dbc;
294        }
295
296        /**
297         * Gets the current driver manager.
298         *
299         * @return the driver manager to use
300         */
301        public CmsDriverManager getDriverManager() {
302
303            return m_driverManager;
304        }
305
306        /**
307         * @see java.lang.Object#hashCode()
308         */
309        @Override
310        public int hashCode() {
311
312            return getActualKey().hashCode();
313        }
314
315        /**
316         * Gets the actual key data.
317         *
318         * @return the actual key data
319         */
320        private String getActualKey() {
321
322            return m_actualKey;
323        }
324
325    }
326
327    /**
328     * Helper class used to store information about resources assigned to OUs in a cache.
329     */
330    public static class ResourceOUMap {
331
332        /** Multimap from the paths of resources to the OUs to which they are assigned as OU resources. */
333        private Multimap<CmsPath, CmsOrganizationalUnit> m_ousByAssignedResourcePaths = ArrayListMultimap.create();
334
335        /** The organizational units, with their UUIDs as keys. */
336        private Map<CmsUUID, CmsOrganizationalUnit> m_ousById = new HashMap<>();
337
338        /**
339         * Gets the list of organizational units to which a given root path belongs, according to the cached
340         * OU resource assignments.
341         *
342         * @param rootPath the root path
343         * @return the organizational units to which the path belongs
344         */
345        public List<CmsOrganizationalUnit> getResourceOrgUnits(String rootPath) {
346
347            Set<CmsOrganizationalUnit> result = new HashSet<>();
348            String currentPath = rootPath;
349            while (currentPath != null) {
350                result.addAll(m_ousByAssignedResourcePaths.get(new CmsPath(currentPath)));
351                currentPath = CmsResource.getParentFolder(currentPath);
352            }
353            return new ArrayList<>(result);
354        }
355
356        /**
357         * Reads the OU resource data from the VFS and initializes this instance with it.
358         *
359         * @param driverManager the driver manager to use
360         * @param dbc the current DB context
361         * @throws CmsException if something goes wrong
362         */
363        public void init(CmsDriverManager driverManager, CmsDbContext dbc) throws CmsException {
364
365            List<CmsRelation> relations = driverManager.getRelationsForResource(
366                dbc,
367                null,
368                CmsRelationFilter.ALL.filterType(CmsRelationType.OU_RESOURCE));
369            CmsOrganizationalUnit root = driverManager.readOrganizationalUnit(dbc, "");
370            List<CmsOrganizationalUnit> children = driverManager.getOrganizationalUnits(dbc, root, true);
371
372            Set<CmsOrganizationalUnit> ous = new HashSet<>();
373            ous.add(root);
374            ous.addAll(children);
375            init(relations, ous);
376
377        }
378
379        /**
380         * Initializes the OU resource data.
381         *
382         * @param ouRelations the current list of OU relations
383         * @param ous the current list of OUs
384         */
385        public void init(Collection<CmsRelation> ouRelations, Collection<CmsOrganizationalUnit> ous) {
386
387            m_ousById.clear();
388            m_ousByAssignedResourcePaths.clear();
389            for (CmsOrganizationalUnit ou : ous) {
390                m_ousById.put(ou.getId(), ou);
391            }
392            for (CmsRelation rel : ouRelations) {
393                CmsOrganizationalUnit ou = m_ousById.get(rel.getSourceId());
394                if (ou != null) {
395                    m_ousByAssignedResourcePaths.put(new CmsPath(rel.getTargetPath()), ou);
396                }
397            }
398        }
399    }
400
401    /**
402     * The comparator used for comparing url name mapping entries by date.<p>
403     */
404    class UrlNameMappingComparator implements Comparator<CmsUrlNameMappingEntry> {
405
406        /**
407         * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
408         */
409        public int compare(CmsUrlNameMappingEntry o1, CmsUrlNameMappingEntry o2) {
410
411            long date1 = o1.getDateChanged();
412            long date2 = o2.getDateChanged();
413            if (date1 < date2) {
414                return -1;
415            }
416            if (date1 > date2) {
417                return +1;
418            }
419            return 0;
420        }
421    }
422
423    /**
424     * Enumeration class for the mode parameter in the
425     * {@link CmsDriverManager#readChangedResourcesInsideProject(CmsDbContext, CmsUUID, CmsReadChangedProjectResourceMode)}
426     * method.<p>
427     */
428    private static class CmsReadChangedProjectResourceMode {
429
430        /**
431         * Default constructor.<p>
432         */
433        protected CmsReadChangedProjectResourceMode() {
434
435            // noop
436        }
437    }
438
439    /** Request context attribute used to override the time used for time-based exclusive access checks. */
440    public static final String ATTR_EXCLUSIVE_ACCESS_CLOCK = "ATTR_EXCLUSIVE_ACCESS_CLOCK";
441
442    /** Attribute for signaling to the user driver that a specific OU should be initialized by fillDefaults. */
443    public static final String ATTR_INIT_OU = "INIT_OU";
444
445    /** DB context attribute used to communicate information about resource cacheability between various methods. */
446    public static final String ATTR_PERMISSION_NOCACHE = "ATTR_PERMISSION_NOCACHE";
447
448    /** Attribute login. */
449    public static final String ATTRIBUTE_LOGIN = "A_LOGIN";
450
451    /** Cache key for all properties. */
452    public static final String CACHE_ALL_PROPERTIES = "_CAP_";
453
454    /**
455     * Values indicating changes of a resource,
456     * ordered according to the scope of the change.
457     */
458    /** Value to indicate a change in access control entries of a resource. */
459    public static final int CHANGED_ACCESSCONTROL = 1;
460
461    /** Value to indicate a content change. */
462    public static final int CHANGED_CONTENT = 16;
463
464    /** Value to indicate a change in the lastmodified settings of a resource. */
465    public static final int CHANGED_LASTMODIFIED = 4;
466
467    /** Value to indicate a project change. */
468    public static final int CHANGED_PROJECT = 32;
469
470    /** Value to indicate a change in the resource data. */
471    public static final int CHANGED_RESOURCE = 8;
472
473    /** Value to indicate a change in the availability timeframe. */
474    public static final int CHANGED_TIMEFRAME = 2;
475
476    /** "cache" string in the configuration-file. */
477    public static final String CONFIGURATION_CACHE = "cache";
478
479    /** "db" string in the configuration-file. */
480    public static final String CONFIGURATION_DB = "db";
481
482    /** "driver.history" string in the configuration-file. */
483    public static final String CONFIGURATION_HISTORY = "driver.history";
484
485    /** "driver.project" string in the configuration-file. */
486    public static final String CONFIGURATION_PROJECT = "driver.project";
487
488    /** "subscription.vfs" string in the configuration file. */
489    public static final String CONFIGURATION_SUBSCRIPTION = "driver.subscription";
490
491    /** "driver.user" string in the configuration-file. */
492    public static final String CONFIGURATION_USER = "driver.user";
493
494    /** "driver.vfs" string in the configuration-file. */
495    public static final String CONFIGURATION_VFS = "driver.vfs";
496
497    /** DBC attribute key needed to fix publishing behavior involving siblings. */
498    public static final String KEY_CHANGED_AND_DELETED = "changedAndDeleted";
499
500    /** The vfs path of the loast and found folder. */
501    public static final String LOST_AND_FOUND_FOLDER = "/system/lost-found";
502
503    /** The maximum length of a VFS resource path. */
504    public static final int MAX_VFS_RESOURCE_PATH_LENGTH = 512;
505
506    /** Key for indicating no changes. */
507    public static final int NOTHING_CHANGED = 0;
508
509    /** Name of the configuration parameter to enable/disable logging to the CMS_LOG table. */
510    public static final String PARAM_LOG_TABLE_ENABLED = "log.table.enabled";
511
512    /** Indicates to ignore the resource path when matching resources. */
513    public static final String READ_IGNORE_PARENT = null;
514
515    /** Indicates to ignore the time value. */
516    public static final long READ_IGNORE_TIME = 0L;
517
518    /** Indicates to ignore the resource type when matching resources. */
519    public static final int READ_IGNORE_TYPE = -1;
520
521    /** Indicates to match resources NOT having the given state. */
522    public static final int READMODE_EXCLUDE_STATE = 8;
523
524    /** Indicates to match immediate children only. */
525    public static final int READMODE_EXCLUDE_TREE = 1;
526
527    /** Indicates to match resources NOT having the given type. */
528    public static final int READMODE_EXCLUDE_TYPE = 4;
529
530    /** Mode for reading project resources from the db. */
531    public static final int READMODE_IGNORESTATE = 0;
532
533    /** Indicates to match resources in given project only. */
534    public static final int READMODE_INCLUDE_PROJECT = 2;
535
536    /** Indicates to match all successors. */
537    public static final int READMODE_INCLUDE_TREE = 0;
538
539    /** Mode for reading project resources from the db. */
540    public static final int READMODE_MATCHSTATE = 1;
541
542    /** Indicates if only file resources should be read. */
543    public static final int READMODE_ONLY_FILES = 128;
544
545    /** Indicates if only folder resources should be read. */
546    public static final int READMODE_ONLY_FOLDERS = 64;
547
548    /** Mode for reading project resources from the db. */
549    public static final int READMODE_UNMATCHSTATE = 2;
550
551    /** Flag that can be used to disable the resource OU caching if necessary. */
552    public static boolean resourceOrgUnitCachingEnabled = true;
553
554    /** Prefix char for temporary files in the VFS. */
555    public static final String TEMP_FILE_PREFIX = "~";
556
557    /** Key to indicate complete update. */
558    public static final int UPDATE_ALL = 3;
559
560    /** Key to indicate update of resource record. */
561    public static final int UPDATE_RESOURCE = 4;
562
563    /** Key to indicate update of last modified project reference. */
564    public static final int UPDATE_RESOURCE_PROJECT = 6;
565
566    /** Key to indicate update of resource state. */
567    public static final int UPDATE_RESOURCE_STATE = 1;
568
569    /** Key to indicate update of resource state including the content date. */
570    public static final int UPDATE_RESOURCE_STATE_CONTENT = 7;
571
572    /** Key to indicate update of structure record. */
573    public static final int UPDATE_STRUCTURE = 5;
574
575    /** Key to indicate update of structure state. */
576    public static final int UPDATE_STRUCTURE_STATE = 2;
577
578    /** Map of pools defined in opencms.properties. */
579    protected static ConcurrentMap<String, CmsDbPoolV11> m_pools = Maps.newConcurrentMap();
580
581    /** The log object for this class. */
582    private static final Log LOG = CmsLog.getLog(CmsDriverManager.class);
583
584    /** Constant mode parameter to read all files and folders in the {@link #readChangedResourcesInsideProject(CmsDbContext, CmsUUID, CmsReadChangedProjectResourceMode)}} method. */
585    private static final CmsReadChangedProjectResourceMode RCPRM_FILES_AND_FOLDERS_MODE = new CmsReadChangedProjectResourceMode();
586
587    /** Constant mode parameter to read all files and folders in the {@link #readChangedResourcesInsideProject(CmsDbContext, CmsUUID, CmsReadChangedProjectResourceMode)}} method. */
588    private static final CmsReadChangedProjectResourceMode RCPRM_FILES_ONLY_MODE = new CmsReadChangedProjectResourceMode();
589
590    /** Constant mode parameter to read all files and folders in the {@link #readChangedResourcesInsideProject(CmsDbContext, CmsUUID, CmsReadChangedProjectResourceMode)}} method. */
591    private static final CmsReadChangedProjectResourceMode RCPRM_FOLDERS_ONLY_MODE = new CmsReadChangedProjectResourceMode();
592
593    private final Object m_publishTagLock = new Object();
594
595    /** The history driver. */
596    private I_CmsHistoryDriver m_historyDriver;
597
598    /** The HTML link validator. */
599    private CmsRelationSystemValidator m_htmlLinkValidator;
600
601    /** The class used for cache key generation. */
602    private I_CmsCacheKey m_keyGenerator;
603
604    /** The lock manager. */
605    private CmsLockManager m_lockManager;
606
607    /** The log entry cache. */
608    private List<CmsLogEntry> m_log = new ArrayList<CmsLogEntry>();
609
610    /** Local reference to the memory monitor to avoid multiple lookups through the OpenCms singleton. */
611    private CmsMemoryMonitor m_monitor;
612
613    /** The project driver. */
614    private I_CmsProjectDriver m_projectDriver;
615
616    /** The the configuration read from the <code>opencms.properties</code> file. */
617    private CmsParameterConfiguration m_propertyConfiguration;
618
619    /** the publish engine. */
620    private CmsPublishEngine m_publishEngine;
621
622    /** Object used for synchronizing updates to the user publish list. */
623    private Object m_publishListUpdateLock = new Object();
624
625    /** The security manager (for access checks). */
626    private CmsSecurityManager m_securityManager;
627
628    /** The sql manager. */
629    private CmsSqlManager m_sqlManager;
630
631    /** The subscription driver. */
632    private I_CmsSubscriptionDriver m_subscriptionDriver;
633
634    /** The user driver. */
635    private I_CmsUserDriver m_userDriver;
636
637    /** The VFS driver. */
638    private I_CmsVfsDriver m_vfsDriver;
639
640    private volatile Integer m_lastPublishTag = null;
641
642    /** Cache for which OUs can be skipped for principal transfer when deleting a user. */
643    private Cache<String, Boolean> m_skipTransferPrincipalResourceCache;
644
645    /**
646     * Private constructor, initializes some required member variables.<p>
647     */
648    private CmsDriverManager() {
649
650        // intentionally left blank
651    }
652
653    /**
654     * Reads the required configurations from the opencms.properties file and creates
655     * the various drivers to access the cms resources.<p>
656     *
657     * The initialization process of the driver manager and its drivers is split into
658     * the following phases:
659     * <ul>
660     * <li>the database pool configuration is read</li>
661     * <li>a plain and empty driver manager instance is created</li>
662     * <li>an instance of each driver is created</li>
663     * <li>the driver manager is passed to each driver during initialization</li>
664     * <li>finally, the driver instances are passed to the driver manager during initialization</li>
665     * </ul>
666     *
667     * @param configurationManager the configuration manager
668     * @param securityManager the security manager
669     * @param runtimeInfoFactory the initialized OpenCms runtime info factory
670     * @param publishEngine the publish engine
671     *
672     * @return CmsDriverManager the instantiated driver manager
673     * @throws CmsInitException if the driver manager couldn't be instantiated
674     */
675    public static CmsDriverManager newInstance(
676        CmsConfigurationManager configurationManager,
677        CmsSecurityManager securityManager,
678        I_CmsDbContextFactory runtimeInfoFactory,
679        CmsPublishEngine publishEngine)
680    throws CmsInitException {
681
682        // read the opencms.properties from the configuration
683        CmsParameterConfiguration config = configurationManager.getConfiguration();
684
685        CmsDriverManager driverManager = null;
686        try {
687            // create a driver manager instance
688            driverManager = new CmsDriverManager();
689            if (CmsLog.INIT.isInfoEnabled()) {
690                CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_DRIVER_MANAGER_START_PHASE1_0));
691            }
692            if (runtimeInfoFactory == null) {
693                throw new CmsInitException(
694                    org.opencms.main.Messages.get().container(org.opencms.main.Messages.ERR_CRITICAL_NO_DB_CONTEXT_0));
695            }
696        } catch (Exception exc) {
697            CmsMessageContainer message = Messages.get().container(Messages.LOG_ERR_DRIVER_MANAGER_START_0);
698            if (LOG.isFatalEnabled()) {
699                LOG.fatal(message.key(), exc);
700            }
701            throw new CmsInitException(message, exc);
702        }
703
704        // store the configuration
705        driverManager.m_propertyConfiguration = config;
706
707        // set the security manager
708        driverManager.m_securityManager = securityManager;
709
710        // set the lock manager
711        driverManager.m_lockManager = new CmsLockManager(driverManager);
712
713        // create and set the sql manager
714        driverManager.m_sqlManager = new CmsSqlManager(driverManager);
715
716        // set the publish engine
717        driverManager.m_publishEngine = publishEngine;
718
719        if (CmsLog.INIT.isInfoEnabled()) {
720            CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_DRIVER_MANAGER_START_PHASE2_0));
721        }
722
723        // read the pool names to initialize
724        List<String> driverPoolNames = config.getList(CmsDriverManager.CONFIGURATION_DB + ".pools");
725        if (CmsLog.INIT.isInfoEnabled()) {
726            String names = "";
727            for (String name : driverPoolNames) {
728                names += name + " ";
729            }
730            CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_DRIVER_MANAGER_START_POOLS_1, names));
731        }
732
733        // initialize each pool
734        for (String name : driverPoolNames) {
735            driverManager.newPoolInstance(config, name);
736        }
737
738        // initialize the runtime info factory with the generated driver manager
739        runtimeInfoFactory.initialize(driverManager);
740
741        if (CmsLog.INIT.isInfoEnabled()) {
742            CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_DRIVER_MANAGER_START_PHASE3_0));
743        }
744
745        // store the access objects
746        CmsDbContext dbc = runtimeInfoFactory.getDbContext();
747        driverManager.m_vfsDriver = (I_CmsVfsDriver)driverManager.createDriver(
748            dbc,
749            configurationManager,
750            config,
751            CONFIGURATION_VFS,
752            ".vfs.driver");
753        dbc.clear();
754
755        dbc = runtimeInfoFactory.getDbContext();
756        driverManager.m_userDriver = (I_CmsUserDriver)driverManager.createDriver(
757            dbc,
758            configurationManager,
759            config,
760            CONFIGURATION_USER,
761            ".user.driver");
762        dbc.clear();
763
764        dbc = runtimeInfoFactory.getDbContext();
765        driverManager.m_projectDriver = (I_CmsProjectDriver)driverManager.createDriver(
766            dbc,
767            configurationManager,
768            config,
769            CONFIGURATION_PROJECT,
770            ".project.driver");
771        dbc.clear();
772
773        dbc = runtimeInfoFactory.getDbContext();
774        driverManager.m_historyDriver = (I_CmsHistoryDriver)driverManager.createDriver(
775            dbc,
776            configurationManager,
777            config,
778            CONFIGURATION_HISTORY,
779            ".history.driver");
780        dbc.clear();
781
782        dbc = runtimeInfoFactory.getDbContext();
783        try {
784            // we wrap this in a try-catch because otherwise it would fail during the update
785            // process, since the subscription driver configuration does not exist at that point.
786            driverManager.m_subscriptionDriver = (I_CmsSubscriptionDriver)driverManager.createDriver(
787                dbc,
788                configurationManager,
789                config,
790                CONFIGURATION_SUBSCRIPTION,
791                ".subscription.driver");
792        } catch (IndexOutOfBoundsException npe) {
793            LOG.warn("Could not instantiate subscription driver!");
794            LOG.warn(npe.getLocalizedMessage(), npe);
795        }
796        dbc.clear();
797
798        // register the driver manager for required events
799        org.opencms.main.OpenCms.addCmsEventListener(
800            driverManager,
801            new int[] {
802                I_CmsEventListener.EVENT_UPDATE_EXPORTS,
803                I_CmsEventListener.EVENT_CLEAR_CACHES,
804                I_CmsEventListener.EVENT_CLEAR_PRINCIPAL_CACHES,
805                I_CmsEventListener.EVENT_USER_MODIFIED,
806                I_CmsEventListener.EVENT_RESOURCE_MODIFIED,
807                I_CmsEventListener.EVENT_OU_MODIFIED,
808                I_CmsEventListener.EVENT_PUBLISH_PROJECT});
809
810        // not sure the manual cache flushing based on events is sufficient in all cases for the 'is principal transfer skippable?' cache,
811        // so we limit it to a few minutes
812        Cache<String, Boolean> skipTransferPrincipalResourceCache = CacheBuilder.newBuilder().concurrencyLevel(
813            4).expireAfterWrite(5, TimeUnit.MINUTES).build();
814        driverManager.m_skipTransferPrincipalResourceCache = skipTransferPrincipalResourceCache;
815
816        // return the configured driver manager
817        return driverManager;
818    }
819
820    /**
821     * Adds an alias entry.<p>
822     *
823     * @param dbc the database context
824     * @param project the current project
825     * @param alias the alias to add
826     *
827     * @throws CmsException if something goes wrong
828     */
829    public void addAlias(CmsDbContext dbc, CmsProject project, CmsAlias alias) throws CmsException {
830
831        I_CmsVfsDriver vfsDriver = getVfsDriver(dbc);
832        vfsDriver.insertAlias(dbc, project, alias);
833    }
834
835    /**
836     * Adds a new relation to the given resource.<p>
837     *
838     * @param dbc the database context
839     * @param resource the resource to add the relation to
840     * @param target the target of the relation
841     * @param type the type of the relation
842     * @param importCase if importing relations
843     *
844     * @throws CmsException if something goes wrong
845     */
846    public void addRelationToResource(
847        CmsDbContext dbc,
848        CmsResource resource,
849        CmsResource target,
850        CmsRelationType type,
851        boolean importCase)
852    throws CmsException {
853
854        if (type.isDefinedInContent()) {
855            throw new CmsIllegalArgumentException(
856                Messages.get().container(
857                    Messages.ERR_ADD_RELATION_IN_CONTENT_3,
858                    dbc.removeSiteRoot(resource.getRootPath()),
859                    dbc.removeSiteRoot(target.getRootPath()),
860                    type.getLocalizedName(dbc.getRequestContext().getLocale())));
861        }
862        CmsRelation relation = new CmsRelation(resource, target, type);
863        getVfsDriver(dbc).createRelation(dbc, dbc.currentProject().getUuid(), relation);
864        // IMPORTANT: In the import case, the importer has to ensure that the
865        // resource is reindexed offline after the relation has been added.
866        // We do not trigger reindexing here for each added relation due to performance issues.
867        // in the the non-import case reindexing is triggered by the update of the last modification date.
868        if (!importCase) {
869            // log it
870            log(
871                dbc,
872                new CmsLogEntry(
873                    dbc,
874                    resource.getStructureId(),
875                    CmsLogEntryType.RESOURCE_ADD_RELATION,
876                    new String[] {relation.getSourcePath(), relation.getTargetPath()}),
877                false);
878            // touch the resource
879            setDateLastModified(dbc, resource, System.currentTimeMillis());
880        }
881    }
882
883    /**
884     * Adds a resource to the given organizational unit.<p>
885     *
886     * @param dbc the current db context
887     * @param orgUnit the organizational unit to add the resource to
888     * @param resource the resource that is to be added to the organizational unit
889     *
890     * @throws CmsException if something goes wrong
891     *
892     * @see org.opencms.security.CmsOrgUnitManager#addResourceToOrgUnit(CmsObject, String, String)
893     * @see org.opencms.security.CmsOrgUnitManager#addResourceToOrgUnit(CmsObject, String, String)
894     */
895    public void addResourceToOrgUnit(CmsDbContext dbc, CmsOrganizationalUnit orgUnit, CmsResource resource)
896    throws CmsException {
897
898        m_monitor.flushCache(CmsMemoryMonitor.CacheType.HAS_ROLE, CmsMemoryMonitor.CacheType.ROLE_LIST);
899        getUserDriver(dbc).addResourceToOrganizationalUnit(dbc, orgUnit, resource);
900    }
901
902    /**
903     * Adds a user to a group.<p>
904     *
905     * @param dbc the current database context
906     * @param username the name of the user that is to be added to the group
907     * @param groupname the name of the group
908     * @param readRoles if reading roles or groups
909     *
910     * @throws CmsException if operation was not successful
911     * @throws CmsDbEntryNotFoundException if the given user or the given group was not found
912     *
913     * @see #removeUserFromGroup(CmsDbContext, String, String, boolean)
914     */
915    public void addUserToGroup(CmsDbContext dbc, String username, String groupname, boolean readRoles)
916    throws CmsException, CmsDbEntryNotFoundException {
917
918        //check if group exists
919        CmsGroup group = readGroup(dbc, groupname);
920        if (group == null) {
921            // the group does not exists
922            throw new CmsDbEntryNotFoundException(Messages.get().container(Messages.ERR_UNKNOWN_GROUP_1, groupname));
923        }
924        if (group.isVirtual() && !readRoles) {
925            String roleName = CmsRole.valueOf(group).getGroupName();
926            if (!userInGroup(dbc, username, roleName, true)) {
927                addUserToGroup(dbc, username, roleName, true);
928                return;
929            }
930        }
931        if (group.isVirtual()) {
932            // this is an hack to prevent unlimited recursive calls
933            readRoles = false;
934        }
935        if ((readRoles && !group.isRole()) || (!readRoles && group.isRole())) {
936            // we want a role but we got a group, or the other way
937            throw new CmsDbEntryNotFoundException(Messages.get().container(Messages.ERR_UNKNOWN_GROUP_1, groupname));
938        }
939        if (userInGroup(dbc, username, groupname, readRoles)) {
940            // the user is already member of the group
941            return;
942        }
943        //check if the user exists
944        CmsUser user = readUser(dbc, username);
945        if (user == null) {
946            // the user does not exists
947            throw new CmsDbEntryNotFoundException(Messages.get().container(Messages.ERR_UNKNOWN_USER_1, username));
948        }
949
950        // if adding an user to a role
951        if (readRoles) {
952            CmsRole role = CmsRole.valueOf(group);
953            // a role can only be set if the user has the given role
954            m_securityManager.checkRole(dbc, role);
955            // now we check if we already have the role
956            if (m_securityManager.hasRole(dbc, user, role)) {
957                // do nothing
958                return;
959            }
960            // and now we need to remove all possible child-roles
961            List<CmsRole> children = role.getChildren(true);
962            Iterator<CmsGroup> itUserGroups = getGroupsOfUser(
963                dbc,
964                username,
965                group.getOuFqn(),
966                true,
967                true,
968                true,
969                dbc.getRequestContext().getRemoteAddress()).iterator();
970            while (itUserGroups.hasNext()) {
971                CmsGroup roleGroup = itUserGroups.next();
972                if (children.contains(CmsRole.valueOf(roleGroup))) {
973                    // remove only child roles
974                    removeUserFromGroup(dbc, username, roleGroup.getName(), true);
975                }
976            }
977            // update virtual groups
978            Iterator<CmsGroup> it = getVirtualGroupsForRole(dbc, role).iterator();
979            while (it.hasNext()) {
980                CmsGroup virtualGroup = it.next();
981                // here we say readroles = true, to prevent an unlimited recursive calls
982                addUserToGroup(dbc, username, virtualGroup.getName(), true);
983            }
984        }
985
986        //add this user to the group
987        getUserDriver(dbc).createUserInGroup(dbc, user.getId(), group.getId());
988
989        // flush the cache
990        if (readRoles) {
991            m_monitor.flushCache(CmsMemoryMonitor.CacheType.HAS_ROLE, CmsMemoryMonitor.CacheType.ROLE_LIST);
992        }
993        m_monitor.flushUserGroups(user.getId());
994        m_monitor.flushCache(CmsMemoryMonitor.CacheType.USER_LIST);
995
996        if (!dbc.getProjectId().isNullUUID() && !CmsProject.ONLINE_PROJECT_ID.equals(dbc.getProjectId())) {
997            // user modified event is not needed
998            return;
999        }
1000        // fire user modified event
1001        Map<String, Object> eventData = new HashMap<String, Object>();
1002        eventData.put(I_CmsEventListener.KEY_USER_ID, user.getId().toString());
1003        eventData.put(I_CmsEventListener.KEY_USER_NAME, user.getName());
1004        eventData.put(I_CmsEventListener.KEY_USER_ID, user.getId().toString());
1005        eventData.put(I_CmsEventListener.KEY_GROUP_NAME, group.getName());
1006        eventData.put(
1007            I_CmsEventListener.KEY_USER_ACTION,
1008            I_CmsEventListener.VALUE_USER_MODIFIED_ACTION_ADD_USER_TO_GROUP);
1009        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_USER_MODIFIED, eventData));
1010    }
1011
1012    /**
1013     * Changes the lock of a resource to the current user,
1014     * that is "steals" the lock from another user.<p>
1015     *
1016     * @param dbc the current database context
1017     * @param resource the resource to change the lock for
1018     * @param lockType the new lock type to set
1019     *
1020     * @throws CmsException if something goes wrong
1021     * @throws CmsSecurityException if something goes wrong
1022     *
1023     *
1024     * @see CmsObject#changeLock(String)
1025     * @see I_CmsResourceType#changeLock(CmsObject, CmsSecurityManager, CmsResource)
1026     *
1027     * @see CmsSecurityManager#hasPermissions(CmsRequestContext, CmsResource, CmsPermissionSet, boolean, CmsResourceFilter)
1028     */
1029    public void changeLock(CmsDbContext dbc, CmsResource resource, CmsLockType lockType)
1030    throws CmsException, CmsSecurityException {
1031
1032        // get the current lock
1033        CmsLock currentLock = getLock(dbc, resource);
1034        // check if the resource is locked at all
1035        if (currentLock.getEditionLock().isUnlocked() && currentLock.getSystemLock().isUnlocked()) {
1036            throw new CmsLockException(
1037                Messages.get().container(
1038                    Messages.ERR_CHANGE_LOCK_UNLOCKED_RESOURCE_1,
1039                    dbc.getRequestContext().getSitePath(resource)));
1040        } else if ((lockType == CmsLockType.EXCLUSIVE)
1041            && currentLock.isExclusiveOwnedInProjectBy(dbc.currentUser(), dbc.currentProject())) {
1042            // the current lock requires no change
1043            return;
1044        }
1045
1046        // duplicate logic from CmsSecurityManager#hasPermissions() because lock state can't be ignored
1047        // if another user has locked the file, the current user can never get WRITE permissions with the default check
1048        int denied = 0;
1049
1050        // check if the current user is vfs manager
1051        boolean canIgnorePermissions = m_securityManager.hasRoleForResource(
1052            dbc,
1053            dbc.currentUser(),
1054            CmsRole.VFS_MANAGER,
1055            resource);
1056        // if the resource type is jsp
1057        // write is only allowed for developers
1058        if (!canIgnorePermissions && (CmsResourceTypeJsp.isJsp(resource))) {
1059            if (!m_securityManager.hasRoleForResource(dbc, dbc.currentUser(), CmsRole.VFS_MANAGER, resource)) {
1060                denied |= CmsPermissionSet.PERMISSION_WRITE;
1061            }
1062        }
1063        CmsPermissionSetCustom permissions;
1064        if (canIgnorePermissions) {
1065            // if the current user is administrator, anything is allowed
1066            permissions = new CmsPermissionSetCustom(~0);
1067        } else {
1068            // otherwise, get the permissions from the access control list
1069            permissions = getPermissions(dbc, resource, dbc.currentUser());
1070        }
1071        // revoke the denied permissions
1072        permissions.denyPermissions(denied);
1073        // now check if write permission is granted
1074        if ((CmsPermissionSet.ACCESS_WRITE.getPermissions()
1075            & permissions.getPermissions()) != CmsPermissionSet.ACCESS_WRITE.getPermissions()) {
1076            // check failed, throw exception
1077            m_securityManager.checkPermissions(
1078                dbc.getRequestContext(),
1079                resource,
1080                CmsPermissionSet.ACCESS_WRITE,
1081                I_CmsPermissionHandler.PERM_DENIED);
1082        }
1083        // if we got here write permission is granted on the target
1084
1085        // remove the old lock
1086        m_lockManager.removeResource(dbc, resource, true, lockType.isSystem());
1087        // apply the new lock
1088        lockResource(dbc, resource, lockType);
1089    }
1090
1091    /**
1092     * Returns a list with all sub resources of a given folder that have set the given property,
1093     * matching the current property's value with the given old value and replacing it by a given new value.<p>
1094     *
1095     * @param dbc the current database context
1096     * @param resource the resource on which property definition values are changed
1097     * @param propertyDefinition the name of the propertydefinition to change the value
1098     * @param oldValue the old value of the propertydefinition
1099     * @param newValue the new value of the propertydefinition
1100     * @param recursive if true, change the property value on the resource and recursively all property values on
1101     *                     sub-resources (only for folders)
1102     * @return a list with the <code>{@link CmsResource}</code>'s where the property value has been changed
1103     *
1104     * @throws CmsVfsException for now only when the search for the oldvalue failed.
1105     * @throws CmsException if operation was not successful
1106     */
1107    public List<CmsResource> changeResourcesInFolderWithProperty(
1108        CmsDbContext dbc,
1109        CmsResource resource,
1110        String propertyDefinition,
1111        String oldValue,
1112        String newValue,
1113        boolean recursive)
1114    throws CmsVfsException, CmsException {
1115
1116        CmsResourceFilter filter = CmsResourceFilter.IGNORE_EXPIRATION;
1117        // collect the resources to look up
1118        List<CmsResource> resources = new ArrayList<CmsResource>();
1119        if (recursive) {
1120            // read the files in the folder
1121            resources = readResourcesWithProperty(dbc, resource, propertyDefinition, null, filter);
1122            // add the folder itself
1123            resources.add(resource);
1124        } else {
1125            resources.add(resource);
1126        }
1127
1128        Pattern oldPattern;
1129        try {
1130            // remove the place holder if available
1131            String tmpOldValue = oldValue;
1132            if (tmpOldValue.contains(CmsStringUtil.PLACEHOLDER_START)
1133                && tmpOldValue.contains(CmsStringUtil.PLACEHOLDER_END)) {
1134                tmpOldValue = tmpOldValue.replace(CmsStringUtil.PLACEHOLDER_START, "");
1135                tmpOldValue = tmpOldValue.replace(CmsStringUtil.PLACEHOLDER_END, "");
1136            }
1137            // compile regular expression pattern
1138            oldPattern = Pattern.compile(tmpOldValue);
1139        } catch (PatternSyntaxException e) {
1140            throw new CmsVfsException(
1141                Messages.get().container(
1142                    Messages.ERR_CHANGE_RESOURCES_IN_FOLDER_WITH_PROP_4,
1143                    new Object[] {propertyDefinition, oldValue, newValue, resource.getRootPath()}),
1144                e);
1145        }
1146
1147        List<CmsResource> changedResources = new ArrayList<CmsResource>(resources.size());
1148        // create permission set and filter to check each resource
1149        CmsPermissionSet perm = CmsPermissionSet.ACCESS_WRITE;
1150        for (int i = 0; i < resources.size(); i++) {
1151            // loop through found resources and check property values
1152            CmsResource res = resources.get(i);
1153            // check resource state and permissions
1154            try {
1155                m_securityManager.checkPermissions(dbc, res, perm, true, filter);
1156            } catch (Exception e) {
1157                // resource is deleted or not writable for current user
1158                continue;
1159            }
1160            CmsProperty property = readPropertyObject(dbc, res, propertyDefinition, false);
1161            String propertyValue = property.getValue();
1162            boolean changed = false;
1163            if ((propertyValue != null) && oldPattern.matcher(propertyValue).matches()) {
1164                // apply the place holder content
1165                String tmpNewValue = CmsStringUtil.transformValues(oldValue, newValue, propertyValue);
1166                // change structure value
1167                property.setStructureValue(tmpNewValue);
1168                changed = true;
1169            }
1170            if (changed) {
1171                // write property object if something has changed
1172                writePropertyObject(dbc, res, property);
1173                changedResources.add(res);
1174            }
1175        }
1176        return changedResources;
1177    }
1178
1179    /**
1180     * Changes the resource flags of a resource.<p>
1181     *
1182     * The resource flags are used to indicate various "special" conditions
1183     * for a resource. Most notably, the "internal only" setting which signals
1184     * that a resource can not be directly requested with it's URL.<p>
1185     *
1186     * @param dbc the current database context
1187     * @param resource the resource to change the flags for
1188     * @param flags the new resource flags for this resource
1189     *
1190     * @throws CmsException if something goes wrong
1191     *
1192     * @see CmsObject#chflags(String, int)
1193     * @see I_CmsResourceType#chflags(CmsObject, CmsSecurityManager, CmsResource, int)
1194     */
1195    public void chflags(CmsDbContext dbc, CmsResource resource, int flags) throws CmsException {
1196
1197        // must operate on a clone to ensure resource is not modified in case permissions are not granted
1198        CmsResource clone = (CmsResource)resource.clone();
1199        clone.setFlags(flags);
1200        // log it
1201        log(
1202            dbc,
1203            new CmsLogEntry(
1204                dbc,
1205                resource.getStructureId(),
1206                CmsLogEntryType.RESOURCE_FLAGS,
1207                new String[] {resource.getRootPath()}),
1208            false);
1209        // write it
1210        writeResource(dbc, clone);
1211    }
1212
1213    /**
1214     * Changes the resource type of a resource.<p>
1215     *
1216     * OpenCms handles resources according to the resource type,
1217     * not the file suffix. This is e.g. why a JSP in OpenCms can have the
1218     * suffix ".html" instead of ".jsp" only. Changing the resource type
1219     * makes sense e.g. if you want to make a plain text file a JSP resource,
1220     * or a binary file an image, etc.<p>
1221     *
1222     * @param dbc the current database context
1223     * @param resource the resource to change the type for
1224     * @param type the new resource type for this resource
1225     *
1226     * @throws CmsException if something goes wrong
1227     *
1228     * @see CmsObject#chtype(String, int)
1229     * @see I_CmsResourceType#chtype(CmsObject, CmsSecurityManager, CmsResource, int)
1230     */
1231    @SuppressWarnings({"javadoc", "deprecation"})
1232    public void chtype(CmsDbContext dbc, CmsResource resource, int type) throws CmsException {
1233
1234        // must operate on a clone to ensure resource is not modified in case permissions are not granted
1235        CmsResource clone = (CmsResource)resource.clone();
1236        I_CmsResourceType newType = OpenCms.getResourceManager().getResourceType(type);
1237        clone.setType(newType.getTypeId());
1238        // log it
1239        log(
1240            dbc,
1241            new CmsLogEntry(
1242                dbc,
1243                resource.getStructureId(),
1244                CmsLogEntryType.RESOURCE_TYPE,
1245                new String[] {resource.getRootPath()}),
1246            false);
1247        // write it
1248        writeResource(dbc, clone);
1249    }
1250
1251    /**
1252     * Cleans up the publish history entries according to the given filter.
1253     *
1254     * @param dbc the database context
1255     * @param filter the filter
1256     * @return the number of cleaned up rows
1257     * @throws CmsDataAccessException if something goes wrong
1258     */
1259    public int cleanupPublishHistory(CmsDbContext dbc, CmsPublishHistoryCleanupFilter filter)
1260    throws CmsDataAccessException {
1261
1262        int result = m_projectDriver.cleanupPublishHistory(dbc, filter);
1263        if (filter.getMode() == CmsPublishHistoryCleanupFilter.Mode.single) {
1264            OpenCms.getMemoryMonitor().cachePublishedResources(filter.getHistoryId().toString(), null);
1265        } else {
1266            OpenCms.getMemoryMonitor().flushCache(CmsMemoryMonitor.CacheType.PUBLISHED_RESOURCES);
1267        }
1268        return result;
1269    }
1270
1271    /**
1272     * @see org.opencms.main.I_CmsEventListener#cmsEvent(org.opencms.main.CmsEvent)
1273     */
1274    public void cmsEvent(CmsEvent event) {
1275
1276        if (LOG.isDebugEnabled()) {
1277            LOG.debug(Messages.get().getBundle().key(Messages.LOG_CMS_EVENT_1, Integer.valueOf(event.getType())));
1278        }
1279
1280        I_CmsReport report;
1281        CmsDbContext dbc;
1282
1283        switch (event.getType()) {
1284
1285            case I_CmsEventListener.EVENT_UPDATE_EXPORTS:
1286                dbc = (CmsDbContext)event.getData().get(I_CmsEventListener.KEY_DBCONTEXT);
1287                updateExportPoints(dbc);
1288                break;
1289
1290            case I_CmsEventListener.EVENT_PUBLISH_PROJECT:
1291                CmsUUID publishHistoryId = new CmsUUID((String)event.getData().get(I_CmsEventListener.KEY_PUBLISHID));
1292                report = (I_CmsReport)event.getData().get(I_CmsEventListener.KEY_REPORT);
1293                dbc = (CmsDbContext)event.getData().get(I_CmsEventListener.KEY_DBCONTEXT);
1294                m_monitor.clearCacheForPublishing();
1295                writeExportPoints(dbc, report, publishHistoryId);
1296                break;
1297
1298            case I_CmsEventListener.EVENT_CLEAR_CACHES:
1299                m_monitor.clearCache();
1300                m_skipTransferPrincipalResourceCache.invalidateAll();
1301                break;
1302            case I_CmsEventListener.EVENT_CLEAR_PRINCIPAL_CACHES:
1303                m_monitor.clearPrincipalsCache();
1304                break;
1305            case I_CmsEventListener.EVENT_USER_MODIFIED:
1306                String action = (String)event.getData().get(I_CmsEventListener.KEY_USER_ACTION);
1307
1308                if (I_CmsEventListener.VALUE_USER_MODIFIED_ACTION_WRITE_USER.equals(action)) {
1309
1310                    // users are written *all the time* during normal editing operations, but we don't usually need to clear
1311                    // caches that don't contain any user objects or depend on the user data itself.
1312                    // so we handle this case separately.
1313
1314                    List<CacheType> toClear = new ArrayList<>();
1315                    toClear.addAll(Arrays.asList(CacheType.USER, CacheType.USER_LIST));
1316                    Object changesObj = event.getData().get(I_CmsEventListener.KEY_USER_CHANGES);
1317                    if (changesObj instanceof Integer) {
1318                        int changes = ((Integer)changesObj).intValue();
1319                        if ((changes & CmsUser.FLAG_CORE_DATA) > 0) {
1320                            // core data changes include flags, which may conceivably be used by permission checks, and happen rarely.
1321                            toClear.add(CacheType.PERMISSION);
1322                        }
1323                    }
1324                    m_monitor.flushCache(toClear.toArray(new CacheType[] {}));
1325                } else {
1326                    m_monitor.flushCache(
1327                        CacheType.USER,
1328                        CacheType.GROUP,
1329                        CacheType.ORG_UNIT,
1330                        CacheType.ACL,
1331                        CacheType.PERMISSION,
1332                        CacheType.USER_LIST);
1333                }
1334
1335                if (I_CmsEventListener.VALUE_USER_MODIFIED_ACTION_ADD_USER_TO_GROUP.equals(action)
1336                    || I_CmsEventListener.VALUE_USER_MODIFIED_ACTION_REMOVE_USER_FROM_GROUP.equals(action)
1337                    || I_CmsEventListener.VALUE_USER_MODIFIED_ACTION_SET_OU.equals(action)) {
1338
1339                    Object userIdObj = event.getData().get(I_CmsEventListener.KEY_USER_ID);
1340                    if (userIdObj != null) {
1341                        CmsUUID userId = null;
1342                        if (userIdObj instanceof CmsUUID) {
1343                            userId = (CmsUUID)userIdObj;
1344                        } else if (userIdObj instanceof String) {
1345                            try {
1346                                userId = new CmsUUID(userIdObj.toString());
1347                            } catch (Exception e) {
1348                                LOG.error(e.getLocalizedMessage(), e);
1349                            }
1350                        }
1351                        if (userId != null) {
1352                            m_monitor.flushUserGroups(userId);
1353                        }
1354                    } else {
1355                        m_monitor.flushCache(CacheType.USERGROUPS);
1356                    }
1357                    m_monitor.flushCache(CacheType.HAS_ROLE, CacheType.ROLE_LIST);
1358                }
1359                break;
1360            case I_CmsEventListener.EVENT_RESOURCE_MODIFIED:
1361
1362                Object resObj = event.getData().get(I_CmsEventListener.KEY_RESOURCE);
1363                if ((resObj != null) && (resObj instanceof CmsResource)) {
1364                    CmsResource resource = (CmsResource)resObj;
1365                    if (resource.getRootPath().startsWith(CmsUserDriver.ORGUNIT_BASE_FOLDER)) {
1366                        m_monitor.flushCache(CacheType.ORG_UNIT_RESOURCES);
1367                        m_skipTransferPrincipalResourceCache.invalidateAll();
1368                    }
1369                }
1370                break;
1371            case I_CmsEventListener.EVENT_OU_MODIFIED:
1372                m_monitor.flushCache(CacheType.ORG_UNIT_RESOURCES);
1373                m_skipTransferPrincipalResourceCache.invalidateAll();
1374                break;
1375            default:
1376                // noop
1377        }
1378    }
1379
1380    /**
1381     * Copies the access control entries of a given resource to a destination resource.<p>
1382     *
1383     * Already existing access control entries of the destination resource are removed.<p>
1384     *
1385     * @param dbc the current database context
1386     * @param source the resource to copy the access control entries from
1387     * @param destination the resource to which the access control entries are copied
1388     * @param updateLastModifiedInfo if true, user and date "last modified" information on the target resource will be updated
1389     *
1390     * @throws CmsException if something goes wrong
1391     */
1392    public void copyAccessControlEntries(
1393        CmsDbContext dbc,
1394        CmsResource source,
1395        CmsResource destination,
1396        boolean updateLastModifiedInfo)
1397    throws CmsException {
1398
1399        // get the entries to copy
1400        ListIterator<CmsAccessControlEntry> aceList = getUserDriver(
1401            dbc).readAccessControlEntries(dbc, dbc.currentProject(), source.getResourceId(), false).listIterator();
1402
1403        // remove the current entries from the destination
1404        getUserDriver(dbc).removeAccessControlEntries(dbc, dbc.currentProject(), destination.getResourceId());
1405
1406        // now write the new entries
1407        while (aceList.hasNext()) {
1408            CmsAccessControlEntry ace = aceList.next();
1409            getUserDriver(dbc).createAccessControlEntry(
1410                dbc,
1411                dbc.currentProject(),
1412                destination.getResourceId(),
1413                ace.getPrincipal(),
1414                ace.getPermissions().getAllowedPermissions(),
1415                ace.getPermissions().getDeniedPermissions(),
1416                ace.getFlags());
1417        }
1418
1419        // log it
1420        log(
1421            dbc,
1422            new CmsLogEntry(
1423                dbc,
1424                destination.getStructureId(),
1425                CmsLogEntryType.RESOURCE_PERMISSIONS,
1426                new String[] {destination.getRootPath()}),
1427            false);
1428
1429        // update the "last modified" information
1430        if (updateLastModifiedInfo) {
1431            setDateLastModified(dbc, destination, destination.getDateLastModified());
1432        }
1433
1434        // clear the cache
1435        m_monitor.clearAccessControlListCache();
1436
1437        // fire a resource modification event
1438        Map<String, Object> data = new HashMap<String, Object>(2);
1439        data.put(I_CmsEventListener.KEY_RESOURCE, destination);
1440        data.put(I_CmsEventListener.KEY_CHANGE, Integer.valueOf(CHANGED_ACCESSCONTROL));
1441        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_MODIFIED, data));
1442    }
1443
1444    /**
1445     * Copies a resource.<p>
1446     *
1447     * You must ensure that the destination path is an absolute, valid and
1448     * existing VFS path. Relative paths from the source are currently not supported.<p>
1449     *
1450     * In case the target resource already exists, it is overwritten with the
1451     * source resource.<p>
1452     *
1453     * The <code>siblingMode</code> parameter controls how to handle siblings
1454     * during the copy operation.
1455     * Possible values for this parameter are:
1456     * <ul>
1457     * <li><code>{@link org.opencms.file.CmsResource#COPY_AS_NEW}</code></li>
1458     * <li><code>{@link org.opencms.file.CmsResource#COPY_AS_SIBLING}</code></li>
1459     * <li><code>{@link org.opencms.file.CmsResource#COPY_PRESERVE_SIBLING}</code></li>
1460     * </ul><p>
1461     *
1462     * @param dbc the current database context
1463     * @param source the resource to copy
1464     * @param destination the name of the copy destination with complete path
1465     * @param siblingMode indicates how to handle siblings during copy
1466     *
1467     * @throws CmsException if something goes wrong
1468     * @throws CmsIllegalArgumentException if the <code>source</code> argument is <code>null</code>
1469     *
1470     * @see CmsObject#copyResource(String, String, CmsResource.CmsResourceCopyMode)
1471     * @see I_CmsResourceType#copyResource(CmsObject, CmsSecurityManager, CmsResource, String, CmsResource.CmsResourceCopyMode)
1472     */
1473    public CmsResource copyResource(
1474        CmsDbContext dbc,
1475        CmsResource source,
1476        String destination,
1477        CmsResource.CmsResourceCopyMode siblingMode)
1478    throws CmsException, CmsIllegalArgumentException {
1479
1480        // check the sibling mode to see if this resource has to be copied as a sibling
1481        boolean copyAsSibling = false;
1482
1483        // siblings of folders are not supported
1484        if (!source.isFolder()) {
1485            // if the "copy as sibling" mode is used, set the flag to true
1486            if (siblingMode == CmsResource.COPY_AS_SIBLING) {
1487                copyAsSibling = true;
1488            }
1489            // if the mode is "preserve siblings", we have to check the sibling counter
1490            if (siblingMode == CmsResource.COPY_PRESERVE_SIBLING) {
1491                if (source.getSiblingCount() > 1) {
1492                    copyAsSibling = true;
1493                }
1494            }
1495        }
1496
1497        // read the source properties
1498        List<CmsProperty> properties = readPropertyObjects(dbc, source, false);
1499
1500        if (copyAsSibling) {
1501            // create a sibling of the source file at the destination
1502            return createSibling(dbc, source, destination, properties);
1503        }
1504
1505        // prepare the content if required
1506        byte[] content = null;
1507        if (source.isFile()) {
1508            if (source instanceof CmsFile) {
1509                // resource already is a file
1510                content = ((CmsFile)source).getContents();
1511            }
1512            if ((content == null) || (content.length < 1)) {
1513                // no known content yet - read from database
1514                content = getVfsDriver(dbc).readContent(dbc, dbc.currentProject().getUuid(), source.getResourceId());
1515            }
1516        }
1517
1518        // determine destination folder
1519        String destinationFoldername = CmsResource.getParentFolder(destination);
1520
1521        // read the destination folder (will also check read permissions)
1522        CmsFolder destinationFolder = m_securityManager.readFolder(
1523            dbc,
1524            destinationFoldername,
1525            CmsResourceFilter.IGNORE_EXPIRATION);
1526
1527        // no further permission check required here, will be done in createResource()
1528
1529        // set user and creation time stamps
1530        long currentTime = System.currentTimeMillis();
1531        long dateLastModified;
1532        CmsUUID userLastModified;
1533        if (source.isFolder()) {
1534            // folders always get a new date and user when they are copied
1535            dateLastModified = currentTime;
1536            userLastModified = dbc.currentUser().getId();
1537        } else {
1538            // files keep the date and user last modified from the source
1539            dateLastModified = source.getDateLastModified();
1540            userLastModified = source.getUserLastModified();
1541        }
1542
1543        // check the resource flags
1544        int flags = source.getFlags();
1545        if (source.isLabeled()) {
1546            // reset "labeled" link flag for new resource
1547            flags &= ~CmsResource.FLAG_LABELED;
1548        }
1549
1550        // create the new resource
1551        CmsResource newResource = new CmsResource(
1552            new CmsUUID(),
1553            new CmsUUID(),
1554            destination,
1555            source.getTypeId(),
1556            source.isFolder(),
1557            flags,
1558            dbc.currentProject().getUuid(),
1559            CmsResource.STATE_NEW,
1560            currentTime,
1561            dbc.currentUser().getId(),
1562            dateLastModified,
1563            userLastModified,
1564            source.getDateReleased(),
1565            source.getDateExpired(),
1566            1,
1567            source.getLength(),
1568            source.getDateContent(),
1569            source.getVersion()); // version number does not matter since it will be computed later
1570
1571        // trigger "is touched" state on resource (will ensure modification date is kept unchanged)
1572        newResource.setDateLastModified(dateLastModified);
1573
1574        // log it
1575        log(
1576            dbc,
1577            new CmsLogEntry(
1578                dbc,
1579                newResource.getStructureId(),
1580                CmsLogEntryType.RESOURCE_COPIED,
1581                new String[] {newResource.getRootPath()}),
1582            false);
1583
1584        // create the resource
1585        newResource = createResource(dbc, destination, newResource, content, properties, false);
1586        // copy relations
1587        copyRelations(dbc, source, newResource);
1588
1589        // copy the access control entries to the created resource
1590        copyAccessControlEntries(dbc, source, newResource, false);
1591
1592        // clear the cache
1593        m_monitor.clearAccessControlListCache();
1594
1595        List<CmsResource> modifiedResources = new ArrayList<CmsResource>();
1596        modifiedResources.add(source);
1597        modifiedResources.add(newResource);
1598        modifiedResources.add(destinationFolder);
1599        OpenCms.fireCmsEvent(
1600            new CmsEvent(
1601                I_CmsEventListener.EVENT_RESOURCE_COPIED,
1602                Collections.<String, Object> singletonMap(I_CmsEventListener.KEY_RESOURCES, modifiedResources)));
1603        return newResource;
1604    }
1605
1606    /**
1607     * Copies a resource to the current project of the user.<p>
1608     *
1609     * @param dbc the current database context
1610     * @param resource the resource to apply this operation to
1611     *
1612     * @throws CmsException if something goes wrong
1613     *
1614     * @see CmsObject#copyResourceToProject(String)
1615     * @see I_CmsResourceType#copyResourceToProject(CmsObject, CmsSecurityManager, CmsResource)
1616     */
1617    public void copyResourceToProject(CmsDbContext dbc, CmsResource resource) throws CmsException {
1618
1619        // copy the resource to the project only if the resource is not already in the project
1620        if (!isInsideCurrentProject(dbc, resource.getRootPath())) {
1621            // check if there are already any subfolders of this resource
1622            I_CmsProjectDriver projectDriver = getProjectDriver(dbc);
1623            if (resource.isFolder()) {
1624                List<String> projectResources = projectDriver.readProjectResources(dbc, dbc.currentProject());
1625                for (int i = 0; i < projectResources.size(); i++) {
1626                    String resname = projectResources.get(i);
1627                    if (resname.startsWith(resource.getRootPath())) {
1628                        // delete the existing project resource first
1629                        projectDriver.deleteProjectResource(dbc, dbc.currentProject().getUuid(), resname);
1630                    }
1631                }
1632            }
1633            try {
1634                projectDriver.createProjectResource(dbc, dbc.currentProject().getUuid(), resource.getRootPath());
1635            } catch (CmsException exc) {
1636                // if the subfolder exists already - all is ok
1637            } finally {
1638                m_monitor.flushCache(CmsMemoryMonitor.CacheType.PROJECT_RESOURCES);
1639
1640                OpenCms.fireCmsEvent(
1641                    new CmsEvent(
1642                        I_CmsEventListener.EVENT_PROJECT_MODIFIED,
1643                        Collections.<String, Object> singletonMap("project", dbc.currentProject())));
1644            }
1645        }
1646    }
1647
1648    /**
1649     * Counts the locked resources in this project.<p>
1650     *
1651     * @param project the project to count the locked resources in
1652     *
1653     * @return the amount of locked resources in this project
1654     */
1655    public int countLockedResources(CmsProject project) {
1656
1657        // count locks
1658        return m_lockManager.countExclusiveLocksInProject(project);
1659    }
1660
1661    /**
1662     * Add a new group to the Cms.<p>
1663     *
1664     * Only the admin can do this.
1665     * Only users, which are in the group "administrators" are granted.<p>
1666     *
1667     * @param dbc the current database context
1668     * @param id the id of the new group
1669     * @param name the name of the new group
1670     * @param description the description for the new group
1671     * @param flags the flags for the new group
1672     * @param parent the name of the parent group (or <code>null</code>)
1673     *
1674     * @return new created group
1675     *
1676     * @throws CmsException if the creation of the group failed
1677     * @throws CmsIllegalArgumentException if the length of the given name was below 1
1678     */
1679    public CmsGroup createGroup(CmsDbContext dbc, CmsUUID id, String name, String description, int flags, String parent)
1680    throws CmsIllegalArgumentException, CmsException {
1681
1682        // check the group name
1683        OpenCms.getValidationHandler().checkGroupName(CmsOrganizationalUnit.getSimpleName(name));
1684        // trim the name
1685        name = name.trim();
1686
1687        // check the OU
1688        readOrganizationalUnit(dbc, CmsOrganizationalUnit.getParentFqn(name));
1689
1690        // get the id of the parent group if necessary
1691        if (CmsStringUtil.isNotEmpty(parent)) {
1692            CmsGroup parentGroup = readGroup(dbc, parent);
1693            if (!parentGroup.isRole()
1694                && !CmsOrganizationalUnit.getParentFqn(parent).equals(CmsOrganizationalUnit.getParentFqn(name))) {
1695                throw new CmsDataAccessException(
1696                    Messages.get().container(
1697                        Messages.ERR_PARENT_GROUP_MUST_BE_IN_SAME_OU_3,
1698                        CmsOrganizationalUnit.getSimpleName(name),
1699                        CmsOrganizationalUnit.getParentFqn(name),
1700                        parent));
1701            }
1702        }
1703
1704        // create the group
1705        CmsGroup group = getUserDriver(dbc).createGroup(dbc, id, name, description, flags, parent);
1706
1707        // if the group is in fact a role, initialize it
1708        if (group.isVirtual()) {
1709            // get all users that have the given role
1710            String groupname = CmsRole.valueOf(group).getGroupName();
1711            Iterator<CmsUser> it = getUsersOfGroup(dbc, groupname, true, false, true).iterator();
1712            while (it.hasNext()) {
1713                CmsUser user = it.next();
1714                // put them in the new group
1715                addUserToGroup(dbc, user.getName(), group.getName(), true);
1716            }
1717        }
1718
1719        // put it into the cache
1720        m_monitor.cacheGroup(group);
1721
1722        if (!dbc.getProjectId().isNullUUID()) {
1723            // group modified event is not needed
1724            return group;
1725        }
1726        // fire group modified event
1727        Map<String, Object> eventData = new HashMap<String, Object>();
1728        eventData.put(I_CmsEventListener.KEY_GROUP_NAME, group.getName());
1729        eventData.put(I_CmsEventListener.KEY_GROUP_ID, group.getId().toString());
1730        eventData.put(I_CmsEventListener.KEY_USER_ACTION, I_CmsEventListener.VALUE_GROUP_MODIFIED_ACTION_CREATE);
1731        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_GROUP_MODIFIED, eventData));
1732
1733        // return it
1734        return group;
1735    }
1736
1737    /**
1738     * Creates a new organizational unit.<p>
1739     *
1740     * @param dbc the current db context
1741     * @param ouFqn the fully qualified name of the new organizational unit
1742     * @param description the description of the new organizational unit
1743     * @param flags the flags for the new organizational unit
1744     * @param resource the first associated resource
1745     *
1746     * @return a <code>{@link CmsOrganizationalUnit}</code> object representing
1747     *          the newly created organizational unit
1748     *
1749     * @throws CmsException if operation was not successful
1750     *
1751     * @see org.opencms.security.CmsOrgUnitManager#createOrganizationalUnit(CmsObject, String, String, int, String)
1752     */
1753    public CmsOrganizationalUnit createOrganizationalUnit(
1754        CmsDbContext dbc,
1755        String ouFqn,
1756        String description,
1757        int flags,
1758        CmsResource resource)
1759    throws CmsException {
1760
1761        // normal case
1762        CmsOrganizationalUnit parent = readOrganizationalUnit(dbc, CmsOrganizationalUnit.getParentFqn(ouFqn));
1763        String name = CmsOrganizationalUnit.getSimpleName(ouFqn);
1764        if (name.endsWith(CmsOrganizationalUnit.SEPARATOR)) {
1765            name = name.substring(0, name.length() - 1);
1766        }
1767
1768        // check the name
1769        CmsResource.checkResourceName(name);
1770
1771        // trim the name
1772        name = name.trim();
1773
1774        // check the description
1775        if (CmsStringUtil.isEmptyOrWhitespaceOnly(description)) {
1776            throw new CmsIllegalArgumentException(Messages.get().container(Messages.ERR_BAD_OU_DESCRIPTION_EMPTY_0));
1777        }
1778
1779        // create the organizational unit
1780        CmsOrganizationalUnit orgUnit = getUserDriver(dbc).createOrganizationalUnit(
1781            dbc,
1782            name,
1783            description,
1784            flags,
1785            parent,
1786            resource != null ? resource.getRootPath() : null);
1787        // put the new created org unit into the cache
1788        m_monitor.cacheOrgUnit(orgUnit);
1789
1790        // flush relevant caches
1791        m_monitor.clearPrincipalsCache();
1792        m_monitor.flushCache(CmsMemoryMonitor.CacheType.PROPERTY, CmsMemoryMonitor.CacheType.PROPERTY_LIST);
1793
1794        // create a publish list for the 'virtual' publish event
1795        CmsResource ouRes = readResource(
1796            dbc,
1797            CmsUserDriver.ORGUNIT_BASE_FOLDER + orgUnit.getName(),
1798            CmsResourceFilter.DEFAULT);
1799        CmsPublishList pl = new CmsPublishList(ouRes, false);
1800        pl.add(ouRes, false);
1801
1802        getProjectDriver(dbc).writePublishHistory(
1803            dbc,
1804            pl.getPublishHistoryId(),
1805            new CmsPublishedResource(ouRes, -1, CmsResourceState.STATE_NEW));
1806
1807        // fire the 'virtual' publish event
1808        Map<String, Object> eventData = new HashMap<String, Object>();
1809        eventData.put(I_CmsEventListener.KEY_PUBLISHID, pl.getPublishHistoryId().toString());
1810        eventData.put(I_CmsEventListener.KEY_PROJECTID, dbc.currentProject().getUuid());
1811        eventData.put(I_CmsEventListener.KEY_DBCONTEXT, dbc);
1812        CmsEvent afterPublishEvent = new CmsEvent(I_CmsEventListener.EVENT_PUBLISH_PROJECT, eventData);
1813        OpenCms.fireCmsEvent(afterPublishEvent);
1814
1815        if (!dbc.getProjectId().isNullUUID()) {
1816            // OU modified event is not needed
1817            return orgUnit;
1818        }
1819
1820        // fire OU modified event
1821        Map<String, Object> event2Data = new HashMap<String, Object>();
1822        event2Data.put(I_CmsEventListener.KEY_OU_NAME, orgUnit.getName());
1823        event2Data.put(I_CmsEventListener.KEY_OU_ID, orgUnit.getId().toString());
1824        event2Data.put(I_CmsEventListener.KEY_USER_ACTION, I_CmsEventListener.VALUE_OU_MODIFIED_ACTION_CREATE);
1825        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_OU_MODIFIED, event2Data));
1826
1827        // return it
1828        return orgUnit;
1829    }
1830
1831    /**
1832     * Creates a project.<p>
1833     *
1834     * @param dbc the current database context
1835     * @param name the name of the project to create
1836     * @param description the description of the project
1837     * @param groupname the project user group to be set
1838     * @param managergroupname the project manager group to be set
1839     * @param projecttype the type of the project
1840     *
1841     * @return the created project
1842     *
1843     * @throws CmsIllegalArgumentException if the chosen <code>name</code> is already used
1844     *         by the online project, or if the name is not valid
1845     * @throws CmsException if something goes wrong
1846     */
1847    public CmsProject createProject(
1848        CmsDbContext dbc,
1849        String name,
1850        String description,
1851        String groupname,
1852        String managergroupname,
1853        CmsProject.CmsProjectType projecttype)
1854    throws CmsIllegalArgumentException, CmsException {
1855
1856        if (CmsProject.ONLINE_PROJECT_NAME.equals(name)) {
1857            throw new CmsIllegalArgumentException(
1858                Messages.get().container(
1859                    Messages.ERR_CREATE_PROJECT_ONLINE_PROJECT_NAME_1,
1860                    CmsProject.ONLINE_PROJECT_NAME));
1861        }
1862        // check the name
1863        CmsProject.checkProjectName(CmsOrganizationalUnit.getSimpleName(name));
1864        // check the ou
1865        readOrganizationalUnit(dbc, CmsOrganizationalUnit.getParentFqn(name));
1866        // read the needed groups from the cms
1867        CmsGroup group = readGroup(dbc, groupname);
1868        CmsGroup managergroup = readGroup(dbc, managergroupname);
1869
1870        return getProjectDriver(dbc).createProject(
1871            dbc,
1872            new CmsUUID(),
1873            dbc.currentUser(),
1874            group,
1875            managergroup,
1876            name,
1877            description,
1878            projecttype.getDefaultFlags(),
1879            projecttype);
1880    }
1881
1882    /**
1883     * Creates a property definition.<p>
1884     *
1885     * Property definitions are valid for all resource types.<p>
1886     *
1887     * @param dbc the current database context
1888     * @param name the name of the property definition to create
1889     *
1890     * @return the created property definition
1891     *
1892     * @throws CmsException if something goes wrong
1893     */
1894    public CmsPropertyDefinition createPropertyDefinition(CmsDbContext dbc, String name) throws CmsException {
1895
1896        CmsPropertyDefinition propertyDefinition = null;
1897
1898        name = name.trim();
1899        // validate the property name
1900        CmsPropertyDefinition.checkPropertyName(name);
1901        // TODO: make the type a parameter
1902        try {
1903            try {
1904                propertyDefinition = getVfsDriver(dbc).readPropertyDefinition(
1905                    dbc,
1906                    name,
1907                    dbc.currentProject().getUuid());
1908            } catch (CmsException e) {
1909                propertyDefinition = getVfsDriver(dbc).createPropertyDefinition(
1910                    dbc,
1911                    dbc.currentProject().getUuid(),
1912                    name,
1913                    CmsPropertyDefinition.TYPE_NORMAL);
1914            }
1915
1916            try {
1917                getVfsDriver(dbc).readPropertyDefinition(dbc, name, CmsProject.ONLINE_PROJECT_ID);
1918            } catch (CmsException e) {
1919                getVfsDriver(dbc).createPropertyDefinition(
1920                    dbc,
1921                    CmsProject.ONLINE_PROJECT_ID,
1922                    name,
1923                    CmsPropertyDefinition.TYPE_NORMAL);
1924            }
1925
1926            try {
1927                getHistoryDriver(dbc).readPropertyDefinition(dbc, name);
1928            } catch (CmsException e) {
1929                getHistoryDriver(dbc).createPropertyDefinition(dbc, name, CmsPropertyDefinition.TYPE_NORMAL);
1930            }
1931        } finally {
1932
1933            // fire an event that a property of a resource has been deleted
1934            OpenCms.fireCmsEvent(
1935                new CmsEvent(
1936                    I_CmsEventListener.EVENT_PROPERTY_DEFINITION_CREATED,
1937                    Collections.<String, Object> singletonMap("propertyDefinition", propertyDefinition)));
1938
1939        }
1940
1941        return propertyDefinition;
1942    }
1943
1944    /**
1945     * Creates a new publish job.<p>
1946     *
1947     * @param dbc the current database context
1948     * @param publishJob the publish job to create
1949     *
1950     * @throws CmsException if something goes wrong
1951     */
1952    public void createPublishJob(CmsDbContext dbc, CmsPublishJobInfoBean publishJob) throws CmsException {
1953
1954        getProjectDriver(dbc).createPublishJob(dbc, publishJob);
1955    }
1956
1957    /**
1958     * Creates a new resource with the provided content and properties.<p>
1959     *
1960     * The <code>content</code> parameter may be <code>null</code> if the resource id
1961     * already exists. If so, the created resource will be a sibling of the existing
1962     * resource, the existing content will remain unchanged.<p>
1963     *
1964     * This is used during file import for import of siblings as the
1965     * <code>manifest.xml</code> only contains one binary copy per file.<p>
1966     *
1967     * If the resource id exists but the <code>content</code> is not <code>null</code>,
1968     * the created resource will be made a sibling of the existing resource,
1969     * and both will share the new content.<p>
1970     *
1971     * @param dbc the current database context
1972     * @param resourcePath the name of the resource to create (full path)
1973     * @param resource the new resource to create
1974     * @param content the content for the new resource
1975     * @param properties the properties for the new resource
1976     * @param importCase if <code>true</code>, signals that this operation is done while
1977     *                      importing resource, causing different lock behavior and
1978     *                      potential "lost and found" usage
1979     *
1980     * @return the created resource
1981     *
1982     * @throws CmsException if something goes wrong
1983     */
1984    public CmsResource createResource(
1985        CmsDbContext dbc,
1986        String resourcePath,
1987        CmsResource resource,
1988        byte[] content,
1989        List<CmsProperty> properties,
1990        boolean importCase)
1991    throws CmsException {
1992
1993        CmsResource newResource = null;
1994        if (resource.isFolder()) {
1995            resourcePath = CmsFileUtil.addTrailingSeparator(resourcePath);
1996        }
1997
1998        try {
1999            synchronized (this) {
2000                // need to provide the parent folder id for resource creation
2001                String parentFolderName = CmsResource.getParentFolder(resourcePath);
2002                CmsResource parentFolder = readFolder(dbc, parentFolderName, CmsResourceFilter.IGNORE_EXPIRATION);
2003
2004                CmsLock parentLock = getLock(dbc, parentFolder);
2005                // it is not allowed to create a resource in a folder locked by other user
2006                if (!parentLock.isUnlocked() && !parentLock.isOwnedBy(dbc.currentUser())) {
2007                    // one exception is if the admin user tries to create a temporary resource
2008                    if (!CmsResource.getName(resourcePath).startsWith(TEMP_FILE_PREFIX)
2009                        || !m_securityManager.hasRole(dbc, dbc.currentUser(), CmsRole.ROOT_ADMIN)) {
2010                        throw new CmsLockException(
2011                            Messages.get().container(
2012                                Messages.ERR_CREATE_RESOURCE_PARENT_LOCK_1,
2013                                dbc.removeSiteRoot(resourcePath)));
2014                    }
2015                }
2016                if (CmsResourceTypeJsp.isJsp(resource)) {
2017                    // security check when trying to create a new jsp file
2018                    m_securityManager.checkRoleForResource(dbc, CmsRole.VFS_MANAGER, parentFolder);
2019                }
2020
2021                // check import configuration of "lost and found" folder
2022                boolean useLostAndFound = importCase && !OpenCms.getImportExportManager().overwriteCollidingResources();
2023
2024                // check if the resource already exists by name
2025                CmsResource currentResourceByName = null;
2026                try {
2027                    currentResourceByName = readResource(dbc, resourcePath, CmsResourceFilter.ALL);
2028                } catch (CmsVfsResourceNotFoundException e) {
2029                    // if the resource does exist, we have to check the id later to decide what to do
2030                }
2031
2032                // check if the resource already exists by id
2033                try {
2034                    CmsResource currentResourceById = readResource(
2035                        dbc,
2036                        resource.getStructureId(),
2037                        CmsResourceFilter.ALL);
2038                    // it is not allowed to import resources when there is already a resource with the same id but different path
2039                    if (!currentResourceById.getRootPath().equals(resourcePath)) {
2040                        throw new CmsVfsResourceAlreadyExistsException(
2041                            Messages.get().container(
2042                                Messages.ERR_RESOURCE_WITH_ID_ALREADY_EXISTS_3,
2043                                dbc.removeSiteRoot(resourcePath),
2044                                dbc.removeSiteRoot(currentResourceById.getRootPath()),
2045                                currentResourceById.getStructureId()));
2046                    }
2047                } catch (CmsVfsResourceNotFoundException e) {
2048                    // if the resource does exist, we have to check the id later to decide what to do
2049                }
2050
2051                // check the permissions
2052                if (currentResourceByName == null) {
2053                    // resource does not exist - check parent folder
2054                    m_securityManager.checkPermissions(
2055                        dbc,
2056                        parentFolder,
2057                        CmsPermissionSet.ACCESS_WRITE,
2058                        false,
2059                        CmsResourceFilter.IGNORE_EXPIRATION);
2060                } else {
2061                    // resource already exists - check existing resource
2062                    m_securityManager.checkPermissions(
2063                        dbc,
2064                        currentResourceByName,
2065                        CmsPermissionSet.ACCESS_WRITE,
2066                        !importCase,
2067                        CmsResourceFilter.ALL);
2068                }
2069
2070                // now look for the resource by name
2071                if (currentResourceByName != null) {
2072                    boolean overwrite = true;
2073                    if (currentResourceByName.getState().isDeleted()) {
2074                        if (!currentResourceByName.isFolder()) {
2075                            // if a non-folder resource was deleted it's treated like a new resource
2076                            overwrite = false;
2077                        }
2078                    } else {
2079                        if (!importCase) {
2080                            // direct "overwrite" of a resource is possible only during import,
2081                            // or if the resource has been deleted
2082                            throw new CmsVfsResourceAlreadyExistsException(
2083                                org.opencms.db.generic.Messages.get().container(
2084                                    org.opencms.db.generic.Messages.ERR_RESOURCE_WITH_NAME_ALREADY_EXISTS_1,
2085                                    dbc.removeSiteRoot(resource.getRootPath())));
2086                        }
2087                        // the resource already exists
2088                        if (!resource.isFolder()
2089                            && useLostAndFound
2090                            && (!currentResourceByName.getResourceId().equals(resource.getResourceId()))) {
2091                            // semantic change: the current resource is moved to L&F and the imported resource will overwrite the old one
2092                            // will leave the resource with state deleted,
2093                            // but it does not matter, since the state will be set later again
2094                            moveToLostAndFound(dbc, currentResourceByName, false);
2095                        }
2096                    }
2097                    if (!overwrite) {
2098                        // lock the resource, will throw an exception if not lockable
2099                        lockResource(dbc, currentResourceByName, CmsLockType.EXCLUSIVE);
2100
2101                        // trigger createResource instead of writeResource
2102                        currentResourceByName = null;
2103                    }
2104                }
2105                // if null, create new resource, if not null write resource
2106                CmsResource overwrittenResource = currentResourceByName;
2107
2108                // extract the name (without path)
2109                String targetName = CmsResource.getName(resourcePath);
2110
2111                int contentLength;
2112
2113                // modify target name and content length in case of folder creation
2114                if (resource.isFolder()) {
2115                    // folders never have any content
2116                    contentLength = -1;
2117                    // must cut of trailing '/' for folder creation (or name check fails)
2118                    if (CmsResource.isFolder(targetName)) {
2119                        targetName = targetName.substring(0, targetName.length() - 1);
2120                    }
2121                } else {
2122                    // otherwise ensure content and content length are set correctly
2123                    if (content != null) {
2124                        // if a content is provided, in each case the length is the length of this content
2125                        contentLength = content.length;
2126                    } else if (overwrittenResource != null) {
2127                        // we have no content, but an already existing resource - length remains unchanged
2128                        contentLength = overwrittenResource.getLength();
2129                    } else {
2130                        // we have no content - length is used as set in the resource
2131                        contentLength = resource.getLength();
2132                    }
2133                }
2134
2135                // check if the target name is valid (forbidden chars etc.),
2136                // if not throw an exception
2137                // must do this here since targetName is modified in folder case (see above)
2138                CmsResource.checkResourceName(targetName);
2139
2140                // set structure and resource ids as given
2141                CmsUUID structureId = resource.getStructureId();
2142                CmsUUID resourceId = resource.getResourceId();
2143
2144                // decide which structure id to use
2145                if (overwrittenResource != null) {
2146                    // resource exists, re-use existing ids
2147                    structureId = overwrittenResource.getStructureId();
2148                }
2149                if (structureId.isNullUUID()) {
2150                    // need a new structure id
2151                    structureId = new CmsUUID();
2152                }
2153
2154                // decide which resource id to use
2155                if (overwrittenResource != null) {
2156                    // if we are overwriting we have to assure the resource id is the same
2157                    resourceId = overwrittenResource.getResourceId();
2158                }
2159                if (resourceId.isNullUUID()) {
2160                    // need a new resource id
2161                    resourceId = new CmsUUID();
2162                }
2163
2164                try {
2165                    // check online resource
2166                    CmsResource onlineResource = getVfsDriver(
2167                        dbc).readResource(dbc, CmsProject.ONLINE_PROJECT_ID, resourcePath, true);
2168                    // only allow to overwrite with different id if importing (createResource will set the right id)
2169                    try {
2170                        CmsResource offlineResource = getVfsDriver(dbc).readResource(
2171                            dbc,
2172                            dbc.currentProject().getUuid(),
2173                            onlineResource.getStructureId(),
2174                            true);
2175                        if (!offlineResource.getRootPath().equals(onlineResource.getRootPath())) {
2176                            throw new CmsVfsOnlineResourceAlreadyExistsException(
2177                                Messages.get().container(
2178                                    Messages.ERR_ONLINE_RESOURCE_EXISTS_2,
2179                                    dbc.removeSiteRoot(resourcePath),
2180                                    dbc.removeSiteRoot(offlineResource.getRootPath())));
2181                        }
2182                    } catch (CmsVfsResourceNotFoundException e) {
2183                        // there is no problem for now
2184                        // but should never happen
2185                        if (LOG.isErrorEnabled()) {
2186                            LOG.error(e.getLocalizedMessage(), e);
2187                        }
2188                    }
2189                } catch (CmsVfsResourceNotFoundException e) {
2190                    // ok, there is no online entry to worry about
2191                }
2192
2193                // now create a resource object with all informations
2194                newResource = new CmsResource(
2195                    structureId,
2196                    resourceId,
2197                    resourcePath,
2198                    resource.getTypeId(),
2199                    resource.isFolder(),
2200                    resource.getFlags(),
2201                    dbc.currentProject().getUuid(),
2202                    resource.getState(),
2203                    resource.getDateCreated(),
2204                    resource.getUserCreated(),
2205                    resource.getDateLastModified(),
2206                    resource.getUserLastModified(),
2207                    resource.getDateReleased(),
2208                    resource.getDateExpired(),
2209                    1,
2210                    contentLength,
2211                    resource.getDateContent(),
2212                    resource.getVersion()); // version number does not matter since it will be computed later
2213
2214                // ensure date is updated only if required
2215                if (resource.isTouched()) {
2216                    // this will trigger the internal "is touched" state on the new resource
2217                    newResource.setDateLastModified(resource.getDateLastModified());
2218                }
2219
2220                if (resource.isFile()) {
2221                    // check if a sibling to the imported resource lies in a marked site
2222                    if (labelResource(dbc, resource, resourcePath, 2)) {
2223                        int flags = resource.getFlags();
2224                        flags |= CmsResource.FLAG_LABELED;
2225                        resource.setFlags(flags);
2226                    }
2227                    // ensure siblings don't overwrite existing resource records
2228                    if (content == null) {
2229                        newResource.setState(CmsResource.STATE_KEEP);
2230                    }
2231                }
2232
2233                // delete all relations for the resource, before writing the content
2234                getVfsDriver(
2235                    dbc).deleteRelations(dbc, dbc.currentProject().getUuid(), newResource, CmsRelationFilter.TARGETS);
2236                if (overwrittenResource == null) {
2237                    CmsLock lock = getLock(dbc, newResource);
2238                    if (lock.getEditionLock().isExclusive()) {
2239                        unlockResource(dbc, newResource, true, false);
2240                    }
2241                    // resource does not exist.
2242                    newResource = getVfsDriver(
2243                        dbc).createResource(dbc, dbc.currentProject().getUuid(), newResource, content);
2244                } else {
2245                    // resource already exists.
2246                    // probably the resource is a merged page file that gets overwritten during import, or it gets
2247                    // overwritten by a copy operation. if so, the structure & resource state are not modified to changed.
2248                    int updateStates = (overwrittenResource.getState().isNew()
2249                    ? CmsDriverManager.NOTHING_CHANGED
2250                    : CmsDriverManager.UPDATE_ALL);
2251                    getVfsDriver(dbc).writeResource(dbc, dbc.currentProject().getUuid(), newResource, updateStates);
2252
2253                    if ((content != null) && resource.isFile()) {
2254                        // also update file content if required
2255                        getVfsDriver(dbc).writeContent(dbc, newResource, content);
2256                    }
2257                }
2258
2259                // write the properties (internal operation, no events or duplicate permission checks)
2260                writePropertyObjects(dbc, newResource, properties, false);
2261
2262                // lock the created resource
2263                try {
2264                    // if it is locked by another user (copied or moved resource) this lock should be preserved and
2265                    // the exception is OK: locks on created resources are a slave feature to original locks
2266                    lockResource(dbc, newResource, CmsLockType.EXCLUSIVE);
2267                } catch (CmsLockException cle) {
2268                    if (LOG.isDebugEnabled()) {
2269                        LOG.debug(
2270                            Messages.get().getBundle().key(
2271                                Messages.ERR_CREATE_RESOURCE_LOCK_1,
2272                                new Object[] {dbc.removeSiteRoot(newResource.getRootPath())}));
2273                    }
2274                }
2275
2276                if (!importCase) {
2277                    log(
2278                        dbc,
2279                        new CmsLogEntry(
2280                            dbc,
2281                            newResource.getStructureId(),
2282                            CmsLogEntryType.RESOURCE_CREATED,
2283                            new String[] {resource.getRootPath()}),
2284                        false);
2285                } else {
2286                    log(
2287                        dbc,
2288                        new CmsLogEntry(
2289                            dbc,
2290                            newResource.getStructureId(),
2291                            CmsLogEntryType.RESOURCE_IMPORTED,
2292                            new String[] {resource.getRootPath()}),
2293                        false);
2294                }
2295            }
2296        } finally {
2297            // clear the internal caches
2298            m_monitor.clearAccessControlListCache();
2299            m_monitor.flushCache(CmsMemoryMonitor.CacheType.PROPERTY, CmsMemoryMonitor.CacheType.PROPERTY_LIST);
2300
2301            if (newResource != null) {
2302                // fire an event that a new resource has been created
2303                OpenCms.fireCmsEvent(
2304                    new CmsEvent(
2305                        I_CmsEventListener.EVENT_RESOURCE_CREATED,
2306                        Collections.<String, Object> singletonMap(I_CmsEventListener.KEY_RESOURCE, newResource)));
2307            }
2308        }
2309        return newResource;
2310    }
2311
2312    /**
2313     * Creates a new resource of the given resource type
2314     * with the provided content and properties.<p>
2315     *
2316     * If the provided content is null and the resource is not a folder,
2317     * the content will be set to an empty byte array.<p>
2318     *
2319     * @param dbc the current database context
2320     * @param resourcename the name of the resource to create (full path)
2321     * @param type the type of the resource to create
2322     * @param content the content for the new resource
2323     * @param properties the properties for the new resource
2324     *
2325     * @return the created resource
2326     *
2327     * @throws CmsException if something goes wrong
2328     * @throws CmsIllegalArgumentException if the <code>resourcename</code> argument is null or of length 0
2329     *
2330     * @see CmsObject#createResource(String, int, byte[], List)
2331     * @see CmsObject#createResource(String, int)
2332     * @see I_CmsResourceType#createResource(CmsObject, CmsSecurityManager, String, byte[], List)
2333     */
2334    @SuppressWarnings("javadoc")
2335    public CmsResource createResource(
2336        CmsDbContext dbc,
2337        String resourcename,
2338        int type,
2339        byte[] content,
2340        List<CmsProperty> properties)
2341    throws CmsException, CmsIllegalArgumentException {
2342
2343        String targetName = resourcename;
2344
2345        if (content == null) {
2346            // name based resource creation MUST have a content
2347            content = new byte[0];
2348        }
2349        int size;
2350
2351        if (CmsFolder.isFolderType(type)) {
2352            // must cut of trailing '/' for folder creation
2353            if (CmsResource.isFolder(targetName)) {
2354                targetName = targetName.substring(0, targetName.length() - 1);
2355            }
2356            size = -1;
2357        } else {
2358            size = content.length;
2359        }
2360
2361        // create a new resource
2362        CmsResource newResource = new CmsResource(
2363            CmsUUID.getNullUUID(), // uuids will be "corrected" later
2364            CmsUUID.getNullUUID(),
2365            targetName,
2366            type,
2367            CmsFolder.isFolderType(type),
2368            0,
2369            dbc.currentProject().getUuid(),
2370            CmsResource.STATE_NEW,
2371            0,
2372            dbc.currentUser().getId(),
2373            0,
2374            dbc.currentUser().getId(),
2375            CmsResource.DATE_RELEASED_DEFAULT,
2376            CmsResource.DATE_EXPIRED_DEFAULT,
2377            1,
2378            size,
2379            0, // version number does not matter since it will be computed later
2380            0); // content time will be corrected later
2381
2382        return createResource(dbc, targetName, newResource, content, properties, false);
2383    }
2384
2385    /**
2386     * Creates a new sibling of the source resource.<p>
2387     *
2388     * @param dbc the current database context
2389     * @param source the resource to create a sibling for
2390     * @param destination the name of the sibling to create with complete path
2391     * @param properties the individual properties for the new sibling
2392     *
2393     * @return the new created sibling
2394     *
2395     * @throws CmsException if something goes wrong
2396     *
2397     * @see CmsObject#createSibling(String, String, List)
2398     * @see I_CmsResourceType#createSibling(CmsObject, CmsSecurityManager, CmsResource, String, List)
2399     */
2400    public CmsResource createSibling(
2401        CmsDbContext dbc,
2402        CmsResource source,
2403        String destination,
2404        List<CmsProperty> properties)
2405    throws CmsException {
2406
2407        if (source.isFolder()) {
2408            throw new CmsVfsException(Messages.get().container(Messages.ERR_VFS_FOLDERS_DONT_SUPPORT_SIBLINGS_0));
2409        }
2410
2411        // determine destination folder and resource name
2412        String destinationFoldername = CmsResource.getParentFolder(destination);
2413
2414        // read the destination folder (will also check read permissions)
2415        CmsFolder destinationFolder = readFolder(dbc, destinationFoldername, CmsResourceFilter.IGNORE_EXPIRATION);
2416
2417        // no further permission check required here, will be done in createResource()
2418
2419        // check the resource flags
2420        int flags = source.getFlags();
2421        if (labelResource(dbc, source, destination, 1)) {
2422            // set "labeled" link flag for new resource
2423            flags |= CmsResource.FLAG_LABELED;
2424        }
2425
2426        // create the new resource
2427        CmsResource newResource = new CmsResource(
2428            new CmsUUID(),
2429            source.getResourceId(),
2430            destination,
2431            source.getTypeId(),
2432            source.isFolder(),
2433            flags,
2434            dbc.currentProject().getUuid(),
2435            CmsResource.STATE_KEEP,
2436            source.getDateCreated(), // ensures current resource record remains untouched
2437            source.getUserCreated(),
2438            source.getDateLastModified(),
2439            source.getUserLastModified(),
2440            source.getDateReleased(),
2441            source.getDateExpired(),
2442            source.getSiblingCount() + 1,
2443            source.getLength(),
2444            source.getDateContent(),
2445            source.getVersion()); // version number does not matter since it will be computed later
2446
2447        // trigger "is touched" state on resource (will ensure modification date is kept unchanged)
2448        newResource.setDateLastModified(newResource.getDateLastModified());
2449
2450        log(
2451            dbc,
2452            new CmsLogEntry(
2453                dbc,
2454                newResource.getStructureId(),
2455                CmsLogEntryType.RESOURCE_CLONED,
2456                new String[] {newResource.getRootPath()}),
2457            false);
2458        // create the resource (null content signals creation of sibling)
2459        newResource = createResource(dbc, destination, newResource, null, properties, false);
2460
2461        // copy relations
2462        copyRelations(dbc, source, newResource);
2463
2464        // clear the caches
2465        m_monitor.clearAccessControlListCache();
2466
2467        List<CmsResource> modifiedResources = new ArrayList<CmsResource>();
2468        modifiedResources.add(source);
2469        modifiedResources.add(newResource);
2470        modifiedResources.add(destinationFolder);
2471        Map<String, Object> eventData = new HashMap<>();
2472        eventData.put(I_CmsEventListener.KEY_RESOURCES, modifiedResources);
2473        eventData.put(I_CmsEventListener.KEY_CHANGE, I_CmsEventListener.VALUE_CREATE_SIBLING);
2474        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCES_AND_PROPERTIES_MODIFIED, eventData));
2475
2476        return newResource;
2477    }
2478
2479    /**
2480     * Creates the project for the temporary workplace files.<p>
2481     *
2482     * @param dbc the current database context
2483     *
2484     * @return the created project for the temporary workplace files
2485     *
2486     * @throws CmsException if something goes wrong
2487     */
2488    public CmsProject createTempfileProject(CmsDbContext dbc) throws CmsException {
2489
2490        // read the needed groups from the cms
2491        CmsGroup projectUserGroup = readGroup(dbc, dbc.currentProject().getGroupId());
2492        CmsGroup projectManagerGroup = readGroup(dbc, dbc.currentProject().getManagerGroupId());
2493
2494        CmsProject tempProject = getProjectDriver(dbc).createProject(
2495            dbc,
2496            new CmsUUID(),
2497            dbc.currentUser(),
2498            projectUserGroup,
2499            projectManagerGroup,
2500            I_CmsProjectDriver.TEMP_FILE_PROJECT_NAME,
2501            Messages.get().getBundle(dbc.getRequestContext().getLocale()).key(
2502                Messages.GUI_WORKPLACE_TEMPFILE_PROJECT_DESC_0),
2503            CmsProject.PROJECT_FLAG_HIDDEN,
2504            CmsProject.PROJECT_TYPE_NORMAL);
2505        getProjectDriver(dbc).createProjectResource(dbc, tempProject.getUuid(), "/");
2506
2507        OpenCms.fireCmsEvent(
2508            new CmsEvent(
2509                I_CmsEventListener.EVENT_PROJECT_MODIFIED,
2510                Collections.<String, Object> singletonMap("project", tempProject)));
2511
2512        return tempProject;
2513    }
2514
2515    /**
2516     * Creates a new user.<p>
2517     *
2518     * @param dbc the current database context
2519     * @param name the name for the new user
2520     * @param password the password for the new user
2521     * @param description the description for the new user
2522     * @param additionalInfos the additional infos for the user
2523     *
2524     * @return the created user
2525     *
2526     * @see CmsObject#createUser(String, String, String, Map)
2527     *
2528     * @throws CmsException if something goes wrong
2529     * @throws CmsIllegalArgumentException if the name for the user is not valid
2530     */
2531    public CmsUser createUser(
2532        CmsDbContext dbc,
2533        String name,
2534        String password,
2535        String description,
2536        Map<String, Object> additionalInfos)
2537    throws CmsException, CmsIllegalArgumentException {
2538
2539        // no space before or after the name
2540        name = name.trim();
2541        // check the user name
2542        String userName = CmsOrganizationalUnit.getSimpleName(name);
2543        OpenCms.getValidationHandler().checkUserName(userName);
2544        if (CmsStringUtil.isEmptyOrWhitespaceOnly(userName)) {
2545            throw new CmsIllegalArgumentException(Messages.get().container(Messages.ERR_BAD_USER_1, userName));
2546        }
2547        // check the ou
2548        CmsOrganizationalUnit ou = readOrganizationalUnit(dbc, CmsOrganizationalUnit.getParentFqn(name));
2549        // check the password
2550        validatePassword(password);
2551
2552        Map<String, Object> info = new HashMap<String, Object>();
2553        if (additionalInfos != null) {
2554            info.putAll(additionalInfos);
2555        }
2556        if (description != null) {
2557            info.put(CmsUserSettings.ADDITIONAL_INFO_DESCRIPTION, description);
2558        }
2559        int flags = 0;
2560        if (ou.hasFlagWebuser()) {
2561            flags += I_CmsPrincipal.FLAG_USER_WEBUSER;
2562        }
2563        CmsUser user = getUserDriver(dbc).createUser(
2564            dbc,
2565            new CmsUUID(),
2566            name,
2567            OpenCms.getPasswordHandler().digest(password),
2568            " ",
2569            " ",
2570            " ",
2571            0,
2572            I_CmsPrincipal.FLAG_ENABLED + flags,
2573            0,
2574            info);
2575
2576        if (!dbc.getProjectId().isNullUUID()) {
2577            // user modified event is not needed
2578            return user;
2579        }
2580        // fire user modified event
2581        Map<String, Object> eventData = new HashMap<String, Object>();
2582        eventData.put(I_CmsEventListener.KEY_USER_ID, user.getId().toString());
2583        eventData.put(I_CmsEventListener.KEY_USER_ACTION, I_CmsEventListener.VALUE_USER_MODIFIED_ACTION_CREATE_USER);
2584        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_USER_MODIFIED, eventData));
2585        return user;
2586    }
2587
2588    /**
2589     * Deletes aliases indicated by a filter.<p>
2590     *
2591     * @param dbc the current database context
2592     * @param project the current project
2593     * @param filter the filter which describes which aliases to delete
2594     *
2595     * @throws CmsException if something goes wrong
2596     */
2597    public void deleteAliases(CmsDbContext dbc, CmsProject project, CmsAliasFilter filter) throws CmsException {
2598
2599        I_CmsVfsDriver vfsDriver = getVfsDriver(dbc);
2600        vfsDriver.deleteAliases(dbc, project, filter);
2601    }
2602
2603    /**
2604     * Deletes all property values of a file or folder.<p>
2605     *
2606     * If there are no other siblings than the specified resource,
2607     * both the structure and resource property values get deleted.
2608     * If the specified resource has siblings, only the structure
2609     * property values get deleted.<p>
2610     *
2611     * @param dbc the current database context
2612     * @param resourcename the name of the resource for which all properties should be deleted
2613     *
2614     * @throws CmsException if operation was not successful
2615     */
2616    public void deleteAllProperties(CmsDbContext dbc, String resourcename) throws CmsException {
2617
2618        CmsResource resource = null;
2619        List<CmsResource> resources = new ArrayList<CmsResource>();
2620
2621        try {
2622            // read the resource
2623            resource = readResource(dbc, resourcename, CmsResourceFilter.IGNORE_EXPIRATION);
2624
2625            // check the security
2626            m_securityManager.checkPermissions(
2627                dbc,
2628                resource,
2629                CmsPermissionSet.ACCESS_WRITE,
2630                false,
2631                CmsResourceFilter.ALL);
2632
2633            // delete the property values
2634            if (resource.getSiblingCount() > 1) {
2635                // the resource has siblings- delete only the (structure) properties of this sibling
2636                getVfsDriver(dbc).deletePropertyObjects(
2637                    dbc,
2638                    dbc.currentProject().getUuid(),
2639                    resource,
2640                    CmsProperty.DELETE_OPTION_DELETE_STRUCTURE_VALUES);
2641                resources.addAll(readSiblings(dbc, resource, CmsResourceFilter.ALL));
2642
2643            } else {
2644                // the resource has no other siblings- delete all (structure+resource) properties
2645                getVfsDriver(dbc).deletePropertyObjects(
2646                    dbc,
2647                    dbc.currentProject().getUuid(),
2648                    resource,
2649                    CmsProperty.DELETE_OPTION_DELETE_STRUCTURE_AND_RESOURCE_VALUES);
2650                resources.add(resource);
2651            }
2652        } finally {
2653            // clear the driver manager cache
2654            m_monitor.flushCache(CmsMemoryMonitor.CacheType.PROPERTY, CmsMemoryMonitor.CacheType.PROPERTY_LIST);
2655
2656            // fire an event that all properties of a resource have been deleted
2657            OpenCms.fireCmsEvent(
2658                new CmsEvent(
2659                    I_CmsEventListener.EVENT_RESOURCES_AND_PROPERTIES_MODIFIED,
2660                    Collections.<String, Object> singletonMap(I_CmsEventListener.KEY_RESOURCES, resources)));
2661        }
2662    }
2663
2664    /**
2665     * Deletes all entries in the published resource table.<p>
2666     *
2667     * @param dbc the current database context
2668     * @param linkType the type of resource deleted (0= non-paramter, 1=parameter)
2669     *
2670     * @throws CmsException if something goes wrong
2671     */
2672    public void deleteAllStaticExportPublishedResources(CmsDbContext dbc, int linkType) throws CmsException {
2673
2674        getProjectDriver(dbc).deleteAllStaticExportPublishedResources(dbc, linkType);
2675    }
2676
2677    /**
2678     * Deletes a group, where all permissions, users and children of the group
2679     * are transfered to a replacement group.<p>
2680     *
2681     * @param dbc the current request context
2682     * @param group the id of the group to be deleted
2683     * @param replacementId the id of the group to be transfered, can be <code>null</code>
2684     *
2685     * @throws CmsException if operation was not successful
2686     * @throws CmsDataAccessException if group to be deleted contains user
2687     */
2688    public void deleteGroup(CmsDbContext dbc, CmsGroup group, CmsUUID replacementId)
2689    throws CmsDataAccessException, CmsException {
2690
2691        CmsGroup replacementGroup = null;
2692        if (replacementId != null) {
2693            replacementGroup = readGroup(dbc, replacementId);
2694        }
2695        // get all child groups of the group
2696        List<CmsGroup> children = getChildren(dbc, group, false);
2697        // get all users in this group
2698        List<CmsUser> users = getUsersOfGroup(dbc, group.getName(), true, true, group.isRole());
2699        // get online project
2700        CmsProject onlineProject = readProject(dbc, CmsProject.ONLINE_PROJECT_ID);
2701        if (replacementGroup == null) {
2702            // remove users
2703            Iterator<CmsUser> itUsers = users.iterator();
2704            while (itUsers.hasNext()) {
2705                CmsUser user = itUsers.next();
2706                if (userInGroup(dbc, user.getName(), group.getName(), group.isRole())) {
2707                    removeUserFromGroup(dbc, user.getName(), group.getName(), group.isRole());
2708                }
2709            }
2710            // transfer children to grandfather if possible
2711            CmsUUID parentId = group.getParentId();
2712            if (parentId == null) {
2713                parentId = CmsUUID.getNullUUID();
2714            }
2715            Iterator<CmsGroup> itChildren = children.iterator();
2716            while (itChildren.hasNext()) {
2717                CmsGroup child = itChildren.next();
2718                child.setParentId(parentId);
2719                writeGroup(dbc, child);
2720            }
2721        } else {
2722            // move children
2723            Iterator<CmsGroup> itChildren = children.iterator();
2724            while (itChildren.hasNext()) {
2725                CmsGroup child = itChildren.next();
2726                child.setParentId(replacementId);
2727                writeGroup(dbc, child);
2728            }
2729            // move users
2730            Iterator<CmsUser> itUsers = users.iterator();
2731            while (itUsers.hasNext()) {
2732                CmsUser user = itUsers.next();
2733                addUserToGroup(dbc, user.getName(), replacementGroup.getName(), group.isRole());
2734                removeUserFromGroup(dbc, user.getName(), group.getName(), group.isRole());
2735            }
2736            // transfer for offline
2737            transferPrincipalResources(dbc, dbc.currentProject(), group.getId(), replacementId, true);
2738            // transfer for online
2739            transferPrincipalResources(dbc, onlineProject, group.getId(), replacementId, true);
2740        }
2741        // remove the group
2742        getUserDriver(
2743            dbc).removeAccessControlEntriesForPrincipal(dbc, dbc.currentProject(), onlineProject, group.getId());
2744        getUserDriver(dbc).deleteGroup(dbc, group.getName());
2745        // backup the group
2746        getHistoryDriver(dbc).writePrincipal(dbc, group);
2747        if (OpenCms.getSubscriptionManager().isEnabled()) {
2748            // delete all subscribed resources for group
2749            unsubscribeAllResourcesFor(dbc, OpenCms.getSubscriptionManager().getPoolName(), group);
2750        }
2751
2752        // clear the relevant caches
2753        m_monitor.uncacheGroup(group);
2754        m_monitor.flushCache(
2755            CmsMemoryMonitor.CacheType.USERGROUPS,
2756            CmsMemoryMonitor.CacheType.USER_LIST,
2757            CmsMemoryMonitor.CacheType.ACL);
2758
2759        if (!dbc.getProjectId().isNullUUID()) {
2760            // group modified event is not needed
2761            return;
2762        }
2763        // fire group modified event
2764        Map<String, Object> eventData = new HashMap<String, Object>();
2765        eventData.put(I_CmsEventListener.KEY_GROUP_ID, group.getId().toString());
2766        eventData.put(I_CmsEventListener.KEY_GROUP_NAME, group.getName());
2767        eventData.put(I_CmsEventListener.KEY_USER_ACTION, I_CmsEventListener.VALUE_GROUP_MODIFIED_ACTION_DELETE);
2768        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_GROUP_MODIFIED, eventData));
2769    }
2770
2771    /**
2772     * Deletes the versions from the history tables, keeping the given number of versions per resource.<p>
2773     *
2774     * if the <code>cleanUp</code> option is set, additionally versions of deleted resources will be removed.<p>
2775     *
2776     * @param dbc the current database context
2777     * @param versionsToKeep number of versions to keep, is ignored if negative
2778     * @param versionsDeleted number of versions to keep for deleted resources, is ignored if negative
2779     * @param timeDeleted deleted resources older than this will also be deleted, is ignored if negative
2780     * @param clearDeletedFilter filter to evaluate whether the deleted resources should be cleared
2781     * @param report the report for output logging
2782     *
2783     * @throws CmsException if operation was not successful
2784     */
2785    public void deleteHistoricalVersions(
2786        CmsDbContext dbc,
2787        int versionsToKeep,
2788        int versionsDeleted,
2789        long timeDeleted,
2790        Predicate<I_CmsHistoryResource> clearDeletedFilter,
2791        I_CmsReport report)
2792    throws CmsException {
2793
2794        if (clearDeletedFilter == null) {
2795            clearDeletedFilter = res -> true;
2796        }
2797
2798        report.println(Messages.get().container(Messages.RPT_START_DELETE_VERSIONS_0), I_CmsReport.FORMAT_HEADLINE);
2799        if (versionsToKeep >= 0) {
2800            report.println(
2801                Messages.get().container(Messages.RPT_START_DELETE_ACT_VERSIONS_1, Integer.valueOf(versionsToKeep)),
2802                I_CmsReport.FORMAT_HEADLINE);
2803
2804            List<I_CmsHistoryResource> resources = getHistoryDriver(dbc).getAllNotDeletedEntries(dbc);
2805            if (resources.isEmpty()) {
2806                report.println(Messages.get().container(Messages.RPT_DELETE_NOTHING_0), I_CmsReport.FORMAT_OK);
2807            }
2808            int n = resources.size();
2809            int m = 1;
2810            Iterator<I_CmsHistoryResource> itResources = resources.iterator();
2811            while (itResources.hasNext()) {
2812                I_CmsHistoryResource histResource = itResources.next();
2813
2814                report.print(
2815                    org.opencms.report.Messages.get().container(
2816                        org.opencms.report.Messages.RPT_SUCCESSION_2,
2817                        String.valueOf(m),
2818                        String.valueOf(n)),
2819                    I_CmsReport.FORMAT_NOTE);
2820                report.print(
2821                    org.opencms.report.Messages.get().container(
2822                        org.opencms.report.Messages.RPT_ARGUMENT_1,
2823                        dbc.removeSiteRoot(histResource.getRootPath())));
2824                report.print(org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_DOTS_0));
2825
2826                try {
2827                    int deleted = getHistoryDriver(dbc).deleteEntries(dbc, histResource, versionsToKeep, -1);
2828
2829                    report.print(
2830                        Messages.get().container(Messages.RPT_VERSION_DELETING_1, Integer.valueOf(deleted)),
2831                        I_CmsReport.FORMAT_NOTE);
2832                    report.print(org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_DOTS_0));
2833                    report.println(
2834                        org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_OK_0),
2835                        I_CmsReport.FORMAT_OK);
2836                } catch (CmsDataAccessException e) {
2837                    report.println(
2838                        org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_ERROR_0),
2839                        I_CmsReport.FORMAT_ERROR);
2840
2841                    if (LOG.isDebugEnabled()) {
2842                        LOG.debug(e.getLocalizedMessage(), e);
2843                    }
2844                }
2845
2846                m++;
2847            }
2848
2849            report.println(
2850                Messages.get().container(Messages.RPT_END_DELETE_ACT_VERSIONS_0),
2851                I_CmsReport.FORMAT_HEADLINE);
2852        }
2853        if ((versionsDeleted >= 0) || (timeDeleted >= 0)) {
2854            if (timeDeleted >= 0) {
2855                report.println(
2856                    Messages.get().container(
2857                        Messages.RPT_START_DELETE_DEL_VERSIONS_2,
2858                        Integer.valueOf(versionsDeleted),
2859                        new Date(timeDeleted)),
2860                    I_CmsReport.FORMAT_HEADLINE);
2861            } else {
2862                report.println(
2863                    Messages.get().container(
2864                        Messages.RPT_START_DELETE_DEL_VERSIONS_1,
2865                        Integer.valueOf(versionsDeleted)),
2866                    I_CmsReport.FORMAT_HEADLINE);
2867            }
2868            List<I_CmsHistoryResource> resources = getHistoryDriver(dbc).getAllDeletedEntries(dbc);
2869            resources = resources.stream().filter(clearDeletedFilter).collect(Collectors.toList());
2870            if (resources.isEmpty()) {
2871                report.println(Messages.get().container(Messages.RPT_DELETE_NOTHING_0), I_CmsReport.FORMAT_OK);
2872            }
2873            int n = resources.size();
2874            int m = 1;
2875            Iterator<I_CmsHistoryResource> itResources = resources.iterator();
2876            while (itResources.hasNext()) {
2877                I_CmsHistoryResource histResource = itResources.next();
2878
2879                report.print(
2880                    org.opencms.report.Messages.get().container(
2881                        org.opencms.report.Messages.RPT_SUCCESSION_2,
2882                        String.valueOf(m),
2883                        String.valueOf(n)),
2884                    I_CmsReport.FORMAT_NOTE);
2885                report.print(
2886                    org.opencms.report.Messages.get().container(
2887                        org.opencms.report.Messages.RPT_ARGUMENT_1,
2888                        dbc.removeSiteRoot(histResource.getRootPath())));
2889                report.print(org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_DOTS_0));
2890
2891                try {
2892                    int deleted = getHistoryDriver(dbc).deleteEntries(dbc, histResource, versionsDeleted, timeDeleted);
2893
2894                    report.print(
2895                        Messages.get().container(Messages.RPT_VERSION_DELETING_1, Integer.valueOf(deleted)),
2896                        I_CmsReport.FORMAT_NOTE);
2897                    report.print(org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_DOTS_0));
2898                    report.println(
2899                        org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_OK_0),
2900                        I_CmsReport.FORMAT_OK);
2901                } catch (CmsDataAccessException e) {
2902                    report.println(
2903                        org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_ERROR_0),
2904                        I_CmsReport.FORMAT_ERROR);
2905
2906                    if (LOG.isDebugEnabled()) {
2907                        LOG.debug(e.getLocalizedMessage(), e);
2908                    }
2909                }
2910
2911                m++;
2912            }
2913            report.println(
2914                Messages.get().container(Messages.RPT_END_DELETE_DEL_VERSIONS_0),
2915                I_CmsReport.FORMAT_HEADLINE);
2916        }
2917        report.println(Messages.get().container(Messages.RPT_END_DELETE_VERSIONS_0), I_CmsReport.FORMAT_HEADLINE);
2918    }
2919
2920    /**
2921     * Deletes all log entries matching the given filter.<p>
2922     *
2923     * @param dbc the current db context
2924     * @param filter the filter to use for deletion
2925     *
2926     * @throws CmsException if something goes wrong
2927     *
2928     * @see CmsSecurityManager#deleteLogEntries(CmsRequestContext, CmsLogFilter)
2929     */
2930    public void deleteLogEntries(CmsDbContext dbc, CmsLogFilter filter) throws CmsException {
2931
2932        updateLog(dbc);
2933        m_projectDriver.deleteLog(dbc, filter);
2934    }
2935
2936    /**
2937     * Deletes an organizational unit.<p>
2938     *
2939     * Only organizational units that contain no suborganizational unit can be deleted.<p>
2940     *
2941     * The organizational unit can not be delete if it is used in the request context,
2942     * or if the current user belongs to it.<p>
2943     *
2944     * All users and groups in the given organizational unit will be deleted.<p>
2945     *
2946     * @param dbc the current db context
2947     * @param organizationalUnit the organizational unit to delete
2948     *
2949     * @throws CmsException if operation was not successful
2950     *
2951     * @see org.opencms.security.CmsOrgUnitManager#deleteOrganizationalUnit(CmsObject, String)
2952     */
2953    public void deleteOrganizationalUnit(CmsDbContext dbc, CmsOrganizationalUnit organizationalUnit)
2954    throws CmsException {
2955
2956        // check organizational unit in context
2957        if (dbc.getRequestContext().getOuFqn().equals(organizationalUnit.getName())) {
2958            throw new CmsDbConsistencyException(
2959                Messages.get().container(Messages.ERR_ORGUNIT_DELETE_IN_CONTEXT_1, organizationalUnit.getName()));
2960        }
2961        // check organizational unit for user
2962        if (dbc.currentUser().getOuFqn().equals(organizationalUnit.getName())) {
2963            throw new CmsDbConsistencyException(
2964                Messages.get().container(Messages.ERR_ORGUNIT_DELETE_CURRENT_USER_1, organizationalUnit.getName()));
2965        }
2966        // check sub organizational units
2967        if (!getOrganizationalUnits(dbc, organizationalUnit, true).isEmpty()) {
2968            throw new CmsDbConsistencyException(
2969                Messages.get().container(Messages.ERR_ORGUNIT_DELETE_SUB_ORGUNITS_1, organizationalUnit.getName()));
2970        }
2971        // check groups
2972        List<CmsGroup> groups = getGroups(dbc, organizationalUnit, true, false);
2973        Iterator<CmsGroup> itGroups = groups.iterator();
2974        while (itGroups.hasNext()) {
2975            CmsGroup group = itGroups.next();
2976            if (!OpenCms.getDefaultUsers().isDefaultGroup(group.getName())) {
2977                throw new CmsDbConsistencyException(
2978                    Messages.get().container(Messages.ERR_ORGUNIT_DELETE_GROUPS_1, organizationalUnit.getName()));
2979            }
2980        }
2981        // check users
2982        if (!getUsers(dbc, organizationalUnit, true).isEmpty()) {
2983            throw new CmsDbConsistencyException(
2984                Messages.get().container(Messages.ERR_ORGUNIT_DELETE_USERS_1, organizationalUnit.getName()));
2985        }
2986
2987        // delete default groups if needed
2988        itGroups = groups.iterator();
2989        while (itGroups.hasNext()) {
2990            CmsGroup group = itGroups.next();
2991            deleteGroup(dbc, group, null);
2992        }
2993
2994        // delete projects
2995        Iterator<CmsProject> itProjects = getProjectDriver(dbc).readProjects(
2996            dbc,
2997            organizationalUnit.getName()).iterator();
2998        while (itProjects.hasNext()) {
2999            CmsProject project = itProjects.next();
3000            deleteProject(dbc, project, false);
3001        }
3002
3003        // delete roles
3004        Iterator<CmsGroup> itRoles = getGroups(dbc, organizationalUnit, true, true).iterator();
3005        while (itRoles.hasNext()) {
3006            CmsGroup role = itRoles.next();
3007            deleteGroup(dbc, role, null);
3008        }
3009
3010        // create a publish list for the 'virtual' publish event
3011        CmsResource resource = readResource(dbc, organizationalUnit.getId(), CmsResourceFilter.DEFAULT);
3012        CmsPublishList pl = new CmsPublishList(resource, false);
3013        pl.add(resource, false);
3014
3015        // remove the organizational unit itself
3016        getUserDriver(dbc).deleteOrganizationalUnit(dbc, organizationalUnit);
3017
3018        // write the publish history entry
3019        getProjectDriver(dbc).writePublishHistory(
3020            dbc,
3021            pl.getPublishHistoryId(),
3022            new CmsPublishedResource(resource, -1, CmsResourceState.STATE_DELETED));
3023
3024        // flush relevant caches
3025        m_monitor.clearPrincipalsCache();
3026        m_monitor.flushCache(CmsMemoryMonitor.CacheType.PROPERTY, CmsMemoryMonitor.CacheType.PROPERTY_LIST);
3027
3028        // fire the 'virtual' publish event
3029        Map<String, Object> eventData = new HashMap<String, Object>();
3030        eventData.put(I_CmsEventListener.KEY_PUBLISHID, pl.getPublishHistoryId().toString());
3031        eventData.put(I_CmsEventListener.KEY_PROJECTID, dbc.currentProject().getUuid());
3032        eventData.put(I_CmsEventListener.KEY_DBCONTEXT, dbc);
3033        CmsEvent afterPublishEvent = new CmsEvent(I_CmsEventListener.EVENT_PUBLISH_PROJECT, eventData);
3034        OpenCms.fireCmsEvent(afterPublishEvent);
3035
3036        m_lockManager.removeDeletedResource(dbc, resource.getRootPath());
3037
3038        if (!dbc.getProjectId().isNullUUID()) {
3039            // OU modified event is not needed
3040            return;
3041        }
3042        // fire OU modified event
3043        Map<String, Object> event2Data = new HashMap<String, Object>();
3044        event2Data.put(I_CmsEventListener.KEY_OU_NAME, organizationalUnit.getName());
3045        event2Data.put(I_CmsEventListener.KEY_USER_ACTION, I_CmsEventListener.VALUE_OU_MODIFIED_ACTION_DELETE);
3046        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_OU_MODIFIED, event2Data));
3047
3048    }
3049
3050    /**
3051     * Deletes a project.<p>
3052     *
3053     * Only the admin or the owner of the project can do this.
3054     *
3055     * @param dbc the current database context
3056     * @param deleteProject the project to be deleted
3057     *
3058     * @throws CmsException if something goes wrong
3059     */
3060    public void deleteProject(CmsDbContext dbc, CmsProject deleteProject) throws CmsException {
3061
3062        deleteProject(dbc, deleteProject, true);
3063    }
3064
3065    /**
3066     * Deletes a project.<p>
3067     *
3068     * Only the admin or the owner of the project can do this.
3069     *
3070     * @param dbc the current database context
3071     * @param deleteProject the project to be deleted
3072     * @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
3073     *
3074     * @throws CmsException if something goes wrong
3075     */
3076    public void deleteProject(CmsDbContext dbc, CmsProject deleteProject, boolean resetResources) throws CmsException {
3077
3078        CmsUUID projectId = deleteProject.getUuid();
3079
3080        if (resetResources) {
3081            // changed/new/deleted files in the specified project
3082            List<CmsResource> modifiedFiles = readChangedResourcesInsideProject(dbc, projectId, RCPRM_FILES_ONLY_MODE);
3083            // changed/new/deleted folders in the specified project
3084            List<CmsResource> modifiedFolders = readChangedResourcesInsideProject(
3085                dbc,
3086                projectId,
3087                RCPRM_FOLDERS_ONLY_MODE);
3088            resetResourcesInProject(dbc, projectId, modifiedFiles, modifiedFolders);
3089        }
3090
3091        // unlock all resources in the project
3092        m_lockManager.removeResourcesInProject(deleteProject.getUuid(), true);
3093        m_monitor.clearAccessControlListCache();
3094        m_monitor.clearResourceCache();
3095
3096        // set project to online project if current project is the one which will be deleted
3097        if (projectId.equals(dbc.currentProject().getUuid())) {
3098            dbc.getRequestContext().setCurrentProject(readProject(dbc, CmsProject.ONLINE_PROJECT_ID));
3099        }
3100
3101        // delete the project itself
3102        getProjectDriver(dbc).deleteProject(dbc, deleteProject);
3103        m_monitor.uncacheProject(deleteProject);
3104
3105        // fire the corresponding event
3106        OpenCms.fireCmsEvent(
3107            new CmsEvent(
3108                I_CmsEventListener.EVENT_PROJECT_MODIFIED,
3109                Collections.<String, Object> singletonMap("project", deleteProject)));
3110
3111    }
3112
3113    /**
3114     * Deletes a property definition.<p>
3115     *
3116     * @param dbc the current database context
3117     * @param name the name of the property definition to delete
3118     *
3119     * @throws CmsException if something goes wrong
3120     */
3121    public void deletePropertyDefinition(CmsDbContext dbc, String name) throws CmsException {
3122
3123        CmsPropertyDefinition propertyDefinition = null;
3124
3125        try {
3126            // first read and then delete the metadefinition.
3127            propertyDefinition = readPropertyDefinition(dbc, name);
3128            getVfsDriver(dbc).deletePropertyDefinition(dbc, propertyDefinition);
3129            getHistoryDriver(dbc).deletePropertyDefinition(dbc, propertyDefinition);
3130        } finally {
3131
3132            // fire an event that a property of a resource has been deleted
3133            OpenCms.fireCmsEvent(
3134                new CmsEvent(
3135                    I_CmsEventListener.EVENT_PROPERTY_DEFINITION_MODIFIED,
3136                    Collections.<String, Object> singletonMap("propertyDefinition", propertyDefinition)));
3137        }
3138    }
3139
3140    /**
3141     * Deletes a publish job identified by its history id.<p>
3142     *
3143     * @param dbc the current database context
3144     * @param publishHistoryId the history id identifying the publish job
3145     *
3146     * @throws CmsException if something goes wrong
3147     */
3148    public void deletePublishJob(CmsDbContext dbc, CmsUUID publishHistoryId) throws CmsException {
3149
3150        getProjectDriver(dbc).deletePublishJob(dbc, publishHistoryId);
3151    }
3152
3153    /**
3154     * Deletes the publish list assigned to a publish job.<p>
3155     *
3156     * @param dbc the current database context
3157     * @param publishHistoryId the history id identifying the publish job
3158     * @throws CmsException if something goes wrong
3159     */
3160    public void deletePublishList(CmsDbContext dbc, CmsUUID publishHistoryId) throws CmsException {
3161
3162        getProjectDriver(dbc).deletePublishList(dbc, publishHistoryId);
3163    }
3164
3165    /**
3166     * Deletes all relations for the given resource matching the given filter.<p>
3167     *
3168     * @param dbc the current db context
3169     * @param resource the resource to delete the relations for
3170     * @param filter the filter to use for deletion
3171     *
3172     * @throws CmsException if something goes wrong
3173     *
3174     * @see CmsSecurityManager#deleteRelationsForResource(CmsRequestContext, CmsResource, CmsRelationFilter)
3175     */
3176    public void deleteRelationsForResource(CmsDbContext dbc, CmsResource resource, CmsRelationFilter filter)
3177    throws CmsException {
3178
3179        if (filter.includesDefinedInContent()) {
3180            throw new CmsIllegalArgumentException(
3181                Messages.get().container(
3182                    Messages.ERR_DELETE_RELATION_IN_CONTENT_2,
3183                    dbc.removeSiteRoot(resource.getRootPath()),
3184                    filter.getTypes()));
3185        }
3186        getVfsDriver(dbc).deleteRelations(dbc, dbc.currentProject().getUuid(), resource, filter);
3187        setDateLastModified(dbc, resource, System.currentTimeMillis());
3188        log(
3189            dbc,
3190            new CmsLogEntry(
3191                dbc,
3192                resource.getStructureId(),
3193                CmsLogEntryType.RESOURCE_REMOVE_RELATION,
3194                new String[] {resource.getRootPath(), filter.toString()}),
3195            false);
3196    }
3197
3198    /**
3199     * Deletes a resource.<p>
3200     *
3201     * The <code>siblingMode</code> parameter controls how to handle siblings
3202     * during the delete operation.
3203     * Possible values for this parameter are:
3204     * <ul>
3205     * <li><code>{@link CmsResource#DELETE_REMOVE_SIBLINGS}</code></li>
3206     * <li><code>{@link CmsResource#DELETE_PRESERVE_SIBLINGS}</code></li>
3207     * </ul><p>
3208     *
3209     * @param dbc the current database context
3210     * @param resource the name of the resource to delete (full path)
3211     * @param siblingMode indicates how to handle siblings of the deleted resource
3212     *
3213     * @throws CmsException if something goes wrong
3214     *
3215     * @see CmsObject#deleteResource(String, CmsResource.CmsResourceDeleteMode)
3216     * @see I_CmsResourceType#deleteResource(CmsObject, CmsSecurityManager, CmsResource, CmsResource.CmsResourceDeleteMode)
3217     */
3218    public void deleteResource(CmsDbContext dbc, CmsResource resource, CmsResource.CmsResourceDeleteMode siblingMode)
3219    throws CmsException {
3220
3221        // upgrade a potential inherited, non-shared lock into a common lock
3222        CmsLock currentLock = getLock(dbc, resource);
3223        if (currentLock.getEditionLock().isDirectlyInherited()) {
3224            // upgrade the lock status if required
3225            lockResource(dbc, resource, CmsLockType.EXCLUSIVE);
3226        }
3227
3228        // check if siblings of the resource exist and must be deleted as well
3229        if (resource.isFolder()) {
3230            // folder can have no siblings
3231            siblingMode = CmsResource.DELETE_PRESERVE_SIBLINGS;
3232        }
3233
3234        // if selected, add all siblings of this resource to the list of resources to be deleted
3235        boolean allSiblingsRemoved;
3236        List<CmsResource> resources;
3237        if (siblingMode == CmsResource.DELETE_REMOVE_SIBLINGS) {
3238            resources = new ArrayList<CmsResource>(readSiblings(dbc, resource, CmsResourceFilter.ALL));
3239            allSiblingsRemoved = true;
3240
3241            // ensure that the resource requested to be deleted is the last resource that gets actually deleted
3242            // to keep the shared locks of the siblings while those get deleted.
3243            resources.remove(resource);
3244            resources.add(resource);
3245        } else {
3246            // only delete the resource, no siblings
3247            resources = Collections.singletonList(resource);
3248            allSiblingsRemoved = false;
3249        }
3250
3251        int size = resources.size();
3252        // if we have only one resource no further check is required
3253        if (size > 1) {
3254            CmsMultiException me = new CmsMultiException();
3255            // ensure that each sibling is unlocked or locked by the current user
3256            for (int i = 0; i < size; i++) {
3257                CmsResource currentResource = resources.get(i);
3258                currentLock = getLock(dbc, currentResource);
3259                if (!currentLock.getEditionLock().isUnlocked() && !currentLock.isOwnedBy(dbc.currentUser())) {
3260                    // the resource is locked by a user different from the current user
3261                    CmsRequestContext context = dbc.getRequestContext();
3262                    me.addException(
3263                        new CmsLockException(
3264                            org.opencms.lock.Messages.get().container(
3265                                org.opencms.lock.Messages.ERR_SIBLING_LOCKED_2,
3266                                context.getSitePath(currentResource),
3267                                context.getSitePath(resource))));
3268                }
3269            }
3270            if (!me.getExceptions().isEmpty()) {
3271                throw me;
3272            }
3273        }
3274
3275        boolean removeAce = true;
3276
3277        if (resource.isFolder()) {
3278            // check if the folder has any resources in it
3279            Iterator<CmsResource> childResources = getVfsDriver(
3280                dbc).readChildResources(dbc, dbc.currentProject(), resource, true, true).iterator();
3281
3282            CmsUUID projectId = CmsProject.ONLINE_PROJECT_ID;
3283            if (dbc.currentProject().isOnlineProject()) {
3284                projectId = CmsUUID.getOpenCmsUUID(); // HACK: to get an offline project id
3285            }
3286
3287            // collect the names of the resources inside the folder, excluding the moved resources
3288            StringBuffer errorResNames = new StringBuffer(128);
3289            while (childResources.hasNext()) {
3290                CmsResource errorRes = childResources.next();
3291                if (errorRes.getState().isDeleted()) {
3292                    continue;
3293                }
3294                // if deleting offline, or not moved, or just renamed inside the deleted folder
3295                // so, it may remain some orphan online entries for moved resources
3296                // which will be fixed during the publishing of the moved resources
3297                boolean error = !dbc.currentProject().isOnlineProject();
3298                if (!error) {
3299                    try {
3300                        String originalPath = getVfsDriver(
3301                            dbc).readResource(dbc, projectId, errorRes.getRootPath(), true).getRootPath();
3302                        error = originalPath.equals(errorRes.getRootPath())
3303                            || originalPath.startsWith(resource.getRootPath());
3304                    } catch (CmsVfsResourceNotFoundException e) {
3305                        // ignore
3306                    }
3307                }
3308                if (error) {
3309                    if (errorResNames.length() != 0) {
3310                        errorResNames.append(", ");
3311                    }
3312                    errorResNames.append("[" + dbc.removeSiteRoot(errorRes.getRootPath()) + "]");
3313                }
3314            }
3315
3316            // the current implementation only deletes empty folders
3317            if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(errorResNames.toString())) {
3318                throw new CmsVfsException(
3319                    org.opencms.db.generic.Messages.get().container(
3320                        org.opencms.db.generic.Messages.ERR_DELETE_NONEMTY_FOLDER_2,
3321                        dbc.removeSiteRoot(resource.getRootPath()),
3322                        errorResNames.toString()));
3323            }
3324        }
3325
3326        // delete all collected resources
3327        for (int i = 0; i < size; i++) {
3328            CmsResource currentResource = resources.get(i);
3329
3330            // try to delete/remove the resource only if the user has write access to the resource
3331            // check permissions only for the sibling, the resource it self was already checked or
3332            // is to be removed without write permissions, ie. while deleting a folder
3333            if (!currentResource.equals(resource)
3334                && (I_CmsPermissionHandler.PERM_ALLOWED != m_securityManager.hasPermissions(
3335                    dbc,
3336                    currentResource,
3337                    CmsPermissionSet.ACCESS_WRITE,
3338                    LockCheck.yes,
3339                    CmsResourceFilter.ALL))) {
3340
3341                // no write access to sibling - must keep ACE (see below)
3342                allSiblingsRemoved = false;
3343            } else {
3344                // write access to sibling granted
3345                boolean existsOnline = (getVfsDriver(dbc).validateStructureIdExists(
3346                    dbc,
3347                    CmsProject.ONLINE_PROJECT_ID,
3348                    currentResource.getStructureId()) || !(currentResource.getState().equals(CmsResource.STATE_NEW)));
3349                if (!existsOnline) {
3350                    // the resource does not exist online => remove the resource
3351                    // this means the resource is "new" (blue) in the offline project
3352
3353                    // delete all properties of this resource
3354                    deleteAllProperties(dbc, currentResource.getRootPath());
3355
3356                    if (currentResource.isFolder()) {
3357                        getVfsDriver(dbc).removeFolder(dbc, dbc.currentProject(), currentResource);
3358                    } else {
3359                        // check labels
3360                        if (currentResource.isLabeled() && !labelResource(dbc, currentResource, null, 2)) {
3361                            // update the resource flags to "un label" the other siblings
3362                            int flags = currentResource.getFlags();
3363                            flags &= ~CmsResource.FLAG_LABELED;
3364                            currentResource.setFlags(flags);
3365                        }
3366                        getVfsDriver(dbc).removeFile(dbc, dbc.currentProject().getUuid(), currentResource);
3367                    }
3368
3369                    // ensure an exclusive lock is removed in the lock manager for a deleted new resource,
3370                    // otherwise it would "stick" in the lock manager, preventing other users from creating
3371                    // a file with the same name (issue with temp files in editor)
3372                    m_lockManager.removeDeletedResource(dbc, currentResource.getRootPath());
3373                    // delete relations
3374                    getVfsDriver(dbc).deleteRelations(
3375                        dbc,
3376                        dbc.currentProject().getUuid(),
3377                        currentResource,
3378                        CmsRelationFilter.TARGETS);
3379                    getVfsDriver(dbc).deleteUrlNameMappingEntries(
3380                        dbc,
3381                        false,
3382                        CmsUrlNameMappingFilter.ALL.filterStructureId(currentResource.getStructureId()));
3383                    getVfsDriver(dbc).deleteAliases(
3384                        dbc,
3385                        dbc.currentProject(),
3386                        new CmsAliasFilter(null, null, currentResource.getStructureId()));
3387                    log(
3388                        dbc,
3389                        new CmsLogEntry(
3390                            dbc,
3391                            currentResource.getStructureId(),
3392                            CmsLogEntryType.RESOURCE_NEW_DELETED,
3393                            new String[] {currentResource.getRootPath()}),
3394                        true);
3395                } else {
3396                    // the resource exists online => mark the resource as deleted
3397                    // structure record is removed during next publish
3398                    // if one (or more) siblings are not removed, the ACE can not be removed
3399                    removeAce = false;
3400                    // set resource state to deleted
3401                    currentResource.setState(CmsResource.STATE_DELETED);
3402                    getVfsDriver(
3403                        dbc).writeResourceState(dbc, dbc.currentProject(), currentResource, UPDATE_STRUCTURE, false);
3404
3405                    // update the project ID
3406                    getVfsDriver(dbc).writeLastModifiedProjectId(
3407                        dbc,
3408                        dbc.currentProject(),
3409                        dbc.currentProject().getUuid(),
3410                        currentResource);
3411                    // log it
3412
3413                    log(
3414                        dbc,
3415                        new CmsLogEntry(
3416                            dbc,
3417                            currentResource.getStructureId(),
3418                            CmsLogEntryType.RESOURCE_DELETED,
3419                            new String[] {currentResource.getRootPath()}),
3420                        true);
3421                }
3422            }
3423        }
3424
3425        if ((resource.getSiblingCount() <= 1) || allSiblingsRemoved) {
3426            if (removeAce) {
3427                // remove the access control entries
3428                getUserDriver(dbc).removeAccessControlEntries(dbc, dbc.currentProject(), resource.getResourceId());
3429            }
3430        }
3431
3432        // flush all caches
3433        m_monitor.clearAccessControlListCache();
3434        m_monitor.flushCache(
3435            CmsMemoryMonitor.CacheType.PROPERTY,
3436            CmsMemoryMonitor.CacheType.PROPERTY_LIST,
3437            CmsMemoryMonitor.CacheType.PROJECT_RESOURCES);
3438
3439        Map<String, Object> eventData = new HashMap<String, Object>();
3440        eventData.put(I_CmsEventListener.KEY_RESOURCES, resources);
3441        eventData.put(I_CmsEventListener.KEY_DBCONTEXT, dbc);
3442        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_DELETED, eventData));
3443    }
3444
3445    /**
3446     * Deletes an entry in the published resource table.<p>
3447     *
3448     * @param dbc the current database context
3449     * @param resourceName The name of the resource to be deleted in the static export
3450     * @param linkType the type of resource deleted (0= non-parameter, 1=parameter)
3451     * @param linkParameter the parameters of the resource
3452     *
3453     * @throws CmsException if something goes wrong
3454     */
3455    public void deleteStaticExportPublishedResource(
3456        CmsDbContext dbc,
3457        String resourceName,
3458        int linkType,
3459        String linkParameter)
3460    throws CmsException {
3461
3462        getProjectDriver(dbc).deleteStaticExportPublishedResource(dbc, resourceName, linkType, linkParameter);
3463    }
3464
3465    /**
3466     * Deletes a user, where all permissions and resources attributes of the user
3467     * were transfered to a replacement user, if given.<p>
3468     *
3469     * Only users, which are in the group "administrators" are granted.<p>
3470     *
3471     * @param dbc the current database context
3472     * @param project the current project
3473     * @param username the name of the user to be deleted
3474     * @param replacementUsername the name of the user to be transfered, can be <code>null</code>
3475     *
3476     * @throws CmsException if operation was not successful
3477     */
3478    public void deleteUser(CmsDbContext dbc, CmsProject project, String username, String replacementUsername)
3479    throws CmsException {
3480
3481        // Test if the users exists
3482        CmsUser user = readUser(dbc, username);
3483        CmsUser replacementUser = null;
3484        if (replacementUsername != null) {
3485            replacementUser = readUser(dbc, replacementUsername);
3486        }
3487
3488        CmsProject onlineProject = readProject(dbc, CmsProject.ONLINE_PROJECT_ID);
3489        boolean withACEs = true;
3490        if (replacementUser == null) {
3491            withACEs = false;
3492            replacementUser = readUser(dbc, OpenCms.getDefaultUsers().getUserDeletedResource());
3493        }
3494
3495        boolean isVfsManager = m_securityManager.hasRole(dbc, replacementUser, CmsRole.VFS_MANAGER);
3496
3497        // iterate groups and roles
3498        for (int i = 0; i < 2; i++) {
3499            boolean readRoles = i != 0;
3500            Iterator<CmsGroup> itGroups = getGroupsOfUser(
3501                dbc,
3502                username,
3503                "",
3504                true,
3505                readRoles,
3506                true,
3507                dbc.getRequestContext().getRemoteAddress()).iterator();
3508            while (itGroups.hasNext()) {
3509                CmsGroup group = itGroups.next();
3510                if (!isVfsManager) {
3511                    // add replacement user to user groups
3512                    if (!userInGroup(dbc, replacementUser.getName(), group.getName(), readRoles)) {
3513                        addUserToGroup(dbc, replacementUser.getName(), group.getName(), readRoles);
3514                    }
3515                }
3516                // remove user from groups
3517                if (userInGroup(dbc, username, group.getName(), readRoles)) {
3518                    // we need this additional check because removing a user from a group
3519                    // may also automatically remove him from other groups if the group was
3520                    // associated with a role.
3521                    removeUserFromGroup(dbc, username, group.getName(), readRoles);
3522                }
3523            }
3524        }
3525        // remove all locks set for the deleted user
3526        m_lockManager.removeLocks(user.getId());
3527
3528        boolean skipTransfer = false;
3529        try {
3530
3531            // Looking up the resources for the principal transfer is pretty expensive,
3532            // but for webuser OUs without associated OU resources, it should be unnecessary.
3533            // So we can save ourselves the work.
3534            // We cache this "skippable" status on a per-OU basis, so we don't need to a do it for each
3535            // user if we delete a lot of users in a row.
3536            //
3537            // Note: Even if there *were* resources which would be affected by transferPrincipalResources,
3538            // skipping the transfer doesn't do any harm - the UI just displays the ID instead.
3539
3540            skipTransfer = m_skipTransferPrincipalResourceCache.get(user.getOuFqn(), () -> {
3541
3542                String ouName = CmsOrganizationalUnit.removeLeadingSeparator(user.getOuFqn());
3543                CmsOrganizationalUnit ou = readOrganizationalUnit(dbc, ouName);
3544                if (!ou.hasFlagWebuser()) {
3545                    return false;
3546                }
3547                List<CmsResource> ouResources = getResourcesForOrganizationalUnit(dbc, ou);
3548                return ouResources.size() == 0;
3549            });
3550        } catch (ExecutionException e) {
3551            LOG.error(e.getLocalizedMessage(), e);
3552        }
3553        if (skipTransfer) {
3554            LOG.info(
3555                "Skipping principal transfer while deleting user "
3556                    + user.getName()
3557                    + " ("
3558                    + user.getId()
3559                    + ") because it is part of a webuser OU with no OU resources.");
3560        } else {
3561            // offline
3562            if (dbc.getProjectId().isNullUUID()) {
3563                // offline project available
3564                transferPrincipalResources(dbc, project, user.getId(), replacementUser.getId(), withACEs);
3565            }
3566            // online
3567            transferPrincipalResources(dbc, onlineProject, user.getId(), replacementUser.getId(), withACEs);
3568        }
3569        getUserDriver(dbc).removeAccessControlEntriesForPrincipal(dbc, project, onlineProject, user.getId());
3570        getHistoryDriver(dbc).writePrincipal(dbc, user);
3571        getUserDriver(dbc).deleteUser(dbc, username);
3572        // delete user from cache
3573        m_monitor.clearUserCache(user);
3574
3575        if (!dbc.getProjectId().isNullUUID()) {
3576            // user modified event is not needed
3577            return;
3578        }
3579        // fire user modified event
3580        Map<String, Object> eventData = new HashMap<String, Object>();
3581        eventData.put(I_CmsEventListener.KEY_USER_ID, user.getId().toString());
3582        eventData.put(I_CmsEventListener.KEY_USER_NAME, user.getName());
3583        eventData.put(I_CmsEventListener.KEY_USER_ACTION, I_CmsEventListener.VALUE_USER_MODIFIED_ACTION_DELETE_USER);
3584        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_USER_MODIFIED, eventData));
3585    }
3586
3587    /**
3588     * Destroys this driver manager and releases all allocated resources.<p>
3589     */
3590    public void destroy() {
3591
3592        try {
3593            if (m_projectDriver != null) {
3594                try {
3595                    m_projectDriver.destroy();
3596                } catch (Throwable t) {
3597                    LOG.error(Messages.get().getBundle().key(Messages.ERR_CLOSE_PROJECT_DRIVER_0), t);
3598                }
3599                m_projectDriver = null;
3600            }
3601            if (m_userDriver != null) {
3602                try {
3603                    m_userDriver.destroy();
3604                } catch (Throwable t) {
3605                    LOG.error(Messages.get().getBundle().key(Messages.ERR_CLOSE_USER_DRIVER_0), t);
3606                }
3607                m_userDriver = null;
3608            }
3609            if (m_vfsDriver != null) {
3610                try {
3611                    m_vfsDriver.destroy();
3612                } catch (Throwable t) {
3613                    LOG.error(Messages.get().getBundle().key(Messages.ERR_CLOSE_VFS_DRIVER_0), t);
3614                }
3615                m_vfsDriver = null;
3616            }
3617            if (m_historyDriver != null) {
3618                try {
3619                    m_historyDriver.destroy();
3620                } catch (Throwable t) {
3621                    LOG.error(Messages.get().getBundle().key(Messages.ERR_CLOSE_HISTORY_DRIVER_0), t);
3622                }
3623                m_historyDriver = null;
3624            }
3625
3626            if (m_pools != null) {
3627                for (CmsDbPoolV11 pool : m_pools.values()) {
3628                    try {
3629                        pool.close();
3630                        if (CmsLog.INIT.isDebugEnabled()) {
3631                            CmsLog.INIT.debug(Messages.get().getBundle().key(Messages.INIT_CLOSE_CONN_POOL_1, pool));
3632                        }
3633
3634                    } catch (Throwable t) {
3635                        LOG.error(Messages.get().getBundle().key(Messages.LOG_CLOSE_CONN_POOL_ERROR_1, pool), t);
3636                    }
3637                }
3638                m_pools.clear();
3639            }
3640
3641            m_monitor.clearCache();
3642
3643            m_lockManager = null;
3644            m_htmlLinkValidator = null;
3645        } catch (Throwable t) {
3646            // ignore
3647        }
3648        if (CmsLog.INIT.isInfoEnabled()) {
3649            CmsLog.INIT.info(
3650                Messages.get().getBundle().key(Messages.INIT_DRIVER_MANAGER_DESTROY_1, getClass().getName()));
3651        }
3652    }
3653
3654    /**
3655     * Tests if a resource with the given resourceId does already exist in the Database.<p>
3656     *
3657     * @param dbc the current database context
3658     * @param resourceId the resource id to test for
3659     * @return true if a resource with the given id was found, false otherweise
3660     * @throws CmsException if something goes wrong
3661     */
3662    public boolean existsResourceId(CmsDbContext dbc, CmsUUID resourceId) throws CmsException {
3663
3664        return getVfsDriver(dbc).validateResourceIdExists(dbc, dbc.currentProject().getUuid(), resourceId);
3665    }
3666
3667    /**
3668     * Fills the given publish list with the the VFS resources that actually get published.<p>
3669     *
3670     * Please refer to the source code of this method for the rules on how to decide whether a
3671     * new/changed/deleted <code>{@link CmsResource}</code> object can be published or not.<p>
3672     *
3673     * @param dbc the current database context
3674     * @param publishList must be initialized with basic publish information (Project or direct publish operation),
3675     *                    the given publish list will be filled with all new/changed/deleted files from the current
3676     *                    (offline) project that will be actually published
3677     *
3678     * @throws CmsException if something goes wrong
3679     *
3680     * @see org.opencms.db.CmsPublishList
3681     */
3682    public void fillPublishList(CmsDbContext dbc, CmsPublishList publishList) throws CmsException {
3683
3684        if (!publishList.isDirectPublish()) {
3685            // when publishing a project
3686            // all modified resources with the last change done in the current project are candidates if unlocked
3687            List<CmsResource> folderList = getVfsDriver(dbc).readResourceTree(
3688                dbc,
3689                dbc.currentProject().getUuid(),
3690                CmsDriverManager.READ_IGNORE_PARENT,
3691                CmsDriverManager.READ_IGNORE_TYPE,
3692                CmsResource.STATE_UNCHANGED,
3693                CmsDriverManager.READ_IGNORE_TIME,
3694                CmsDriverManager.READ_IGNORE_TIME,
3695                CmsDriverManager.READ_IGNORE_TIME,
3696                CmsDriverManager.READ_IGNORE_TIME,
3697                CmsDriverManager.READ_IGNORE_TIME,
3698                CmsDriverManager.READ_IGNORE_TIME,
3699                CmsDriverManager.READMODE_INCLUDE_TREE
3700                    | CmsDriverManager.READMODE_INCLUDE_PROJECT
3701                    | CmsDriverManager.READMODE_EXCLUDE_STATE
3702                    | CmsDriverManager.READMODE_ONLY_FOLDERS);
3703
3704            List<CmsResource> fileList = getVfsDriver(dbc).readResourceTree(
3705                dbc,
3706                dbc.currentProject().getUuid(),
3707                CmsDriverManager.READ_IGNORE_PARENT,
3708                CmsDriverManager.READ_IGNORE_TYPE,
3709                CmsResource.STATE_UNCHANGED,
3710                CmsDriverManager.READ_IGNORE_TIME,
3711                CmsDriverManager.READ_IGNORE_TIME,
3712                CmsDriverManager.READ_IGNORE_TIME,
3713                CmsDriverManager.READ_IGNORE_TIME,
3714                CmsDriverManager.READ_IGNORE_TIME,
3715                CmsDriverManager.READ_IGNORE_TIME,
3716                CmsDriverManager.READMODE_INCLUDE_TREE
3717                    | CmsDriverManager.READMODE_INCLUDE_PROJECT
3718                    | CmsDriverManager.READMODE_EXCLUDE_STATE
3719                    | CmsDriverManager.READMODE_ONLY_FILES);
3720            CmsRequestContext context = dbc.getRequestContext();
3721            if ((context != null)
3722                && (context.getAttribute(CmsDefaultWorkflowManager.ATTR_CHECK_PUBLISH_RESOURCE_LIMIT) != null)) {
3723
3724                // check if total size and if it exceeds the resource limit and the request
3725                // context attribute is set, throw an exception.
3726                // we do it here since filterResources() can be very expensive on large resource lists
3727
3728                int limit = OpenCms.getWorkflowManager().getResourceLimit();
3729                int total = fileList.size() + folderList.size();
3730                if (total > limit) {
3731                    throw new CmsTooManyPublishResourcesException(total);
3732                }
3733            }
3734            publishList.addAll(filterResources(dbc, null, folderList), true);
3735            publishList.addAll(filterResources(dbc, publishList, fileList), true);
3736        } else {
3737            // this is a direct publish
3738            Iterator<CmsResource> it = publishList.getDirectPublishResources().iterator();
3739            while (it.hasNext()) {
3740                // iterate all resources in the direct publish list
3741                CmsResource directPublishResource = it.next();
3742                if (directPublishResource.isFolder()) {
3743                    // when publishing a folder directly,
3744                    // the folder and all modified resources within the tree below this folder
3745                    // and with the last change done in the current project are candidates if lockable
3746                    CmsLock lock = getLock(dbc, directPublishResource);
3747                    if (!directPublishResource.getState().isUnchanged() && lock.isLockableBy(dbc.currentUser())) {
3748
3749                        try {
3750                            m_securityManager.checkPermissions(
3751                                dbc,
3752                                directPublishResource,
3753                                CmsPermissionSet.ACCESS_DIRECT_PUBLISH,
3754                                false,
3755                                CmsResourceFilter.ALL);
3756                            publishList.add(directPublishResource, true);
3757                        } catch (CmsException e) {
3758                            // skip if not enough permissions
3759                        }
3760                    }
3761                    boolean shouldPublishDeletedSubResources = publishList.isUserPublishList()
3762                        && directPublishResource.getState().isDeleted();
3763                    if (publishList.isPublishSubResources() || shouldPublishDeletedSubResources) {
3764                        addSubResources(dbc, publishList, directPublishResource, resource -> true);
3765                    }
3766                } else if (directPublishResource.isFile() && !directPublishResource.getState().isUnchanged()) {
3767
3768                    // when publishing a file directly this file is the only candidate
3769                    // if it is modified and lockable
3770                    CmsLock lock = getLock(dbc, directPublishResource);
3771                    if (lock.isLockableBy(dbc.currentUser())) {
3772                        // check permissions
3773                        try {
3774                            m_securityManager.checkPermissions(
3775                                dbc,
3776                                directPublishResource,
3777                                CmsPermissionSet.ACCESS_DIRECT_PUBLISH,
3778                                false,
3779                                CmsResourceFilter.ALL);
3780                            publishList.add(directPublishResource, true);
3781                        } catch (CmsException e) {
3782                            // skip if not enough permissions
3783                        }
3784                    }
3785                }
3786            }
3787        }
3788
3789        // Step 2: if desired, extend the list of files to publish with related siblings
3790        if (publishList.isPublishSiblings()) {
3791            List<CmsResource> publishFiles = publishList.getFileList();
3792            int size = publishFiles.size();
3793
3794            // Improved: first calculate closure of all siblings, then filter and add them
3795            Set<CmsResource> siblingsClosure = new HashSet<CmsResource>(publishFiles);
3796            for (int i = 0; i < size; i++) {
3797                CmsResource currentFile = publishFiles.get(i);
3798                if (currentFile.getSiblingCount() > 1) {
3799                    siblingsClosure.addAll(readSiblings(dbc, currentFile, CmsResourceFilter.ALL_MODIFIED));
3800                }
3801            }
3802            publishList.addAll(filterSiblings(dbc, publishList, siblingsClosure), true);
3803        }
3804        publishList.initialize();
3805    }
3806
3807    /**
3808     * Returns the list of access control entries of a resource given its name.<p>
3809     *
3810     * @param dbc the current database context
3811     * @param resource the resource to read the access control entries for
3812     * @param getInherited true if the result should include all access control entries inherited by parent folders
3813     *
3814     * @return a list of <code>{@link CmsAccessControlEntry}</code> objects defining all permissions for the given resource
3815     *
3816     * @throws CmsException if something goes wrong
3817     */
3818    public List<CmsAccessControlEntry> getAccessControlEntries(
3819        CmsDbContext dbc,
3820        CmsResource resource,
3821        boolean getInherited)
3822    throws CmsException {
3823
3824        // get the ACE of the resource itself
3825        I_CmsUserDriver userDriver = getUserDriver(dbc);
3826        I_CmsVfsDriver vfsDriver = getVfsDriver(dbc);
3827        List<CmsAccessControlEntry> ace = userDriver.readAccessControlEntries(
3828            dbc,
3829            dbc.currentProject(),
3830            resource.getResourceId(),
3831            false);
3832
3833        // sort and check if we got the 'overwrite all' ace to stop looking up
3834        boolean overwriteAll = sortAceList(ace);
3835
3836        // get the ACE of each parent folder
3837        // Note: for the immediate parent, get non-inherited access control entries too,
3838        // if the resource is not a folder
3839        String parentPath = CmsResource.getParentFolder(resource.getRootPath());
3840        int d = (resource.isFolder()) ? 1 : 0;
3841
3842        while (!overwriteAll && getInherited && (parentPath != null)) {
3843            resource = vfsDriver.readFolder(dbc, dbc.currentProject().getUuid(), parentPath);
3844            List<CmsAccessControlEntry> entries = userDriver.readAccessControlEntries(
3845                dbc,
3846                dbc.currentProject(),
3847                resource.getResourceId(),
3848                d > 0);
3849
3850            // sort and check if we got the 'overwrite all' ace to stop looking up
3851            overwriteAll = sortAceList(entries);
3852
3853            for (CmsAccessControlEntry e : entries) {
3854                e.setFlags(CmsAccessControlEntry.ACCESS_FLAGS_INHERITED);
3855            }
3856
3857            ace.addAll(entries);
3858            parentPath = CmsResource.getParentFolder(resource.getRootPath());
3859            d++;
3860        }
3861
3862        return ace;
3863    }
3864
3865    /**
3866     * Returns the full access control list of a given resource.<p>
3867     *
3868     * @param dbc the current database context
3869     * @param resource the resource
3870     *
3871     * @return the access control list of the resource
3872     *
3873     * @throws CmsException if something goes wrong
3874     */
3875    public CmsAccessControlList getAccessControlList(CmsDbContext dbc, CmsResource resource) throws CmsException {
3876
3877        return getAccessControlList(dbc, resource, false);
3878    }
3879
3880    /**
3881     * Returns the access control list of a given resource.<p>
3882     *
3883     * If <code>inheritedOnly</code> is set, only inherited access control entries
3884     * are returned.<p>
3885     *
3886     * Note: For file resources, *all* permissions set at the immediate parent folder are inherited,
3887     * not only these marked to inherit.
3888     *
3889     * @param dbc the current database context
3890     * @param resource the resource
3891     * @param inheritedOnly skip non-inherited entries if set
3892     *
3893     * @return the access control list of the resource
3894     *
3895     * @throws CmsException if something goes wrong
3896     */
3897    public CmsAccessControlList getAccessControlList(CmsDbContext dbc, CmsResource resource, boolean inheritedOnly)
3898    throws CmsException {
3899
3900        return getAccessControlList(dbc, resource, inheritedOnly, resource.isFolder(), 0);
3901    }
3902
3903    /**
3904     * Returns the number of active connections managed by a pool.<p>
3905     *
3906     * @param dbPoolUrl the url of a pool
3907     * @return the number of active connections
3908     * @throws CmsDbException if something goes wrong
3909     */
3910    public int getActiveConnections(String dbPoolUrl) throws CmsDbException {
3911
3912        CmsDbPoolV11 pool = m_pools.get(dbPoolUrl);
3913        if (pool == null) {
3914            CmsMessageContainer message = Messages.get().container(Messages.ERR_UNKNOWN_POOL_URL_1, dbPoolUrl);
3915            throw new CmsDbException(message);
3916        }
3917        try {
3918            return pool.getActiveConnections();
3919        } catch (Exception exc) {
3920            CmsMessageContainer message = Messages.get().container(Messages.ERR_ACCESSING_POOL_1, dbPoolUrl);
3921            throw new CmsDbException(message, exc);
3922        }
3923
3924    }
3925
3926    /**
3927     * Reads all access control entries.<p>
3928     *
3929     * @param dbc the current database context
3930     * @return all access control entries for the current project (offline/online)
3931     *
3932     * @throws CmsException if something goes wrong
3933     */
3934    public List<CmsAccessControlEntry> getAllAccessControlEntries(CmsDbContext dbc) throws CmsException {
3935
3936        I_CmsUserDriver userDriver = getUserDriver(dbc);
3937        List<CmsAccessControlEntry> ace = userDriver.readAccessControlEntries(
3938            dbc,
3939            dbc.currentProject(),
3940            CmsAccessControlEntry.PRINCIPAL_READALL_ID,
3941            false);
3942        return ace;
3943    }
3944
3945    /**
3946     * Returns all projects which are owned by the current user or which are
3947     * accessible by the current user.<p>
3948     *
3949     * @param dbc the current database context
3950     * @param orgUnit the organizational unit to search project in
3951     * @param includeSubOus if to include sub organizational units
3952     *
3953     * @return a list of objects of type <code>{@link CmsProject}</code>
3954     *
3955     * @throws CmsException if something goes wrong
3956     */
3957    public List<CmsProject> getAllAccessibleProjects(
3958        CmsDbContext dbc,
3959        CmsOrganizationalUnit orgUnit,
3960        boolean includeSubOus)
3961    throws CmsException {
3962
3963        Set<CmsProject> projects = new HashSet<CmsProject>();
3964
3965        // get the ous where the user has the project manager role
3966        List<CmsOrganizationalUnit> ous = getOrgUnitsForRole(
3967            dbc,
3968            CmsRole.PROJECT_MANAGER.forOrgUnit(orgUnit.getName()),
3969            includeSubOus);
3970
3971        // get the groups of the user if needed
3972        Set<CmsUUID> userGroupIds = new HashSet<CmsUUID>();
3973        Iterator<CmsGroup> itGroups = getGroupsOfUser(dbc, dbc.currentUser().getName(), false).iterator();
3974        while (itGroups.hasNext()) {
3975            CmsGroup group = itGroups.next();
3976            userGroupIds.add(group.getId());
3977        }
3978
3979        // TODO: this could be optimize if this method would have an additional parameter 'includeSubOus'
3980        // get all projects that might come in question
3981        projects.addAll(getProjectDriver(dbc).readProjects(dbc, orgUnit.getName()));
3982
3983        // filter hidden and not accessible projects
3984        Iterator<CmsProject> itProjects = projects.iterator();
3985        while (itProjects.hasNext()) {
3986            CmsProject project = itProjects.next();
3987            boolean accessible = true;
3988            // if hidden
3989            accessible = accessible && !project.isHidden();
3990
3991            if (!includeSubOus) {
3992                // if not exact in the given ou
3993                accessible = accessible && project.getOuFqn().equals(orgUnit.getName());
3994            } else {
3995                // if not in the given ou
3996                accessible = accessible && project.getOuFqn().startsWith(orgUnit.getName());
3997            }
3998
3999            if (!accessible) {
4000                itProjects.remove();
4001                continue;
4002            }
4003
4004            accessible = false;
4005            // online project
4006            accessible = accessible || project.isOnlineProject();
4007            // if owner
4008            accessible = accessible || project.getOwnerId().equals(dbc.currentUser().getId());
4009
4010            // project managers
4011            Iterator<CmsOrganizationalUnit> itOus = ous.iterator();
4012            while (!accessible && itOus.hasNext()) {
4013                CmsOrganizationalUnit ou = itOus.next();
4014                // for project managers check visibility
4015                accessible = accessible || project.getOuFqn().startsWith(ou.getName());
4016            }
4017
4018            if (!accessible) {
4019                // if direct user or manager of project
4020                CmsUUID groupId = null;
4021                if (userGroupIds.contains(project.getGroupId())) {
4022                    groupId = project.getGroupId();
4023                } else if (userGroupIds.contains(project.getManagerGroupId())) {
4024                    groupId = project.getManagerGroupId();
4025                }
4026                if (groupId != null) {
4027                    String oufqn = readGroup(dbc, groupId).getOuFqn();
4028                    accessible = accessible || (oufqn.startsWith(dbc.getRequestContext().getOuFqn()));
4029                }
4030            }
4031            if (!accessible) {
4032                // remove not accessible project
4033                itProjects.remove();
4034            }
4035        }
4036
4037        List<CmsProject> accessibleProjects = new ArrayList<CmsProject>(projects);
4038        // sort the list of projects based on the project name
4039        Collections.sort(accessibleProjects);
4040        // ensure the online project is in first place
4041        CmsProject onlineProject = readProject(dbc, CmsProject.ONLINE_PROJECT_ID);
4042        if (accessibleProjects.contains(onlineProject)) {
4043            accessibleProjects.remove(onlineProject);
4044        }
4045        accessibleProjects.add(0, onlineProject);
4046
4047        return accessibleProjects;
4048    }
4049
4050    /**
4051     * Returns a list with all projects from history.<p>
4052     *
4053     * @param dbc the current database context
4054     *
4055     * @return list of <code>{@link CmsHistoryProject}</code> objects
4056     *           with all projects from history.
4057     *
4058     * @throws CmsException if operation was not successful
4059     */
4060    public List<CmsHistoryProject> getAllHistoricalProjects(CmsDbContext dbc) throws CmsException {
4061
4062        // user is allowed to access all existing projects for the ous he has the project_manager role
4063        Set<CmsOrganizationalUnit> manOus = new HashSet<CmsOrganizationalUnit>(
4064            getOrgUnitsForRole(dbc, CmsRole.PROJECT_MANAGER, true));
4065
4066        List<CmsHistoryProject> projects = getHistoryDriver(dbc).readProjects(dbc);
4067        Iterator<CmsHistoryProject> itProjects = projects.iterator();
4068        while (itProjects.hasNext()) {
4069            CmsHistoryProject project = itProjects.next();
4070            if (project.isHidden()) {
4071                // project is hidden
4072                itProjects.remove();
4073                continue;
4074            }
4075            if (!project.getOuFqn().startsWith(dbc.currentUser().getOuFqn())) {
4076                // project is not visible from the users ou
4077                itProjects.remove();
4078                continue;
4079            }
4080            CmsOrganizationalUnit ou = readOrganizationalUnit(dbc, project.getOuFqn());
4081            if (manOus.contains(ou)) {
4082                // user is project manager for this project
4083                continue;
4084            } else if (project.getOwnerId().equals(dbc.currentUser().getId())) {
4085                // user is owner of the project
4086                continue;
4087            } else {
4088                boolean found = false;
4089                Iterator<CmsGroup> itGroups = getGroupsOfUser(dbc, dbc.currentUser().getName(), false).iterator();
4090                while (itGroups.hasNext()) {
4091                    CmsGroup group = itGroups.next();
4092                    if (project.getManagerGroupId().equals(group.getId())) {
4093                        found = true;
4094                        break;
4095                    }
4096                }
4097                if (found) {
4098                    // user is member of the manager group of the project
4099                    continue;
4100                }
4101            }
4102            itProjects.remove();
4103        }
4104        return projects;
4105    }
4106
4107    /**
4108     * Returns all projects which are owned by the current user or which are manageable
4109     * for the group of the user.<p>
4110     *
4111     * @param dbc the current database context
4112     * @param orgUnit the organizational unit to search project in
4113     * @param includeSubOus if to include sub organizational units
4114     *
4115     * @return a list of objects of type <code>{@link CmsProject}</code>
4116     *
4117     * @throws CmsException if operation was not successful
4118     */
4119    public List<CmsProject> getAllManageableProjects(
4120        CmsDbContext dbc,
4121        CmsOrganizationalUnit orgUnit,
4122        boolean includeSubOus)
4123    throws CmsException {
4124
4125        Set<CmsProject> projects = new HashSet<CmsProject>();
4126
4127        // get the ous where the user has the project manager role
4128        List<CmsOrganizationalUnit> ous = getOrgUnitsForRole(
4129            dbc,
4130            CmsRole.PROJECT_MANAGER.forOrgUnit(orgUnit.getName()),
4131            includeSubOus);
4132
4133        // get the groups of the user if needed
4134        Set<CmsUUID> userGroupIds = new HashSet<CmsUUID>();
4135        Iterator<CmsGroup> itGroups = getGroupsOfUser(dbc, dbc.currentUser().getName(), false).iterator();
4136        while (itGroups.hasNext()) {
4137            CmsGroup group = itGroups.next();
4138            userGroupIds.add(group.getId());
4139        }
4140
4141        // TODO: this could be optimize if this method would have an additional parameter 'includeSubOus'
4142        // get all projects that might come in question
4143        projects.addAll(getProjectDriver(dbc).readProjects(dbc, orgUnit.getName()));
4144
4145        // filter hidden and not manageable projects
4146        Iterator<CmsProject> itProjects = projects.iterator();
4147        while (itProjects.hasNext()) {
4148            CmsProject project = itProjects.next();
4149            boolean manageable = true;
4150            // if online
4151            manageable = manageable && !project.isOnlineProject();
4152            // if hidden
4153            manageable = manageable && !project.isHidden();
4154
4155            if (!includeSubOus) {
4156                // if not exact in the given ou
4157                manageable = manageable && project.getOuFqn().equals(orgUnit.getName());
4158            } else {
4159                // if not in the given ou
4160                manageable = manageable && project.getOuFqn().startsWith(orgUnit.getName());
4161            }
4162
4163            if (!manageable) {
4164                itProjects.remove();
4165                continue;
4166            }
4167
4168            manageable = false;
4169            // if owner
4170            manageable = manageable || project.getOwnerId().equals(dbc.currentUser().getId());
4171
4172            // project managers
4173            Iterator<CmsOrganizationalUnit> itOus = ous.iterator();
4174            while (!manageable && itOus.hasNext()) {
4175                CmsOrganizationalUnit ou = itOus.next();
4176                // for project managers check visibility
4177                manageable = manageable || project.getOuFqn().startsWith(ou.getName());
4178            }
4179
4180            if (!manageable) {
4181                // if manager of project
4182                if (userGroupIds.contains(project.getManagerGroupId())) {
4183                    String oufqn = readGroup(dbc, project.getManagerGroupId()).getOuFqn();
4184                    manageable = manageable || (oufqn.startsWith(dbc.getRequestContext().getOuFqn()));
4185                }
4186            }
4187            if (!manageable) {
4188                // remove not accessible project
4189                itProjects.remove();
4190            }
4191        }
4192
4193        List<CmsProject> manageableProjects = new ArrayList<CmsProject>(projects);
4194        // sort the list of projects based on the project name
4195        Collections.sort(manageableProjects);
4196        // ensure the online project is not in the list
4197        CmsProject onlineProject = readProject(dbc, CmsProject.ONLINE_PROJECT_ID);
4198        if (manageableProjects.contains(onlineProject)) {
4199            manageableProjects.remove(onlineProject);
4200        }
4201
4202        return manageableProjects;
4203    }
4204
4205    /**
4206     * Returns all child groups of a group.<p>
4207     *
4208     * @param dbc the current database context
4209     * @param group the group to get the child for
4210     * @param includeSubChildren if set also returns all sub-child groups of the given group
4211     *
4212     * @return a list of all child <code>{@link CmsGroup}</code> objects
4213     *
4214     * @throws CmsException if operation was not successful
4215     */
4216    public List<CmsGroup> getChildren(CmsDbContext dbc, CmsGroup group, boolean includeSubChildren)
4217    throws CmsException {
4218
4219        if (!includeSubChildren) {
4220            return getUserDriver(dbc).readChildGroups(dbc, group.getName());
4221        }
4222        Set<CmsGroup> allChildren = new TreeSet<CmsGroup>();
4223        // iterate all child groups
4224        Iterator<CmsGroup> it = getUserDriver(dbc).readChildGroups(dbc, group.getName()).iterator();
4225        while (it.hasNext()) {
4226            CmsGroup child = it.next();
4227            // add the group itself
4228            allChildren.add(child);
4229            // now get all sub-children for each group
4230            allChildren.addAll(getChildren(dbc, child, true));
4231        }
4232        return new ArrayList<CmsGroup>(allChildren);
4233    }
4234
4235    /**
4236     * Returns the date when the resource was last visited by the user.<p>
4237     *
4238     * @param dbc the database context
4239     * @param poolName the name of the database pool to use
4240     * @param user the user to check the date
4241     * @param resource the resource to check the date
4242     *
4243     * @return the date when the resource was last visited by the user
4244     *
4245     * @throws CmsException if something goes wrong
4246     */
4247    public long getDateLastVisitedBy(CmsDbContext dbc, String poolName, CmsUser user, CmsResource resource)
4248    throws CmsException {
4249
4250        return m_subscriptionDriver.getDateLastVisitedBy(dbc, poolName, user, resource);
4251    }
4252
4253    /**
4254     * Returns all groups of the given organizational unit.<p>
4255     *
4256     * @param dbc the current db context
4257     * @param orgUnit the organizational unit to get the groups for
4258     * @param includeSubOus if all groups of sub-organizational units should be retrieved too
4259     * @param readRoles if to read roles or groups
4260     *
4261     * @return all <code>{@link CmsGroup}</code> objects in the organizational unit
4262     *
4263     * @throws CmsException if operation was not successful
4264     *
4265     * @see org.opencms.security.CmsOrgUnitManager#getResourcesForOrganizationalUnit(CmsObject, String)
4266     * @see org.opencms.security.CmsOrgUnitManager#getGroups(CmsObject, String, boolean)
4267     */
4268    public List<CmsGroup> getGroups(
4269        CmsDbContext dbc,
4270        CmsOrganizationalUnit orgUnit,
4271        boolean includeSubOus,
4272        boolean readRoles)
4273    throws CmsException {
4274
4275        return getUserDriver(dbc).getGroups(dbc, orgUnit, includeSubOus, readRoles);
4276    }
4277
4278    /**
4279     * Returns the groups of an user filtered by the specified IP address.<p>
4280     *
4281     * @param dbc the current database context
4282     * @param username the name of the user
4283     * @param readRoles if to read roles or groups
4284     *
4285     * @return the groups of the given user, as a list of {@link CmsGroup} objects
4286     *
4287     * @throws CmsException if something goes wrong
4288     */
4289    public List<CmsGroup> getGroupsOfUser(CmsDbContext dbc, String username, boolean readRoles) throws CmsException {
4290
4291        return getGroupsOfUser(dbc, username, "", true, readRoles, false, dbc.getRequestContext().getRemoteAddress());
4292    }
4293
4294    /**
4295     * Returns the groups of an user filtered by the specified IP address.<p>
4296     *
4297     * @param dbc the current database context
4298     * @param username the name of the user
4299     * @param ouFqn the fully qualified name of the organizational unit to restrict the result set for
4300     * @param includeChildOus include groups of child organizational units
4301     * @param readRoles if to read roles or groups
4302     * @param directGroupsOnly if set only the direct assigned groups will be returned, if not also indirect groups
4303     * @param remoteAddress the IP address to filter the groups in the result list
4304     *
4305     * @return a list of <code>{@link CmsGroup}</code> objects
4306     *
4307     * @throws CmsException if operation was not successful
4308     */
4309    public List<CmsGroup> getGroupsOfUser(
4310        CmsDbContext dbc,
4311        String username,
4312        String ouFqn,
4313        boolean includeChildOus,
4314        boolean readRoles,
4315        boolean directGroupsOnly,
4316        String remoteAddress)
4317    throws CmsException {
4318
4319        CmsUser user = readUser(dbc, username);
4320        String prefix = ouFqn + "_" + includeChildOus + "_" + directGroupsOnly + "_" + readRoles + "_" + remoteAddress;
4321        String cacheKey = m_keyGenerator.getCacheKeyForUserGroups(prefix, dbc, user);
4322        List<CmsGroup> groups = m_monitor.getCachedUserGroups(user.getId(), cacheKey);
4323        if (groups == null) {
4324            // get all groups of the user
4325            List<CmsGroup> directGroups = getUserDriver(dbc).readGroupsOfUser(
4326                dbc,
4327                user.getId(),
4328                readRoles ? "" : ouFqn,
4329                readRoles ? true : includeChildOus,
4330                remoteAddress,
4331                readRoles);
4332            Set<CmsGroup> allGroups = new HashSet<CmsGroup>();
4333            if (!readRoles) {
4334                allGroups.addAll(directGroups);
4335            }
4336            if (!directGroupsOnly) {
4337                if (!readRoles) {
4338                    // now get all parents of the groups
4339                    for (int i = 0; i < directGroups.size(); i++) {
4340                        CmsGroup parent = getParent(dbc, directGroups.get(i).getName());
4341                        while ((parent != null) && (!allGroups.contains(parent))) {
4342                            if (parent.getOuFqn().startsWith(ouFqn)) {
4343                                allGroups.add(parent);
4344                            }
4345                            // read next parent group
4346                            parent = getParent(dbc, parent.getName());
4347                        }
4348                    }
4349                }
4350            }
4351            if (readRoles) {
4352                // for each for role
4353                for (int i = 0; i < directGroups.size(); i++) {
4354                    CmsGroup group = directGroups.get(i);
4355                    CmsRole role = CmsRole.valueOf(group);
4356                    if (!includeChildOus && role.getOuFqn().equals(ouFqn)) {
4357                        allGroups.add(group);
4358                    }
4359                    if (includeChildOus && role.getOuFqn().startsWith(ouFqn)) {
4360                        allGroups.add(group);
4361                    }
4362                    if (directGroupsOnly || (!includeChildOus && !role.getOuFqn().equals(ouFqn))) {
4363                        // if roles of child OUs are not requested and the role does not belong to the requested OU don't include the role children
4364                        continue;
4365                    }
4366                    CmsOrganizationalUnit currentOu = readOrganizationalUnit(dbc, group.getOuFqn());
4367                    boolean readChildRoleGroups = true;
4368                    if (currentOu.hasFlagWebuser() && role.forOrgUnit(null).equals(CmsRole.ACCOUNT_MANAGER)) {
4369                        readChildRoleGroups = false;
4370                    }
4371                    if (readChildRoleGroups) {
4372                        // get the child roles
4373                        Iterator<CmsRole> itChildRoles = role.getChildren(true).iterator();
4374                        while (itChildRoles.hasNext()) {
4375                            CmsRole childRole = itChildRoles.next();
4376                            if (childRole.isSystemRole()) {
4377                                if (canReadRoleInOu(currentOu, childRole)) {
4378                                    // include system roles only
4379                                    try {
4380                                        allGroups.add(readGroup(dbc, childRole.getGroupName()));
4381                                    } catch (CmsDataAccessException e) {
4382                                        // should not happen, log error if it does
4383                                        LOG.error(e.getLocalizedMessage(), e);
4384                                    }
4385                                }
4386                            }
4387                        }
4388                    } else {
4389                        LOG.info("Skipping child role group check for web user OU " + currentOu.getName());
4390                    }
4391                    if (includeChildOus) {
4392                        // if needed include the roles of child ous
4393                        Iterator<CmsOrganizationalUnit> itSubOus = getOrganizationalUnits(
4394                            dbc,
4395                            readOrganizationalUnit(dbc, group.getOuFqn()),
4396                            true).iterator();
4397                        while (itSubOus.hasNext()) {
4398                            CmsOrganizationalUnit subOu = itSubOus.next();
4399                            // add role in child ou
4400                            try {
4401                                if (canReadRoleInOu(subOu, role)) {
4402                                    allGroups.add(readGroup(dbc, role.forOrgUnit(subOu.getName()).getGroupName()));
4403                                }
4404                            } catch (CmsDbEntryNotFoundException e) {
4405                                // ignore, this may happen while deleting an orgunit
4406                                if (LOG.isDebugEnabled()) {
4407                                    LOG.debug(e.getLocalizedMessage(), e);
4408                                }
4409                            }
4410                            // add child roles in child ous
4411                            Iterator<CmsRole> itChildRoles = role.getChildren(true).iterator();
4412                            while (itChildRoles.hasNext()) {
4413                                CmsRole childRole = itChildRoles.next();
4414                                try {
4415                                    if (canReadRoleInOu(subOu, childRole)) {
4416                                        allGroups.add(
4417                                            readGroup(dbc, childRole.forOrgUnit(subOu.getName()).getGroupName()));
4418                                    }
4419                                } catch (CmsDbEntryNotFoundException e) {
4420                                    // ignore, this may happen while deleting an orgunit
4421                                    if (LOG.isDebugEnabled()) {
4422                                        LOG.debug(e.getLocalizedMessage(), e);
4423                                    }
4424                                }
4425                            }
4426                        }
4427                    }
4428                }
4429            }
4430            // make group list unmodifiable for caching
4431            groups = Collections.unmodifiableList(new ArrayList<CmsGroup>(allGroups));
4432            if (dbc.getProjectId().isNullUUID()) {
4433                m_monitor.getGroupListCache().setGroups(user, cacheKey, groups);
4434            }
4435        }
4436
4437        return groups;
4438    }
4439
4440    /**
4441     * Returns the history driver.<p>
4442     *
4443     * @return the history driver
4444     */
4445    public I_CmsHistoryDriver getHistoryDriver() {
4446
4447        return m_historyDriver;
4448    }
4449
4450    /**
4451     * Returns the history driver for a given database context.<p>
4452     *
4453     * @param dbc the database context
4454     * @return the history driver for the database context
4455     */
4456    public I_CmsHistoryDriver getHistoryDriver(CmsDbContext dbc) {
4457
4458        if ((dbc == null) || (dbc.getProjectId() == null) || dbc.getProjectId().isNullUUID()) {
4459            return m_historyDriver;
4460        }
4461        I_CmsHistoryDriver driver = dbc.getHistoryDriver(dbc.getProjectId());
4462        return driver != null ? driver : m_historyDriver;
4463
4464    }
4465
4466    /**
4467     * Returns the number of idle connections managed by a pool.<p>
4468     *
4469     * @param dbPoolUrl the url of a pool
4470     * @return the number of idle connections
4471     * @throws CmsDbException if something goes wrong
4472     */
4473    public int getIdleConnections(String dbPoolUrl) throws CmsDbException {
4474
4475        CmsDbPoolV11 pool = m_pools.get(dbPoolUrl);
4476        if (pool == null) {
4477            CmsMessageContainer message = Messages.get().container(Messages.ERR_UNKNOWN_POOL_URL_1, dbPoolUrl);
4478            throw new CmsDbException(message);
4479        }
4480        try {
4481            return pool.getIdleConnections();
4482        } catch (Exception exc) {
4483            CmsMessageContainer message = Messages.get().container(Messages.ERR_ACCESSING_POOL_1, dbPoolUrl);
4484            throw new CmsDbException(message, exc);
4485        }
4486
4487    }
4488
4489    /**
4490     * Returns the lock state of a resource.<p>
4491     *
4492     * @param dbc the current database context
4493     * @param resource the resource to return the lock state for
4494     *
4495     * @return the lock state of the resource
4496     *
4497     * @throws CmsException if something goes wrong
4498     */
4499    public CmsLock getLock(CmsDbContext dbc, CmsResource resource) throws CmsException {
4500
4501        return m_lockManager.getLock(dbc, resource);
4502    }
4503
4504    /**
4505     * Returns all locked resources in a given folder.<p>
4506     *
4507     * @param dbc the current database context
4508     * @param resource the folder to search in
4509     * @param filter the lock filter
4510     *
4511     * @return a list of locked resource paths (relative to current site)
4512     *
4513     * @throws CmsException if the current project is locked
4514     */
4515    public List<String> getLockedResources(CmsDbContext dbc, CmsResource resource, CmsLockFilter filter)
4516    throws CmsException {
4517
4518        List<String> lockedResources = new ArrayList<String>();
4519        // get locked resources
4520        Iterator<CmsLock> it = m_lockManager.getLocks(dbc, resource.getRootPath(), filter).iterator();
4521        while (it.hasNext()) {
4522            CmsLock lock = it.next();
4523            lockedResources.add(dbc.removeSiteRoot(lock.getResourceName()));
4524        }
4525        Collections.sort(lockedResources);
4526        return lockedResources;
4527    }
4528
4529    /**
4530     * Returns all locked resources in a given folder.<p>
4531     *
4532     * @param dbc the current database context
4533     * @param resource the folder to search in
4534     * @param filter the lock filter
4535     *
4536     * @return a list of locked resources
4537     *
4538     * @throws CmsException if the current project is locked
4539     */
4540    public List<CmsResource> getLockedResourcesObjects(CmsDbContext dbc, CmsResource resource, CmsLockFilter filter)
4541    throws CmsException {
4542
4543        return m_lockManager.getLockedResources(dbc, resource, filter);
4544    }
4545
4546    /**
4547     * Returns all locked resources in a given folder, but uses a cache for resource lookups.<p>
4548     *
4549     * @param dbc the current database context
4550     * @param resource the folder to search in
4551     * @param filter the lock filter
4552     * @param cache the cache to use for resource lookups
4553     *
4554     * @return a list of locked resources
4555     *
4556     * @throws CmsException if the current project is locked
4557     */
4558    public List<CmsResource> getLockedResourcesObjectsWithCache(
4559        CmsDbContext dbc,
4560        CmsResource resource,
4561        CmsLockFilter filter,
4562        Map<String, CmsResource> cache)
4563    throws CmsException {
4564
4565        return m_lockManager.getLockedResourcesWithCache(dbc, resource, filter, cache);
4566    }
4567
4568    /**
4569     * Returns all log entries matching the given filter.<p>
4570     *
4571     * @param dbc the current db context
4572     * @param filter the filter to match the log entries
4573     *
4574     * @return all log entries matching the given filter
4575     *
4576     * @throws CmsException if something goes wrong
4577     *
4578     * @see CmsSecurityManager#getLogEntries(CmsRequestContext, CmsLogFilter)
4579     */
4580    public List<CmsLogEntry> getLogEntries(CmsDbContext dbc, CmsLogFilter filter) throws CmsException {
4581
4582        updateLog(dbc);
4583        return m_projectDriver.readLog(dbc, filter);
4584    }
4585
4586    /**
4587     * Returns the next publish tag for the published historical resources.<p>
4588     *
4589     * @param dbc the current database context
4590     *
4591     * @return the next available publish tag
4592     */
4593    public int getNextPublishTag(CmsDbContext dbc) {
4594
4595        if (dbc.getProjectId().isNullUUID()) {
4596            synchronized (m_publishTagLock) {
4597                int dbNextPublishTag = getHistoryDriver(dbc).readNextPublishTag(dbc);
4598                if (m_lastPublishTag == null) {
4599                    m_lastPublishTag = Integer.valueOf(dbNextPublishTag);
4600                    return dbNextPublishTag;
4601                } else {
4602                    int newValue = Math.max(dbNextPublishTag, m_lastPublishTag.intValue() + 1);
4603                    m_lastPublishTag = Integer.valueOf(newValue);
4604                    return newValue;
4605
4606                }
4607            }
4608        } else {
4609            return getHistoryDriver(dbc).readNextPublishTag(dbc);
4610        }
4611
4612    }
4613
4614    /**
4615     * Returns all child organizational units of the given parent organizational unit including
4616     * hierarchical deeper organization units if needed.<p>
4617     *
4618     * @param dbc the current db context
4619     * @param parent the parent organizational unit, or <code>null</code> for the root
4620     * @param includeChildren if hierarchical deeper organization units should also be returned
4621     *
4622     * @return a list of <code>{@link CmsOrganizationalUnit}</code> objects
4623     *
4624     * @throws CmsException if operation was not successful
4625     *
4626     * @see org.opencms.security.CmsOrgUnitManager#getOrganizationalUnits(CmsObject, String, boolean)
4627     */
4628    public List<CmsOrganizationalUnit> getOrganizationalUnits(
4629        CmsDbContext dbc,
4630        CmsOrganizationalUnit parent,
4631        boolean includeChildren)
4632    throws CmsException {
4633
4634        if (parent == null) {
4635            throw new CmsIllegalArgumentException(Messages.get().container(Messages.ERR_PARENT_ORGUNIT_NULL_0));
4636        }
4637        return getUserDriver(dbc).getOrganizationalUnits(dbc, parent, includeChildren);
4638    }
4639
4640    /**
4641     * Returns all the organizational units for which the current user has the given role.<p>
4642     *
4643     * @param dbc the current database context
4644     * @param role the role to check
4645     * @param includeSubOus if sub organizational units should be included in the search
4646     *
4647     * @return a list of {@link org.opencms.security.CmsOrganizationalUnit} objects
4648     *
4649     * @throws CmsException if something goes wrong
4650     */
4651    public List<CmsOrganizationalUnit> getOrgUnitsForRole(CmsDbContext dbc, CmsRole role, boolean includeSubOus)
4652    throws CmsException {
4653
4654        String ouFqn = role.getOuFqn();
4655        if (ouFqn == null) {
4656            ouFqn = "";
4657            role = role.forOrgUnit("");
4658        }
4659        CmsOrganizationalUnit ou = readOrganizationalUnit(dbc, ouFqn);
4660        List<CmsOrganizationalUnit> orgUnits = new ArrayList<CmsOrganizationalUnit>();
4661        if (m_securityManager.hasRole(dbc, dbc.currentUser(), role)) {
4662            orgUnits.add(ou);
4663        }
4664        if (includeSubOus) {
4665            Iterator<CmsOrganizationalUnit> it = getOrganizationalUnits(dbc, ou, true).iterator();
4666            while (it.hasNext()) {
4667                CmsOrganizationalUnit orgUnit = it.next();
4668                if (m_securityManager.hasRole(dbc, dbc.currentUser(), role.forOrgUnit(orgUnit.getName()))) {
4669                    orgUnits.add(orgUnit);
4670                }
4671            }
4672        }
4673        return orgUnits;
4674    }
4675
4676    /**
4677     * Returns the parent group of a group.<p>
4678     *
4679     * @param dbc the current database context
4680     * @param groupname the name of the group
4681     *
4682     * @return group the parent group or <code>null</code>
4683     *
4684     * @throws CmsException if operation was not successful
4685     */
4686    public CmsGroup getParent(CmsDbContext dbc, String groupname) throws CmsException {
4687
4688        CmsGroup group = readGroup(dbc, groupname);
4689        if (group.getParentId().isNullUUID()) {
4690            return null;
4691        }
4692
4693        // try to read from cache
4694        CmsGroup parent = m_monitor.getCachedGroup(group.getParentId().toString());
4695        if (parent == null) {
4696            parent = getUserDriver(dbc).readGroup(dbc, group.getParentId());
4697            m_monitor.cacheGroup(parent);
4698        }
4699        return parent;
4700    }
4701
4702    /**
4703     * Returns the set of permissions of the current user for a given resource.<p>
4704     *
4705     * @param dbc the current database context
4706     * @param resource the resource
4707     * @param user the user
4708     *
4709     * @return bit set with allowed permissions
4710     *
4711     * @throws CmsException if something goes wrong
4712     */
4713    public CmsPermissionSetCustom getPermissions(CmsDbContext dbc, CmsResource resource, CmsUser user)
4714    throws CmsException {
4715
4716        CmsAccessControlList acList = getAccessControlList(dbc, resource, false);
4717        List<CmsGroup> groups = getGroupsOfUser(dbc, user.getName(), false);
4718        List<CmsRole> roles = getRolesForUser(dbc, user);
4719        CmsPermissionSetCustom permissions = acList.getPermissions(user, groups, roles);
4720
4721        if (acList.getExclusiveAccessPrincipals().size() > 0) {
4722            long now;
4723            @SuppressWarnings("unchecked")
4724            Supplier<Long> alternativeClock = (Supplier<Long>)(dbc.getRequestContext().getAttribute(
4725                ATTR_EXCLUSIVE_ACCESS_CLOCK));
4726            if (alternativeClock != null) {
4727                // used for testing
4728                now = alternativeClock.get().longValue();
4729            } else {
4730                // *NOT* using dbc.getRequestContext().getRequestTime(), even though that value is used for normal resource availability checks, because
4731                // that value may be manipulated by some workplace classes, and we want the real time for permission checks
4732                now = System.currentTimeMillis();
4733            }
4734            permissions.setCacheable(false); // resources going in/out of availability can change permissions - don't cache
4735            if (!resource.isReleasedAndNotExpired(now)) {
4736                boolean hasExclusiveAccess = false;
4737                for (CmsGroup group : groups) {
4738                    if (acList.getExclusiveAccessPrincipals().contains(group.getId())) {
4739                        hasExclusiveAccess = true;
4740                        break;
4741                    }
4742                }
4743                hasExclusiveAccess |= acList.getExclusiveAccessPrincipals().contains(user.getId());
4744                if (!hasExclusiveAccess) {
4745                    permissions.denyPermissions(CmsPermissionSet.PERMISSION_FULL);
4746                }
4747            }
4748        }
4749        return permissions;
4750    }
4751
4752    /**
4753     * Returns the project driver.<p>
4754     *
4755     * @return the project driver
4756     */
4757    public I_CmsProjectDriver getProjectDriver() {
4758
4759        return m_projectDriver;
4760    }
4761
4762    /**
4763     * Returns the project driver for a given DB context.<p>
4764     *
4765     * @param dbc the database context
4766     *
4767     * @return the project driver for the database context
4768     */
4769    public I_CmsProjectDriver getProjectDriver(CmsDbContext dbc) {
4770
4771        if ((dbc == null) || (dbc.getProjectId() == null) || dbc.getProjectId().isNullUUID()) {
4772            return m_projectDriver;
4773        }
4774        I_CmsProjectDriver driver = dbc.getProjectDriver(dbc.getProjectId());
4775        return driver != null ? driver : m_projectDriver;
4776    }
4777
4778    /**
4779     * Returns either the project driver for the DB context (if it has one) or a default project driver.<p>
4780     *
4781     * @param dbc the DB context
4782     * @param defaultDriver the driver which should be returned if there is no project driver for the DB context
4783     *
4784     * @return either the project driver for the DB context, or the default driver
4785     */
4786    public I_CmsProjectDriver getProjectDriver(CmsDbContext dbc, I_CmsProjectDriver defaultDriver) {
4787
4788        if ((dbc == null) || (dbc.getProjectId() == null) || dbc.getProjectId().isNullUUID()) {
4789            return defaultDriver;
4790        }
4791        I_CmsProjectDriver driver = dbc.getProjectDriver(dbc.getProjectId());
4792        return driver != null ? driver : defaultDriver;
4793    }
4794
4795    /**
4796     * Returns the uuid id for the given id.<p>
4797     *
4798     * TODO: remove this method as soon as possible
4799     *
4800     * @param dbc the current database context
4801     * @param id the old project id
4802     *
4803     * @return the new uuid for the given id
4804     *
4805     * @throws CmsException if something goes wrong
4806     */
4807    public CmsUUID getProjectId(CmsDbContext dbc, int id) throws CmsException {
4808
4809        Iterator<CmsProject> itProjects = getAllAccessibleProjects(
4810            dbc,
4811            readOrganizationalUnit(dbc, ""),
4812            true).iterator();
4813        while (itProjects.hasNext()) {
4814            CmsProject project = itProjects.next();
4815            if (project.getUuid().hashCode() == id) {
4816                return project.getUuid();
4817            }
4818        }
4819        return null;
4820    }
4821
4822    /**
4823     * Returns the configuration read from the <code>opencms.properties</code> file.<p>
4824     *
4825     * @return the configuration read from the <code>opencms.properties</code> file
4826     */
4827    public CmsParameterConfiguration getPropertyConfiguration() {
4828
4829        return m_propertyConfiguration;
4830    }
4831
4832    /**
4833     * Returns a new publish list that contains the unpublished resources related
4834     * to all resources in the given publish list, the related resources exclude
4835     * all resources in the given publish list and also locked (by other users) resources.<p>
4836     *
4837     * @param dbc the current database context
4838     * @param publishList the publish list to exclude from result
4839     * @param filter the relation filter to use to get the related resources
4840     *
4841     * @return a new publish list that contains the related resources
4842     *
4843     * @throws CmsException if something goes wrong
4844     *
4845     * @see org.opencms.publish.CmsPublishManager#getRelatedResourcesToPublish(CmsObject, CmsPublishList)
4846     */
4847    public CmsPublishList getRelatedResourcesToPublish(
4848        CmsDbContext dbc,
4849        CmsPublishList publishList,
4850        CmsRelationFilter filter)
4851    throws CmsException {
4852
4853        Map<String, CmsResource> relations = new HashMap<String, CmsResource>();
4854
4855        // check if progress should be set in the thread
4856        A_CmsProgressThread thread = null;
4857        if (Thread.currentThread() instanceof A_CmsProgressThread) {
4858            thread = (A_CmsProgressThread)Thread.currentThread();
4859        }
4860
4861        // get all resources to publish
4862        List<CmsResource> publishResources = publishList.getAllResources();
4863        Iterator<CmsResource> itCheckList = publishResources.iterator();
4864        // iterate over them
4865        int count = 0;
4866        while (itCheckList.hasNext()) {
4867
4868            // set progress in thread
4869            count++;
4870            if (thread != null) {
4871
4872                if (thread.isInterrupted()) {
4873                    throw new CmsIllegalStateException(
4874                        org.opencms.workplace.commons.Messages.get().container(
4875                            org.opencms.workplace.commons.Messages.ERR_PROGRESS_INTERRUPTED_0));
4876                }
4877                thread.setProgress((count * 20) / publishResources.size());
4878                thread.setDescription(
4879                    org.opencms.workplace.commons.Messages.get().getBundle().key(
4880                        org.opencms.workplace.commons.Messages.GUI_PROGRESS_PUBLISH_STEP1_2,
4881                        Integer.valueOf(count),
4882                        Integer.valueOf(publishResources.size())));
4883            }
4884
4885            CmsResource checkResource = itCheckList.next();
4886            // get and iterate over all related resources
4887            Iterator<CmsRelation> itRelations = getRelationsForResource(dbc, checkResource, filter).iterator();
4888            while (itRelations.hasNext()) {
4889                CmsRelation relation = itRelations.next();
4890                try {
4891                    // get the target of the relation, see CmsRelation#getTarget(CmsObject, CmsResourceFilter)
4892                    CmsResource target;
4893                    try {
4894                        // first look up by id
4895                        target = readResource(dbc, relation.getTargetId(), CmsResourceFilter.ALL);
4896                    } catch (CmsVfsResourceNotFoundException e) {
4897                        // then look up by name, but from the root site
4898                        String storedSiteRoot = dbc.getRequestContext().getSiteRoot();
4899                        try {
4900                            dbc.getRequestContext().setSiteRoot("");
4901                            target = readResource(dbc, relation.getTargetPath(), CmsResourceFilter.ALL);
4902                        } finally {
4903                            dbc.getRequestContext().setSiteRoot(storedSiteRoot);
4904                        }
4905                    }
4906                    CmsLock lock = getLock(dbc, target);
4907                    // just add resources that may come in question
4908                    if (!publishResources.contains(target) // is not in the original list
4909                        && !relations.containsKey(target.getRootPath()) // has not been already added by another relation
4910                        && !target.getState().isUnchanged() // has been changed
4911                        && lock.isLockableBy(dbc.currentUser())) { // is lockable by current user
4912
4913                        relations.put(target.getRootPath(), target);
4914                        // now check the folder structure
4915                        CmsResource parent = getVfsDriver(dbc).readParentFolder(
4916                            dbc,
4917                            dbc.currentProject().getUuid(),
4918                            target.getStructureId());
4919                        while ((parent != null) && parent.getState().isNew()) {
4920                            // just add resources that may come in question
4921                            if (!publishResources.contains(parent) // is not in the original list
4922                                && !relations.containsKey(parent.getRootPath())) { // has not been already added by another relation
4923
4924                                relations.put(parent.getRootPath(), parent);
4925                            }
4926                            parent = getVfsDriver(dbc).readParentFolder(
4927                                dbc,
4928                                dbc.currentProject().getUuid(),
4929                                parent.getStructureId());
4930                        }
4931                    }
4932                } catch (CmsVfsResourceNotFoundException e) {
4933                    // ignore broken links
4934                    if (LOG.isDebugEnabled()) {
4935                        LOG.debug(e.getLocalizedMessage(), e);
4936                    }
4937                }
4938            }
4939        }
4940
4941        CmsPublishList ret = new CmsPublishList(publishList.getDirectPublishResources(), false, false);
4942        ret.addAll(relations.values(), false);
4943        ret.initialize();
4944        return ret;
4945    }
4946
4947    /**
4948     * Returns all relations for the given resource matching the given filter.<p>
4949     *
4950     * @param dbc the current db context
4951     * @param resource the resource to retrieve the relations for
4952     * @param filter the filter to match the relation
4953     *
4954     * @return all relations for the given resource matching the given filter
4955     *
4956     * @throws CmsException if something goes wrong
4957     *
4958     * @see CmsSecurityManager#getRelationsForResource(CmsRequestContext, CmsResource, CmsRelationFilter)
4959     */
4960    public List<CmsRelation> getRelationsForResource(CmsDbContext dbc, CmsResource resource, CmsRelationFilter filter)
4961    throws CmsException {
4962
4963        CmsUUID projectId = getProjectIdForContext(dbc);
4964        return getVfsDriver(dbc).readRelations(dbc, projectId, resource, filter);
4965    }
4966
4967    /**
4968     * Returns the list of organizational units the given resource belongs to.<p>
4969     *
4970     * @param dbc the current database context
4971     * @param resource the resource
4972     *
4973     * @return list of {@link CmsOrganizationalUnit} objects
4974     *
4975     * @throws CmsException if something goes wrong
4976     */
4977    public List<CmsOrganizationalUnit> getResourceOrgUnits(CmsDbContext dbc, CmsResource resource) throws CmsException {
4978
4979        boolean nullDbcProjectId = (dbc.getProjectId() == null) || dbc.getProjectId().isNullUUID();
4980        if (nullDbcProjectId && resourceOrgUnitCachingEnabled) {
4981            try {
4982                return m_monitor.getResourceOuCache().get(new ResourceOUCacheKey(this, dbc)).getResourceOrgUnits(
4983                    resource.getRootPath());
4984            } catch (ExecutionException e) {
4985                LOG.error(e.getLocalizedMessage(), e);
4986            }
4987        }
4988        List<CmsOrganizationalUnit> result = getVfsDriver(dbc).getResourceOus(
4989            dbc,
4990            dbc.currentProject().getUuid(),
4991            resource);
4992
4993        return result;
4994    }
4995
4996    /**
4997     * Returns all resources of the given organizational unit.<p>
4998     *
4999     * @param dbc the current db context
5000     * @param orgUnit the organizational unit to get all resources for
5001     *
5002     * @return all <code>{@link CmsResource}</code> objects in the organizational unit
5003     *
5004     * @throws CmsException if operation was not successful
5005     *
5006     * @see org.opencms.security.CmsOrgUnitManager#getResourcesForOrganizationalUnit(CmsObject, String)
5007     * @see org.opencms.security.CmsOrgUnitManager#getUsers(CmsObject, String, boolean)
5008     * @see org.opencms.security.CmsOrgUnitManager#getGroups(CmsObject, String, boolean)
5009     */
5010    public List<CmsResource> getResourcesForOrganizationalUnit(CmsDbContext dbc, CmsOrganizationalUnit orgUnit)
5011    throws CmsException {
5012
5013        return getUserDriver(dbc).getResourcesForOrganizationalUnit(dbc, orgUnit);
5014    }
5015
5016    /**
5017     * Returns all resources associated to a given principal via an ACE with the given permissions.<p>
5018     *
5019     * If the <code>includeAttr</code> flag is set it returns also all resources associated to
5020     * a given principal through some of following attributes.<p>
5021     *
5022     * <ul>
5023     *    <li>User Created</li>
5024     *    <li>User Last Modified</li>
5025     * </ul><p>
5026     *
5027     * @param dbc the current database context
5028     * @param project the to read the entries from
5029     * @param principalId the id of the principal
5030     * @param permissions a set of permissions to match, can be <code>null</code> for all ACEs
5031     * @param includeAttr a flag to include resources associated by attributes
5032     *
5033     * @return a set of <code>{@link CmsResource}</code> objects
5034     *
5035     * @throws CmsException if something goes wrong
5036     */
5037    public Set<CmsResource> getResourcesForPrincipal(
5038        CmsDbContext dbc,
5039        CmsProject project,
5040        CmsUUID principalId,
5041        CmsPermissionSet permissions,
5042        boolean includeAttr)
5043    throws CmsException {
5044
5045        Set<CmsResource> resources = new HashSet<CmsResource>(
5046            getVfsDriver(dbc).readResourcesForPrincipalACE(dbc, project, principalId));
5047        if (permissions != null) {
5048            Iterator<CmsResource> itRes = resources.iterator();
5049            while (itRes.hasNext()) {
5050                CmsAccessControlEntry ace = readAccessControlEntry(dbc, itRes.next(), principalId);
5051                if ((ace.getPermissions().getPermissions()
5052                    & permissions.getPermissions()) != permissions.getPermissions()) {
5053                    // remove if permissions does not match
5054                    itRes.remove();
5055                }
5056            }
5057        }
5058        if (includeAttr) {
5059            resources.addAll(getVfsDriver(dbc).readResourcesForPrincipalAttr(dbc, project, principalId));
5060        }
5061        return resources;
5062    }
5063
5064    /**
5065     * Gets the rewrite aliases matching a given filter.<p>
5066     *
5067     * @param dbc the current database context
5068     * @param filter the filter used for filtering rewrite aliases
5069     *
5070     * @return the rewrite aliases matching the given filter
5071     *
5072     * @throws CmsException if something goes wrong
5073     */
5074    public List<CmsRewriteAlias> getRewriteAliases(CmsDbContext dbc, CmsRewriteAliasFilter filter) throws CmsException {
5075
5076        return getVfsDriver(dbc).readRewriteAliases(dbc, filter);
5077    }
5078
5079    /**
5080     * Collects the groups which constitute a given role.<p>
5081     *
5082     * @param dbc the database context
5083     * @param roleGroupName the group related to the role
5084     * @param directUsersOnly if true, only the group belonging to the entry itself wil
5085     *
5086     * @return the set of groups which constitute the role
5087     *
5088     * @throws CmsException if something goes wrong
5089     */
5090    public Set<CmsGroup> getRoleGroups(CmsDbContext dbc, String roleGroupName, boolean directUsersOnly)
5091    throws CmsException {
5092
5093        return getRoleGroupsImpl(dbc, roleGroupName, directUsersOnly, new HashMap<String, Set<CmsGroup>>());
5094    }
5095
5096    /**
5097     * Collects the groups which constitute a given role.<p>
5098     *
5099     * @param dbc the database context
5100     * @param roleGroupName the group related to the role
5101     * @param directUsersOnly if true, only the group belonging to the entry itself wil
5102     * @param accumulator a map for memoizing return values of recursive calls
5103     *
5104     * @return the set of groups which constitute the role
5105     *
5106     * @throws CmsException if something goes wrong
5107     */
5108    public Set<CmsGroup> getRoleGroupsImpl(
5109        CmsDbContext dbc,
5110        String roleGroupName,
5111        boolean directUsersOnly,
5112        Map<String, Set<CmsGroup>> accumulator)
5113    throws CmsException {
5114
5115        Set<CmsGroup> result = new HashSet<CmsGroup>();
5116        if (accumulator.get(roleGroupName) != null) {
5117            return accumulator.get(roleGroupName);
5118        }
5119        CmsGroup group = readGroup(dbc, roleGroupName); // check that the group really exists
5120        if ((group == null) || (!group.isRole())) {
5121            throw new CmsDbEntryNotFoundException(
5122                Messages.get().container(Messages.ERR_UNKNOWN_GROUP_1, roleGroupName));
5123        }
5124        result.add(group);
5125        if (!directUsersOnly) {
5126            CmsRole role = CmsRole.valueOf(group);
5127            if (role.getParentRole() != null) {
5128                try {
5129                    String parentGroup = role.getParentRole().getGroupName();
5130                    // iterate the parent roles
5131                    result.addAll(getRoleGroupsImpl(dbc, parentGroup, directUsersOnly, accumulator));
5132                } catch (CmsDbEntryNotFoundException e) {
5133                    // ignore, this may happen while deleting an orgunit
5134                    if (LOG.isDebugEnabled()) {
5135                        LOG.debug(e.getLocalizedMessage(), e);
5136                    }
5137                }
5138            }
5139            String parentOu = CmsOrganizationalUnit.getParentFqn(group.getOuFqn());
5140            if (parentOu != null) {
5141                // iterate the parent ou's
5142                result.addAll(getRoleGroupsImpl(dbc, parentOu + group.getSimpleName(), directUsersOnly, accumulator));
5143            }
5144        }
5145        accumulator.put(roleGroupName, result);
5146        return result;
5147    }
5148
5149    /**
5150     * Returns all roles the given user has for the given resource.<p>
5151     *
5152     * @param dbc the current database context
5153     * @param user the user to check
5154     * @param resource the resource to check the roles for
5155     *
5156     * @return a list of {@link CmsRole} objects
5157     *
5158     * @throws CmsException if something goes wrong
5159     */
5160    public List<CmsRole> getRolesForResource(CmsDbContext dbc, CmsUser user, CmsResource resource) throws CmsException {
5161
5162        // guest user has no role
5163        if (user.isGuestUser()) {
5164            return Collections.emptyList();
5165        }
5166
5167        // try to read from cache
5168        String key = user.getId().toString() + resource.getRootPath();
5169        List<CmsRole> result = m_monitor.getCachedRoleList(key);
5170        if (result != null) {
5171            return result;
5172        }
5173        result = new ArrayList<CmsRole>();
5174
5175        Iterator<CmsOrganizationalUnit> itOus = getResourceOrgUnits(dbc, resource).iterator();
5176        while (itOus.hasNext()) {
5177            CmsOrganizationalUnit ou = itOus.next();
5178
5179            // read all roles of the current user
5180            List<CmsGroup> groups = new ArrayList<CmsGroup>(
5181                getGroupsOfUser(
5182                    dbc,
5183                    user.getName(),
5184                    ou.getName(),
5185                    false,
5186                    true,
5187                    false,
5188                    dbc.getRequestContext().getRemoteAddress()));
5189            // check the roles applying to the given resource
5190            Iterator<CmsGroup> it = groups.iterator();
5191            while (it.hasNext()) {
5192                CmsGroup group = it.next();
5193                CmsRole givenRole = CmsRole.valueOf(group).forOrgUnit(null);
5194                if (givenRole.isOrganizationalUnitIndependent() || result.contains(givenRole)) {
5195                    // skip already added roles
5196                    continue;
5197                }
5198                result.add(givenRole);
5199            }
5200        }
5201
5202        result = Collections.unmodifiableList(result);
5203        m_monitor.cacheRoleList(key, result);
5204        return result;
5205    }
5206
5207    /**
5208     * Returns all roles the given user has independent of the resource.<p>
5209     *
5210     * @param dbc the current database context
5211     * @param user the user to check
5212     *
5213     * @return a list of {@link CmsRole} objects
5214     *
5215     * @throws CmsException if something goes wrong
5216     */
5217    public List<CmsRole> getRolesForUser(CmsDbContext dbc, CmsUser user) throws CmsException {
5218
5219        // guest user has no role
5220        if (user.isGuestUser()) {
5221            return Collections.emptyList();
5222        }
5223
5224        // try to read from cache
5225        List<CmsRole> result = m_monitor.getGroupListCache().getBareRoles(user.getId());
5226        if (result != null) {
5227            return result;
5228        }
5229        result = new ArrayList<CmsRole>();
5230
5231        // read all roles of the current user
5232        List<CmsGroup> groups = new ArrayList<CmsGroup>(
5233            getGroupsOfUser(dbc, user.getName(), "", true, true, false, dbc.getRequestContext().getRemoteAddress()));
5234
5235        // check the roles applying to the given resource
5236        Iterator<CmsGroup> it = groups.iterator();
5237        while (it.hasNext()) {
5238            CmsGroup group = it.next();
5239            CmsRole givenRole = CmsRole.valueOf(group);
5240            givenRole = givenRole.forOrgUnit(null);
5241            if (!result.contains(givenRole)) {
5242                result.add(givenRole);
5243            }
5244        }
5245        result = Collections.unmodifiableList(result);
5246        m_monitor.getGroupListCache().setBareRoles(user, result);
5247        return result;
5248    }
5249
5250    /**
5251     * Returns the security manager this driver manager belongs to.<p>
5252     *
5253     * @return the security manager this driver manager belongs to
5254     */
5255    public CmsSecurityManager getSecurityManager() {
5256
5257        return m_securityManager;
5258    }
5259
5260    /**
5261     * Returns an instance of the common sql manager.<p>
5262     *
5263     * @return an instance of the common sql manager
5264     */
5265    public CmsSqlManager getSqlManager() {
5266
5267        return m_sqlManager;
5268    }
5269
5270    /**
5271     * Returns a delivery-capable storage backend by its stable storage identifier.<p>
5272     *
5273     * @param dbc the current database context
5274     * @param storage the stable storage identifier
5275     *
5276     * @return the delivery-capable storage backend, or <code>null</code> if not supported
5277     *
5278     * @throws CmsException if operation was not successful
5279     */
5280    public I_CmsStorageDelivery getStoredContentDelivery(CmsDbContext dbc, String storage) throws CmsException {
5281
5282        return getVfsDriver(dbc).getStoredContentDelivery(dbc, storage);
5283    }
5284
5285    /**
5286     * Returns the subscription driver of this driver manager.<p>
5287     *
5288     * @return a subscription driver
5289     */
5290    public I_CmsSubscriptionDriver getSubscriptionDriver() {
5291
5292        return m_subscriptionDriver;
5293    }
5294
5295    /**
5296     * Returns the user driver.<p>
5297     *
5298     * @return the user driver
5299     */
5300    public I_CmsUserDriver getUserDriver() {
5301
5302        return m_userDriver;
5303    }
5304
5305    /**
5306     * Returns the user driver for a given database context.<p>
5307     *
5308     * @param dbc the database context
5309     *
5310     * @return the user driver for the database context
5311     */
5312    public I_CmsUserDriver getUserDriver(CmsDbContext dbc) {
5313
5314        if ((dbc == null) || (dbc.getProjectId() == null) || dbc.getProjectId().isNullUUID()) {
5315            return m_userDriver;
5316        }
5317        I_CmsUserDriver driver = dbc.getUserDriver(dbc.getProjectId());
5318        return driver != null ? driver : m_userDriver;
5319
5320    }
5321
5322    /**
5323     * Returns either the user driver for the given DB context (if it has one) or a default value instead.<p>
5324     *
5325     * @param dbc the DB context
5326     * @param defaultDriver the driver that should be returned if no driver for the DB context was found
5327     *
5328     * @return either the user driver for the DB context, or <code>defaultDriver</code> if none were found
5329     */
5330    public I_CmsUserDriver getUserDriver(CmsDbContext dbc, I_CmsUserDriver defaultDriver) {
5331
5332        if ((dbc == null) || (dbc.getProjectId() == null) || dbc.getProjectId().isNullUUID()) {
5333            return defaultDriver;
5334        }
5335        I_CmsUserDriver driver = dbc.getUserDriver(dbc.getProjectId());
5336        return driver != null ? driver : defaultDriver;
5337    }
5338
5339    /**
5340     * Returns all direct users of the given organizational unit.<p>
5341     *
5342     * @param dbc the current db context
5343     * @param orgUnit the organizational unit to get all users for
5344     * @param recursive if all groups of sub-organizational units should be retrieved too
5345     *
5346     * @return all <code>{@link CmsUser}</code> objects in the organizational unit
5347     *
5348     * @throws CmsException if operation was not successful
5349     *
5350     * @see org.opencms.security.CmsOrgUnitManager#getResourcesForOrganizationalUnit(CmsObject, String)
5351     * @see org.opencms.security.CmsOrgUnitManager#getUsers(CmsObject, String, boolean)
5352     */
5353    public List<CmsUser> getUsers(CmsDbContext dbc, CmsOrganizationalUnit orgUnit, boolean recursive)
5354    throws CmsException {
5355
5356        return getUserDriver(dbc).getUsers(dbc, orgUnit, recursive);
5357    }
5358
5359    /**
5360     * Returns a list of users in a group.<p>
5361     *
5362     * @param dbc the current database context
5363     * @param groupname the name of the group to list users from
5364     * @param includeOtherOuUsers include users of other organizational units
5365     * @param directUsersOnly if set only the direct assigned users will be returned,
5366     *                        if not also indirect users, ie. members of parent roles,
5367     *                        this parameter only works with roles
5368     * @param readRoles if to read roles or groups
5369     *
5370     * @return all <code>{@link CmsUser}</code> objects in the group
5371     *
5372     * @throws CmsException if operation was not successful
5373     */
5374    public List<CmsUser> getUsersOfGroup(
5375        CmsDbContext dbc,
5376        String groupname,
5377        boolean includeOtherOuUsers,
5378        boolean directUsersOnly,
5379        boolean readRoles)
5380    throws CmsException {
5381
5382        return internalUsersOfGroup(
5383            dbc,
5384            CmsOrganizationalUnit.getParentFqn(groupname),
5385            groupname,
5386            includeOtherOuUsers,
5387            directUsersOnly,
5388            readRoles);
5389    }
5390
5391    /**
5392     * Returns the given user's publish list.<p>
5393     *
5394     * @param dbc the database context
5395     * @param userId the user's id
5396     *
5397     * @return the given user's publish list
5398     *
5399     * @throws CmsDataAccessException if something goes wrong
5400     */
5401    public List<CmsResource> getUsersPubList(CmsDbContext dbc, CmsUUID userId) throws CmsDataAccessException {
5402
5403        synchronized (m_publishListUpdateLock) {
5404            updateLog(dbc);
5405            return m_projectDriver.getUsersPubList(dbc, userId);
5406        }
5407    }
5408
5409    /**
5410     * Returns all direct users of the given organizational unit, without their additional info.<p>
5411     *
5412     * @param dbc the current db context
5413     * @param orgUnit the organizational unit to get all users for
5414     * @param recursive if all groups of sub-organizational units should be retrieved too
5415     *
5416     * @return all <code>{@link CmsUser}</code> objects in the organizational unit
5417     *
5418     * @throws CmsException if operation was not successful
5419     *
5420     * @see org.opencms.security.CmsOrgUnitManager#getResourcesForOrganizationalUnit(CmsObject, String)
5421     * @see org.opencms.security.CmsOrgUnitManager#getUsers(CmsObject, String, boolean)
5422     */
5423    public List<CmsUser> getUsersWithoutAdditionalInfo(
5424        CmsDbContext dbc,
5425        CmsOrganizationalUnit orgUnit,
5426        boolean recursive)
5427    throws CmsException {
5428
5429        return getUserDriver(dbc).getUsersWithoutAdditionalInfo(dbc, orgUnit, recursive);
5430    }
5431
5432    /**
5433     * Returns the VFS driver.<p>
5434     *
5435     * @return the VFS driver
5436     */
5437    public I_CmsVfsDriver getVfsDriver() {
5438
5439        return m_vfsDriver;
5440    }
5441
5442    /**
5443     * Returns the VFS driver for the given database context.<p>
5444     *
5445     * @param dbc the database context
5446     *
5447     * @return a VFS driver
5448     */
5449    public I_CmsVfsDriver getVfsDriver(CmsDbContext dbc) {
5450
5451        if ((dbc == null) || (dbc.getProjectId() == null) || dbc.getProjectId().isNullUUID()) {
5452            return m_vfsDriver;
5453        }
5454        I_CmsVfsDriver driver = dbc.getVfsDriver(dbc.getProjectId());
5455        return driver != null ? driver : m_vfsDriver;
5456
5457    }
5458
5459    /**
5460     * Writes a vector of access control entries as new access control entries of a given resource.<p>
5461     *
5462     * Already existing access control entries of this resource are removed before.
5463     * Access is granted, if:<p>
5464     * <ul>
5465     * <li>the current user has control permission on the resource</li>
5466     * </ul>
5467     *
5468     * @param dbc the current database context
5469     * @param resource the resource
5470     * @param acEntries a list of <code>{@link CmsAccessControlEntry}</code> objects
5471     *
5472     * @throws CmsException if something goes wrong
5473     */
5474    public void importAccessControlEntries(
5475        CmsDbContext dbc,
5476        CmsResource resource,
5477        List<CmsAccessControlEntry> acEntries)
5478    throws CmsException {
5479
5480        I_CmsUserDriver userDriver = getUserDriver(dbc);
5481        userDriver.removeAccessControlEntries(dbc, dbc.currentProject(), resource.getResourceId());
5482        List<CmsAccessControlEntry> fixedAces = new ArrayList<>();
5483        for (CmsAccessControlEntry entry : acEntries) {
5484            if (entry.getResource() == null) {
5485                entry = new CmsAccessControlEntry(
5486                    resource.getResourceId(),
5487                    entry.getPrincipal(),
5488                    entry.getPermissions(),
5489                    entry.getFlags());
5490            }
5491            fixedAces.add(entry);
5492        }
5493
5494        Iterator<CmsAccessControlEntry> i = fixedAces.iterator();
5495        while (i.hasNext()) {
5496            userDriver.writeAccessControlEntry(dbc, dbc.currentProject(), i.next());
5497        }
5498        m_monitor.clearAccessControlListCache();
5499    }
5500
5501    /**
5502     * Imports a rewrite alias.<p>
5503     *
5504     * @param dbc the database context
5505     * @param siteRoot the site root of the alias
5506     * @param source the source of the alias
5507     * @param target the target of the alias
5508     * @param mode the alias mode
5509     *
5510     * @return the import result
5511     *
5512     * @throws CmsException if something goes wrong
5513     */
5514    public CmsAliasImportResult importRewriteAlias(
5515        CmsDbContext dbc,
5516        String siteRoot,
5517        String source,
5518        String target,
5519        CmsAliasMode mode)
5520    throws CmsException {
5521
5522        I_CmsVfsDriver vfs = getVfsDriver(dbc);
5523        List<CmsRewriteAlias> existingAliases = vfs.readRewriteAliases(
5524            dbc,
5525            new CmsRewriteAliasFilter().setSiteRoot(siteRoot));
5526        CmsUUID idToDelete = null;
5527        for (CmsRewriteAlias alias : existingAliases) {
5528            if (alias.getPatternString().equals(source)) {
5529                idToDelete = alias.getId();
5530            }
5531        }
5532        if (idToDelete != null) {
5533            vfs.deleteRewriteAliases(dbc, new CmsRewriteAliasFilter().setId(idToDelete));
5534        }
5535        CmsRewriteAlias alias = new CmsRewriteAlias(new CmsUUID(), siteRoot, source, target, mode);
5536        List<CmsRewriteAlias> aliases = new ArrayList<CmsRewriteAlias>();
5537        aliases.add(alias);
5538        getVfsDriver(dbc).insertRewriteAliases(dbc, aliases);
5539        CmsAliasImportResult result = new CmsAliasImportResult(
5540            CmsAliasImportStatus.aliasNew,
5541            "OK",
5542            source,
5543            target,
5544            mode);
5545        return result;
5546    }
5547
5548    /**
5549     * Creates a new user by import.<p>
5550     *
5551     * @param dbc the current database context
5552     * @param id the id of the user
5553     * @param name the new name for the user
5554     * @param password the new password for the user (already encrypted)
5555     * @param firstname the firstname of the user
5556     * @param lastname the lastname of the user
5557     * @param email the email of the user
5558     * @param flags the flags for a user (for example <code>{@link I_CmsPrincipal#FLAG_ENABLED}</code>)
5559     * @param dateCreated the creation date
5560     * @param additionalInfos the additional user infos
5561     *
5562     * @return the imported user
5563     *
5564     * @throws CmsException if something goes wrong
5565     */
5566    public CmsUser importUser(
5567        CmsDbContext dbc,
5568        String id,
5569        String name,
5570        String password,
5571        String firstname,
5572        String lastname,
5573        String email,
5574        int flags,
5575        long dateCreated,
5576        Map<String, Object> additionalInfos)
5577    throws CmsException {
5578
5579        // no space before or after the name
5580        name = name.trim();
5581        // check the user name
5582        String userName = CmsOrganizationalUnit.getSimpleName(name);
5583        OpenCms.getValidationHandler().checkUserName(userName);
5584        if (CmsStringUtil.isEmptyOrWhitespaceOnly(userName)) {
5585            throw new CmsIllegalArgumentException(Messages.get().container(Messages.ERR_BAD_USER_1, userName));
5586        }
5587        // check the ou
5588        CmsOrganizationalUnit ou = readOrganizationalUnit(dbc, CmsOrganizationalUnit.getParentFqn(name));
5589
5590        // check webuser ou
5591        if (ou.hasFlagWebuser() && ((flags & I_CmsPrincipal.FLAG_USER_WEBUSER) == 0)) {
5592            flags += I_CmsPrincipal.FLAG_USER_WEBUSER;
5593        }
5594        CmsUser newUser = getUserDriver(dbc).createUser(
5595            dbc,
5596            new CmsUUID(id),
5597            name,
5598            password,
5599            firstname,
5600            lastname,
5601            email,
5602            0,
5603            flags,
5604            dateCreated,
5605            additionalInfos);
5606        return newUser;
5607    }
5608
5609    /**
5610     * Increments a counter and returns its value before incrementing.<p>
5611     *
5612     * @param dbc the current database context
5613     * @param name the name of the counter which should be incremented
5614     *
5615     * @return the value of the counter
5616     *
5617     * @throws CmsException if something goes wrong
5618     */
5619    public int incrementCounter(CmsDbContext dbc, String name) throws CmsException {
5620
5621        return getVfsDriver(dbc).incrementCounter(dbc, name);
5622    }
5623
5624    /**
5625     * Initializes the driver and sets up all required modules and connections.<p>
5626     *
5627     * @param configurationManager the configuration manager
5628     * @param dbContextFactory the db context factory
5629     *
5630     * @throws CmsException if something goes wrong
5631     * @throws Exception if something goes wrong
5632     */
5633    public void init(CmsConfigurationManager configurationManager, I_CmsDbContextFactory dbContextFactory)
5634    throws CmsException, Exception {
5635
5636        // initialize the access-module.
5637        if (CmsLog.INIT.isInfoEnabled()) {
5638            CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_DRIVER_MANAGER_START_PHASE4_0));
5639        }
5640        // store local reference to the memory monitor to avoid multiple lookups through the OpenCms singelton
5641        m_monitor = OpenCms.getMemoryMonitor();
5642
5643        CmsSystemConfiguration systemConfiguation = (CmsSystemConfiguration)configurationManager.getConfiguration(
5644            CmsSystemConfiguration.class);
5645        CmsCacheSettings settings = systemConfiguation.getCacheSettings();
5646
5647        // initialize the key generator
5648        m_keyGenerator = (I_CmsCacheKey)Class.forName(settings.getCacheKeyGenerator()).newInstance();
5649
5650        // initialize the HTML link validator
5651        m_htmlLinkValidator = new CmsRelationSystemValidator(this);
5652
5653        // fills the defaults if needed
5654        CmsDbContext dbc1 = dbContextFactory.getDbContext();
5655        getUserDriver().fillDefaults(dbc1);
5656        getProjectDriver().fillDefaults(dbc1);
5657
5658        // set the driver manager in the publish engine
5659        m_publishEngine.setDriverManager(this);
5660        // create the root organizational unit if needed
5661        CmsDbContext dbc2 = dbContextFactory.getDbContext(
5662            new CmsRequestContext(
5663                readUser(dbc1, OpenCms.getDefaultUsers().getUserAdmin()),
5664                readProject(dbc1, CmsProject.ONLINE_PROJECT_ID),
5665                null,
5666                CmsSiteMatcher.DEFAULT_MATCHER,
5667                "",
5668                false,
5669                null,
5670                null,
5671                null,
5672                0,
5673                null,
5674                null,
5675                "",
5676                false));
5677        dbc1.clear();
5678        getUserDriver().createRootOrganizationalUnit(dbc2);
5679        dbc2.clear();
5680    }
5681
5682    /**
5683     * Initializes the organizational unit.<p>
5684     *
5685     * @param dbc the DB context
5686     * @param ou the organizational unit
5687     */
5688    public void initOrgUnit(CmsDbContext dbc, CmsOrganizationalUnit ou) {
5689
5690        try {
5691            dbc.setAttribute(ATTR_INIT_OU, ou);
5692            m_userDriver.fillDefaults(dbc);
5693        } finally {
5694            dbc.removeAttribute(ATTR_INIT_OU);
5695        }
5696    }
5697
5698    /**
5699     * Checks if the specified resource is inside the current project.<p>
5700     *
5701     * The project "view" is determined by a set of path prefixes.
5702     * If the resource starts with any one of this prefixes, it is considered to
5703     * be "inside" the project.<p>
5704     *
5705     * @param dbc the current database context
5706     * @param resourcename the specified resource name (full path)
5707     *
5708     * @return <code>true</code>, if the specified resource is inside the current project
5709     */
5710    public boolean isInsideCurrentProject(CmsDbContext dbc, String resourcename) {
5711
5712        List<String> projectResources = null;
5713        try {
5714            projectResources = readProjectResources(dbc, dbc.currentProject());
5715        } catch (CmsException e) {
5716            if (LOG.isErrorEnabled()) {
5717                LOG.error(
5718                    Messages.get().getBundle().key(
5719                        Messages.LOG_CHECK_RESOURCE_INSIDE_CURRENT_PROJECT_2,
5720                        resourcename,
5721                        dbc.currentProject().getName()),
5722                    e);
5723            }
5724            return false;
5725        }
5726        return CmsProject.isInsideProject(projectResources, resourcename);
5727    }
5728
5729    /**
5730     * Checks whether the subscription driver is available.<p>
5731     *
5732     * @return true if the subscription driver is available
5733     */
5734    public boolean isSubscriptionDriverAvailable() {
5735
5736        return m_subscriptionDriver != null;
5737    }
5738
5739    /**
5740     * Checks if a project is the tempfile project.<p>
5741     * @param project the project to test
5742     * @return true if the project is the tempfile project
5743     */
5744    public boolean isTempfileProject(CmsProject project) {
5745
5746        return project.getName().equals("tempFileProject");
5747    }
5748
5749    /**
5750     * Checks if one of the resources (except the resource itself)
5751     * is a sibling in a "labeled" site folder.<p>
5752     *
5753     * This method is used when creating a new sibling
5754     * (use the <code>newResource</code> parameter &amp; <code>action = 1</code>)
5755     * or deleting/importing a resource (call with <code>action = 2</code>).<p>
5756     *
5757     * @param dbc the current database context
5758     * @param resource the resource
5759     * @param newResource absolute path for a resource sibling which will be created
5760     * @param action the action which has to be performed (1: create VFS link, 2: all other actions)
5761     *
5762     * @return <code>true</code> if the flag should be set for the resource, otherwise <code>false</code>
5763     *
5764     * @throws CmsDataAccessException if something goes wrong
5765     */
5766    public boolean labelResource(CmsDbContext dbc, CmsResource resource, String newResource, int action)
5767    throws CmsDataAccessException {
5768
5769        // get the list of labeled site folders from the runtime property
5770        List<String> labeledSites = OpenCms.getWorkplaceManager().getLabelSiteFolders();
5771
5772        if (labeledSites.size() == 0) {
5773            // no labeled sites defined, just return false
5774            return false;
5775        }
5776
5777        if (action == 1) {
5778            // CASE 1: a new resource is created, check the sites
5779            if (!resource.isLabeled()) {
5780                // source isn't labeled yet, so check!
5781                boolean linkInside = false;
5782                boolean sourceInside = false;
5783                for (int i = 0; i < labeledSites.size(); i++) {
5784                    String curSite = labeledSites.get(i);
5785                    if (newResource.startsWith(curSite)) {
5786                        // the link lies in a labeled site
5787                        linkInside = true;
5788                    }
5789                    if (resource.getRootPath().startsWith(curSite)) {
5790                        // the source lies in a labeled site
5791                        sourceInside = true;
5792                    }
5793                    if (linkInside && sourceInside) {
5794                        break;
5795                    }
5796                }
5797                // return true when either source or link is in labeled site, otherwise false
5798                return (linkInside != sourceInside);
5799            }
5800            // resource is already labeled
5801            return false;
5802
5803        } else {
5804            // CASE 2: the resource will be deleted or created (import)
5805            // check if at least one of the other siblings resides inside a "labeled site"
5806            // and if at least one of the other siblings resides outside a "labeled site"
5807            boolean isInside = false;
5808            boolean isOutside = false;
5809            // check if one of the other vfs links lies in a labeled site folder
5810            List<CmsResource> siblings = getVfsDriver(
5811                dbc).readSiblings(dbc, dbc.currentProject().getUuid(), resource, false);
5812            updateContextDates(dbc, siblings);
5813            Iterator<CmsResource> i = siblings.iterator();
5814            while (i.hasNext() && (!isInside || !isOutside)) {
5815                CmsResource currentResource = i.next();
5816                if (currentResource.equals(resource)) {
5817                    // dont't check the resource itself!
5818                    continue;
5819                }
5820                String curPath = currentResource.getRootPath();
5821                boolean curInside = false;
5822                for (int k = 0; k < labeledSites.size(); k++) {
5823                    if (curPath.startsWith(labeledSites.get(k))) {
5824                        // the link is in the labeled site
5825                        isInside = true;
5826                        curInside = true;
5827                        break;
5828                    }
5829                }
5830                if (!curInside) {
5831                    // the current link was not found in labeled site, so it is outside
5832                    isOutside = true;
5833                }
5834            }
5835            // now check the new resource name if present
5836            if (newResource != null) {
5837                boolean curInside = false;
5838                for (int k = 0; k < labeledSites.size(); k++) {
5839                    if (newResource.startsWith(labeledSites.get(k))) {
5840                        // the new resource is in the labeled site
5841                        isInside = true;
5842                        curInside = true;
5843                        break;
5844                    }
5845                }
5846                if (!curInside) {
5847                    // the new resource was not found in labeled site, so it is outside
5848                    isOutside = true;
5849                }
5850            }
5851            return (isInside && isOutside);
5852        }
5853    }
5854
5855    /**
5856     * Returns the user, who had locked the resource.<p>
5857     *
5858     * A user can lock a resource, so he is the only one who can write this
5859     * resource. This methods checks, if a resource was locked.
5860     *
5861     * @param dbc the current database context
5862     * @param resource the resource
5863     *
5864     * @return the user, who had locked the resource
5865     *
5866     * @throws CmsException will be thrown, if the user has not the rights for this resource
5867     */
5868    public CmsUser lockedBy(CmsDbContext dbc, CmsResource resource) throws CmsException {
5869
5870        return readUser(dbc, m_lockManager.getLock(dbc, resource).getEditionLock().getUserId());
5871    }
5872
5873    /**
5874     * Locks a resource.<p>
5875     *
5876     * The <code>type</code> parameter controls what kind of lock is used.<br>
5877     * Possible values for this parameter are: <br>
5878     * <ul>
5879     * <li><code>{@link org.opencms.lock.CmsLockType#EXCLUSIVE}</code></li>
5880     * <li><code>{@link org.opencms.lock.CmsLockType#TEMPORARY}</code></li>
5881     * <li><code>{@link org.opencms.lock.CmsLockType#PUBLISH}</code></li>
5882     * </ul><p>
5883     *
5884     * @param dbc the current database context
5885     * @param resource the resource to lock
5886     * @param type type of the lock
5887     *
5888     * @throws CmsException if something goes wrong
5889     *
5890     * @see CmsObject#lockResource(String)
5891     * @see CmsObject#lockResourceTemporary(String)
5892     * @see org.opencms.file.types.I_CmsResourceType#lockResource(CmsObject, CmsSecurityManager, CmsResource, CmsLockType)
5893     */
5894    public void lockResource(CmsDbContext dbc, CmsResource resource, CmsLockType type) throws CmsException {
5895
5896        // update the resource cache
5897        m_monitor.clearResourceCache();
5898
5899        CmsProject project = dbc.currentProject();
5900
5901        // add the resource to the lock dispatcher
5902        m_lockManager.addResource(dbc, resource, dbc.currentUser(), project, type);
5903        boolean changedProjectLastModified = false;
5904        if (!resource.getState().isUnchanged() && !resource.getState().isKeep()) {
5905            // update the project flag of a modified resource as "last modified inside the current project"
5906            getVfsDriver(dbc).writeLastModifiedProjectId(dbc, project, project.getUuid(), resource);
5907            changedProjectLastModified = true;
5908        }
5909
5910        // we must also clear the permission cache
5911        m_monitor.flushCache(CmsMemoryMonitor.CacheType.PERMISSION);
5912
5913        // fire resource modification event
5914        Map<String, Object> data = new HashMap<String, Object>(2);
5915        data.put(I_CmsEventListener.KEY_RESOURCE, resource);
5916        data.put(
5917            I_CmsEventListener.KEY_CHANGE,
5918            Integer.valueOf(changedProjectLastModified ? CHANGED_PROJECT : NOTHING_CHANGED));
5919        data.put(I_CmsEventListener.KEY_SKIPINDEX, Boolean.TRUE);
5920        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_MODIFIED, data));
5921    }
5922
5923    /**
5924     * Adds the given log entry to the current user's log.<p>
5925     *
5926     * This operation works only on memory, to get the log entries actually
5927     * written to DB you have to call the {@link #updateLog(CmsDbContext)} method.<p>
5928     *
5929     * @param dbc the current database context
5930     * @param logEntry the log entry to create
5931     * @param force forces the log entry to be counted,
5932     *              if not only the first log entry in a transaction will be taken into account
5933     */
5934    public void log(CmsDbContext dbc, CmsLogEntry logEntry, boolean force) {
5935
5936        if (dbc == null) {
5937            return;
5938        }
5939        // check log level
5940        if (!logEntry.getType().isActive()) {
5941            // do not log inactive entries
5942            return;
5943        }
5944        // if not forcing
5945        if (!force) {
5946            // operation already logged
5947            boolean abort = (dbc.getAttribute(CmsLogEntry.ATTR_LOG_ENTRY) != null);
5948            // disabled logging from outside
5949            abort |= (dbc.getRequestContext().getAttribute(CmsLogEntry.ATTR_LOG_ENTRY) != null);
5950            if (abort) {
5951                return;
5952            }
5953        }
5954        // prevent several entries for the same operation
5955        dbc.setAttribute(CmsLogEntry.ATTR_LOG_ENTRY, Boolean.TRUE);
5956        // keep it for later
5957        m_log.add(logEntry);
5958    }
5959
5960    /**
5961     * Attempts to authenticate a user into OpenCms with the given password.
5962     *
5963     * <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,
5964     * while check mode merely checks the login details without firing the events normally fired during login, and without modifying the user. However,
5965     * in the case an incorrect password is given, the invalid login counter is still incremented.
5966     *
5967     * @param dbc the current database context
5968     * @param userName the name of the user to be logged in
5969     * @param password the password of the user
5970     * @param secondFactorInfo the second factor information for 2FA (may be null)
5971     * @param remoteAddress the ip address of the request
5972     * @param mode the mode to use (real login or check only)
5973     *
5974     * @return the logged in user
5975     *
5976     * @throws CmsAuthentificationException if the login was not successful
5977     * @throws CmsDataAccessException in case of errors accessing the database
5978     * @throws CmsPasswordEncryptionException in case of errors encrypting the users password
5979     */
5980    public CmsUser loginUser(
5981        CmsDbContext dbc,
5982        String userName,
5983        String password,
5984        CmsSecondFactorInfo secondFactorInfo,
5985        String remoteAddress,
5986        LoginUserMode mode)
5987    throws CmsAuthentificationException, CmsDataAccessException, CmsPasswordEncryptionException {
5988
5989        if (CmsStringUtil.isEmptyOrWhitespaceOnly(password)) {
5990            throw new CmsDbEntryNotFoundException(Messages.get().container(Messages.ERR_UNKNOWN_USER_1, userName));
5991        }
5992        CmsUser newUser;
5993        CmsUser userCopy;
5994        try {
5995            // read the user from the driver to avoid the cache
5996            newUser = getUserDriver(dbc).readUser(dbc, userName, password, remoteAddress);
5997            userCopy = newUser.clone();
5998            userName = newUser.getName();
5999
6000        } catch (CmsDbEntryNotFoundException e) {
6001            // this indicates that the username / password combination does not exist
6002            // any other exception indicates database issues, these are not catched here
6003
6004            // check if a user with this name exists at all
6005            CmsUser user = null;
6006            try {
6007                user = readUser(dbc, userName);
6008                userName = user.getName();
6009            } catch (CmsDataAccessException e2) {
6010                // apparently this user does not exist in the database
6011            }
6012
6013            if (user != null) {
6014                if (dbc.currentUser().isGuestUser()) {
6015                    // add an invalid login attempt for this user to the storage
6016                    OpenCms.getLoginManager().addInvalidLogin(userName, remoteAddress);
6017                }
6018                OpenCms.getLoginManager().checkInvalidLogins(userName, remoteAddress);
6019                throw new CmsAuthentificationException(
6020                    org.opencms.security.Messages.get().container(
6021                        org.opencms.security.Messages.ERR_LOGIN_FAILED_2,
6022                        userName,
6023                        remoteAddress),
6024                    e);
6025            } else {
6026                String userOu = CmsOrganizationalUnit.getParentFqn(userName);
6027                if (userOu != null) {
6028                    String parentOu = CmsOrganizationalUnit.getParentFqn(userOu);
6029                    if (parentOu != null) {
6030                        // try a higher level ou
6031                        String uName = CmsOrganizationalUnit.getSimpleName(userName);
6032                        return loginUser(dbc, parentOu + uName, password, secondFactorInfo, remoteAddress, mode);
6033                    }
6034                }
6035                throw new CmsAuthentificationException(
6036                    org.opencms.security.Messages.get().container(
6037                        org.opencms.security.Messages.ERR_LOGIN_FAILED_NO_USER_2,
6038                        userName,
6039                        remoteAddress),
6040                    e);
6041            }
6042        }
6043        // check if the "enabled" flag is set for the user
6044        if (!newUser.isEnabled()) {
6045            // user is disabled, throw a securiy exception
6046            throw new CmsAuthentificationException(
6047                org.opencms.security.Messages.get().container(
6048                    org.opencms.security.Messages.ERR_LOGIN_FAILED_DISABLED_2,
6049                    userName,
6050                    remoteAddress));
6051        }
6052
6053        if (mode == LoginUserMode.standard) {
6054            CmsTwoFactorAuthenticationHandler handler = OpenCms.getTwoFactorAuthenticationHandler();
6055            if (handler.needsTwoFactorAuthentication(newUser)) {
6056                // note that password check must already have been successful at this stage
6057
6058                if (handler.hasSecondFactor(newUser)) {
6059                    if (!handler.verifySecondFactor(newUser, secondFactorInfo)) {
6060                        if (dbc.currentUser().isGuestUser()) {
6061                            // add an invalid login attempt for this user to the storage
6062                            OpenCms.getLoginManager().addInvalidLogin(userName, remoteAddress);
6063                        }
6064                        OpenCms.getLoginManager().checkInvalidLogins(userName, remoteAddress);
6065                        throw new CmsAuthentificationException(
6066                            org.opencms.security.Messages.get().container(
6067                                org.opencms.security.Messages.ERR_VERIFICATION_FAILED_1,
6068                                userName));
6069                    }
6070                } else {
6071                    try {
6072                        if (handler.setUpAndVerifySecondFactor(newUser, secondFactorInfo)) {
6073                            LOG.info("Second factor setup successful for user " + newUser.getName());
6074                        } else {
6075                            if (dbc.currentUser().isGuestUser()) {
6076                                // add an invalid login attempt for this user to the storage
6077                                OpenCms.getLoginManager().addInvalidLogin(userName, remoteAddress);
6078                            }
6079                            OpenCms.getLoginManager().checkInvalidLogins(userName, remoteAddress);
6080                            throw new CmsAuthentificationException(
6081                                org.opencms.security.Messages.get().container(
6082                                    org.opencms.security.Messages.ERR_VERIFICATION_FAILED_1,
6083                                    userName));
6084                        }
6085                    } catch (CmsSecondFactorSetupException e) {
6086                        throw new CmsAuthentificationException(
6087                            org.opencms.security.Messages.get().container(
6088                                org.opencms.security.Messages.ERR_VERIFICATION_FAILED_1,
6089                                userName),
6090                            e);
6091                    }
6092                }
6093            }
6094        }
6095        if (dbc.currentUser().isGuestUser()) {
6096            // check if this account is temporarily disabled because of too many invalid login attempts
6097            // this will throw an exception if the test fails
6098            OpenCms.getLoginManager().checkInvalidLogins(userName, remoteAddress);
6099            if (mode == LoginUserMode.standard) {
6100                // test successful, remove all previous invalid login attempts for this user from the storage
6101                OpenCms.getLoginManager().removeInvalidLogins(userName, remoteAddress);
6102            }
6103        }
6104
6105        if (!m_securityManager.hasRole(
6106            dbc,
6107            newUser,
6108            CmsRole.ADMINISTRATOR.forOrgUnit(dbc.getRequestContext().getOuFqn()))) {
6109            // new user is not Administrator, check if login is currently allowed
6110            OpenCms.getLoginManager().checkLoginAllowed();
6111        }
6112
6113        if (mode == LoginUserMode.standard) {
6114
6115            newUser.setLastlogin(System.currentTimeMillis());
6116            m_monitor.clearUserCache(newUser);
6117
6118            // write the changed user object back to the user driver
6119            Map<String, Object> additionalInfosForRepositories = OpenCms.getRepositoryManager().getAdditionalInfoForLogin(
6120                newUser.getName(),
6121                password);
6122            boolean requiresAddInfoUpdate = false;
6123
6124            // check for changes
6125            for (Entry<String, Object> entry : additionalInfosForRepositories.entrySet()) {
6126                Object value = entry.getValue();
6127                Object current = newUser.getAdditionalInfo(entry.getKey());
6128                if (((value == null) && (current != null)) || ((value != null) && !value.equals(current))) {
6129                    requiresAddInfoUpdate = true;
6130                    break;
6131                }
6132            }
6133            if (requiresAddInfoUpdate) {
6134                newUser.getAdditionalInfo().putAll(additionalInfosForRepositories);
6135            }
6136            String lastPasswordChange = (String)newUser.getAdditionalInfo(
6137                CmsUserSettings.ADDITIONAL_INFO_LAST_PASSWORD_CHANGE);
6138            if (lastPasswordChange == null) {
6139                requiresAddInfoUpdate = true;
6140                newUser.getAdditionalInfo().put(
6141                    CmsUserSettings.ADDITIONAL_INFO_LAST_PASSWORD_CHANGE,
6142                    "" + System.currentTimeMillis());
6143            }
6144            if (!requiresAddInfoUpdate) {
6145                dbc.setAttribute(ATTRIBUTE_LOGIN, newUser.getName());
6146            }
6147
6148            if (mode == LoginUserMode.standard) {
6149                OpenCms.getTwoFactorAuthenticationHandler().trackUserChange(dbc.getRequestContext(), userCopy, newUser);
6150                getUserDriver(dbc).writeUser(dbc, newUser);
6151            }
6152            int changes = CmsUser.FLAG_LAST_LOGIN;
6153
6154            // check if we need to update the password
6155            if (!OpenCms.getPasswordHandler().checkPassword(password, newUser.getPassword(), false)
6156                && OpenCms.getPasswordHandler().checkPassword(password, newUser.getPassword(), true)) {
6157                // the password does not check with the current hash algorithm but with the fall back, update the password
6158                getUserDriver(dbc).writePassword(dbc, userName, password, password);
6159                changes = changes | CmsUser.FLAG_CORE_DATA;
6160            }
6161
6162            // update cache
6163            m_monitor.cacheUser(newUser);
6164
6165            // invalidate all user dependent caches
6166            m_monitor.flushCache(
6167                CmsMemoryMonitor.CacheType.ACL,
6168                CmsMemoryMonitor.CacheType.GROUP,
6169                CmsMemoryMonitor.CacheType.ORG_UNIT,
6170                CmsMemoryMonitor.CacheType.USER_LIST,
6171                CmsMemoryMonitor.CacheType.PERMISSION,
6172                CmsMemoryMonitor.CacheType.RESOURCE_LIST);
6173
6174            // fire user modified event
6175            Map<String, Object> eventData = new HashMap<String, Object>();
6176            eventData.put(I_CmsEventListener.KEY_USER_ID, newUser.getId().toString());
6177            eventData.put(I_CmsEventListener.KEY_USER_NAME, newUser.getName());
6178            eventData.put(I_CmsEventListener.KEY_USER_ACTION, I_CmsEventListener.VALUE_USER_MODIFIED_ACTION_WRITE_USER);
6179            eventData.put(I_CmsEventListener.KEY_USER_CHANGES, Integer.valueOf(changes));
6180            OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_USER_MODIFIED, eventData));
6181        }
6182
6183        // return the user object read from the driver
6184        return newUser.clone();
6185    }
6186
6187    /**
6188     * Lookup and read the user or group with the given UUID.<p>
6189     *
6190     * @param dbc the current database context
6191     * @param principalId the UUID of the principal to lookup
6192     *
6193     * @return the principal (group or user) if found, otherwise <code>null</code>
6194     */
6195    public I_CmsPrincipal lookupPrincipal(CmsDbContext dbc, CmsUUID principalId) {
6196
6197        try {
6198            CmsGroup group = getUserDriver(dbc).readGroup(dbc, principalId);
6199            if (group != null) {
6200                return group;
6201            }
6202        } catch (Exception e) {
6203            // ignore this exception
6204        }
6205
6206        try {
6207            CmsUser user = readUser(dbc, principalId);
6208            if (user != null) {
6209                return user;
6210            }
6211        } catch (Exception e) {
6212            // ignore this exception
6213        }
6214
6215        return null;
6216    }
6217
6218    /**
6219     * Lookup and read the user or group with the given name.<p>
6220     *
6221     * @param dbc the current database context
6222     * @param principalName the name of the principal to lookup
6223     *
6224     * @return the principal (group or user) if found, otherwise <code>null</code>
6225     */
6226    public I_CmsPrincipal lookupPrincipal(CmsDbContext dbc, String principalName) {
6227
6228        try {
6229            CmsGroup group = getUserDriver(dbc).readGroup(dbc, principalName);
6230            if (group != null) {
6231                return group;
6232            }
6233        } catch (Exception e) {
6234            // ignore this exception
6235        }
6236
6237        try {
6238            CmsUser user = readUser(dbc, principalName);
6239            if (user != null) {
6240                return user;
6241            }
6242        } catch (Exception e) {
6243            // ignore this exception
6244        }
6245
6246        return null;
6247    }
6248
6249    /**
6250     * Mark the given resource as visited by the user.<p>
6251     *
6252     * @param dbc the database context
6253     * @param poolName the name of the database pool to use
6254     * @param resource the resource to mark as visited
6255     * @param user the user that visited the resource
6256     *
6257     * @throws CmsException if something goes wrong
6258     */
6259    public void markResourceAsVisitedBy(CmsDbContext dbc, String poolName, CmsResource resource, CmsUser user)
6260    throws CmsException {
6261
6262        getSubscriptionDriver().markResourceAsVisitedBy(dbc, poolName, resource, user);
6263    }
6264
6265    /**
6266     * Moves a resource.<p>
6267     *
6268     * You must ensure that the parent of the destination path is an absolute, valid and
6269     * existing VFS path. Relative paths from the source are not supported.<p>
6270     *
6271     * The moved resource will always be locked to the current user
6272     * after the move operation.<p>
6273     *
6274     * In case the target resource already exists, it will be overwritten with the
6275     * source resource if possible.<p>
6276     *
6277     * @param dbc the current database context
6278     * @param source the resource to move
6279     * @param destination the name of the move destination with complete path
6280     * @param internal if set nothing more than the path is modified
6281     *
6282     * @throws CmsException if something goes wrong
6283     *
6284     * @see CmsSecurityManager#moveResource(CmsRequestContext, CmsResource, String)
6285     */
6286    public void moveResource(CmsDbContext dbc, CmsResource source, String destination, boolean internal)
6287    throws CmsException {
6288
6289        CmsFolder destinationFolder = readFolder(dbc, CmsResource.getParentFolder(destination), CmsResourceFilter.ALL);
6290        m_securityManager.checkPermissions(
6291            dbc,
6292            destinationFolder,
6293            CmsPermissionSet.ACCESS_WRITE,
6294            false,
6295            CmsResourceFilter.ALL);
6296
6297        if (source.isFolder()) {
6298            m_monitor.flushCache(CmsMemoryMonitor.CacheType.HAS_ROLE, CmsMemoryMonitor.CacheType.ROLE_LIST);
6299        }
6300        getVfsDriver(dbc).moveResource(dbc, dbc.getRequestContext().getCurrentProject().getUuid(), source, destination);
6301
6302        if (!internal) {
6303            CmsResourceState newState = CmsResource.STATE_CHANGED;
6304            if (source.getState().isNew()) {
6305                newState = CmsResource.STATE_NEW;
6306            } else if (source.getState().isDeleted()) {
6307                newState = CmsResource.STATE_DELETED;
6308            }
6309            source.setState(newState);
6310            // safe since this operation always uses the ids instead of the resource path
6311            getVfsDriver(dbc).writeResourceState(
6312                dbc,
6313                dbc.currentProject(),
6314                source,
6315                CmsDriverManager.UPDATE_STRUCTURE_STATE,
6316                false);
6317            // log it
6318            log(
6319                dbc,
6320                new CmsLogEntry(
6321                    dbc,
6322                    source.getStructureId(),
6323                    CmsLogEntryType.RESOURCE_MOVED,
6324                    new String[] {source.getRootPath(), destination}),
6325                false);
6326        }
6327
6328        CmsResource destRes = readResource(dbc, destination, CmsResourceFilter.ALL);
6329        // move lock
6330        m_lockManager.moveResource(source.getRootPath(), destRes.getRootPath());
6331
6332        // flush all relevant caches
6333        m_monitor.clearAccessControlListCache();
6334        m_monitor.flushCache(
6335            CmsMemoryMonitor.CacheType.PROPERTY,
6336            CmsMemoryMonitor.CacheType.PROPERTY_LIST,
6337            CmsMemoryMonitor.CacheType.PROJECT_RESOURCES);
6338
6339        List<CmsResource> resources = new ArrayList<CmsResource>(4);
6340        // source
6341        resources.add(source);
6342        try {
6343            resources.add(readFolder(dbc, CmsResource.getParentFolder(source.getRootPath()), CmsResourceFilter.ALL));
6344        } catch (Exception e) {
6345            if (LOG.isDebugEnabled()) {
6346                LOG.debug(e.getLocalizedMessage(), e);
6347            }
6348        }
6349        // destination
6350        resources.add(destRes);
6351        resources.add(destinationFolder);
6352
6353        Map<String, Object> eventData = new HashMap<String, Object>();
6354        eventData.put(I_CmsEventListener.KEY_RESOURCES, resources);
6355        eventData.put(I_CmsEventListener.KEY_DBCONTEXT, dbc);
6356
6357        // fire the events
6358        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_MOVED, eventData));
6359    }
6360
6361    /**
6362     * Moves a resource to the "lost and found" folder.<p>
6363     *
6364     * The method can also be used to check get the name of a resource
6365     * in the "lost and found" folder only without actually moving the
6366     * the resource. To do this, the <code>returnNameOnly</code> flag
6367     * must be set to <code>true</code>.<p>
6368     *
6369     * @param dbc the current database context
6370     * @param resource the resource to apply this operation to
6371     * @param returnNameOnly if <code>true</code>, only the name of the resource in the "lost and found"
6372     *        folder is returned, the move operation is not really performed
6373     *
6374     * @return the name of the resource inside the "lost and found" folder
6375     *
6376     * @throws CmsException if something goes wrong
6377     * @throws CmsIllegalArgumentException if the <code>resourcename</code> argument is null or of length 0
6378     *
6379     * @see CmsObject#moveToLostAndFound(String)
6380     * @see CmsObject#getLostAndFoundName(String)
6381     */
6382    public String moveToLostAndFound(CmsDbContext dbc, CmsResource resource, boolean returnNameOnly)
6383    throws CmsException, CmsIllegalArgumentException {
6384
6385        String resourcename = dbc.removeSiteRoot(resource.getRootPath());
6386
6387        String siteRoot = dbc.getRequestContext().getSiteRoot();
6388        dbc.getRequestContext().setSiteRoot("");
6389        String destination = CmsDriverManager.LOST_AND_FOUND_FOLDER + resourcename;
6390        // create the required folders if necessary
6391        try {
6392            // collect all folders...
6393            String folderPath = CmsResource.getParentFolder(destination);
6394            folderPath = folderPath.substring(1, folderPath.length() - 1); // cut out leading and trailing '/'
6395            Iterator<String> folders = CmsStringUtil.splitAsList(folderPath, '/').iterator();
6396            // ...now create them....
6397            folderPath = "/";
6398            while (folders.hasNext()) {
6399                folderPath += folders.next().toString() + "/";
6400                try {
6401                    readFolder(dbc, folderPath, CmsResourceFilter.IGNORE_EXPIRATION);
6402                } catch (Exception e1) {
6403                    if (returnNameOnly) {
6404                        // we can use the original name without risk, and we do not need to recreate the parent folders
6405                        break;
6406                    }
6407                    // the folder is not existing, so create it
6408                    createResource(
6409                        dbc,
6410                        folderPath,
6411                        CmsResourceTypeFolder.RESOURCE_TYPE_ID,
6412                        null,
6413                        new ArrayList<CmsProperty>());
6414                }
6415            }
6416            // check if this resource name does already exist
6417            // if so add a postfix to the name
6418            String des = destination;
6419            int postfix = 1;
6420            boolean found = true;
6421            while (found) {
6422                try {
6423                    // try to read the file.....
6424                    found = true;
6425                    readResource(dbc, des, CmsResourceFilter.ALL);
6426                    // ....it's there, so add a postfix and try again
6427                    String path = destination.substring(0, destination.lastIndexOf('/') + 1);
6428                    String filename = destination.substring(destination.lastIndexOf('/') + 1, destination.length());
6429
6430                    des = path;
6431
6432                    if (filename.lastIndexOf('.') > 0) {
6433                        des += filename.substring(0, filename.lastIndexOf('.'));
6434                    } else {
6435                        des += filename;
6436                    }
6437                    des += "_" + postfix;
6438                    if (filename.lastIndexOf('.') > 0) {
6439                        des += filename.substring(filename.lastIndexOf('.'), filename.length());
6440                    }
6441                    postfix++;
6442                } catch (CmsException e3) {
6443                    // the file does not exist, so we can use this filename
6444                    found = false;
6445                }
6446            }
6447            destination = des;
6448
6449            if (!returnNameOnly) {
6450                // do not use the move semantic here! to prevent links pointing to the lost & found folder
6451                copyResource(dbc, resource, destination, CmsResource.COPY_AS_SIBLING);
6452                deleteResource(dbc, resource, CmsResource.DELETE_PRESERVE_SIBLINGS);
6453            }
6454        } catch (CmsException e2) {
6455            throw e2;
6456        } finally {
6457            // set the site root to the old value again
6458            dbc.getRequestContext().setSiteRoot(siteRoot);
6459        }
6460        return destination;
6461    }
6462
6463    /**
6464     * Gets a new driver instance.<p>
6465     *
6466     * @param dbc the database context
6467     * @param configurationManager the configuration manager
6468     * @param driverName the driver name
6469     * @param successiveDrivers the list of successive drivers
6470     *
6471     * @return the driver object
6472     * @throws CmsInitException if the selected driver could not be initialized
6473     */
6474    public Object newDriverInstance(
6475        CmsDbContext dbc,
6476        CmsConfigurationManager configurationManager,
6477        String driverName,
6478        List<String> successiveDrivers)
6479    throws CmsInitException {
6480
6481        Class<?> driverClass = null;
6482        I_CmsDriver driver = null;
6483
6484        try {
6485            // try to get the class
6486            driverClass = Class.forName(driverName);
6487            if (CmsLog.INIT.isInfoEnabled()) {
6488                CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_DRIVER_START_1, driverName));
6489            }
6490
6491            // try to create a instance
6492            driver = (I_CmsDriver)driverClass.newInstance();
6493            if (CmsLog.INIT.isInfoEnabled()) {
6494                CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_DRIVER_INITIALIZING_1, driverName));
6495            }
6496
6497            // invoke the init-method of this access class
6498            driver.init(dbc, configurationManager, successiveDrivers, this);
6499            if (CmsLog.INIT.isInfoEnabled()) {
6500                CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_DRIVER_INIT_FINISHED_0));
6501            }
6502
6503        } catch (Throwable t) {
6504            CmsMessageContainer message = Messages.get().container(
6505                Messages.ERR_ERROR_INITIALIZING_DRIVER_1,
6506                driverName);
6507            if (LOG.isErrorEnabled()) {
6508                LOG.error(message.key(), t);
6509            }
6510            throw new CmsInitException(message, t);
6511        }
6512
6513        return driver;
6514    }
6515
6516    /**
6517     * Method to create a new instance of a driver.<p>
6518     *
6519     * @param configuration the configurations from the propertyfile
6520     * @param driverName the class name of the driver
6521     * @param driverPoolUrl the pool url for the driver
6522     * @return an initialized instance of the driver
6523     * @throws CmsException if something goes wrong
6524     */
6525    public Object newDriverInstance(CmsParameterConfiguration configuration, String driverName, String driverPoolUrl)
6526    throws CmsException {
6527
6528        Class<?>[] initParamClasses = {CmsParameterConfiguration.class, String.class, CmsDriverManager.class};
6529        Object[] initParams = {configuration, driverPoolUrl, this};
6530
6531        Class<?> driverClass = null;
6532        Object driver = null;
6533
6534        try {
6535            // try to get the class
6536            driverClass = Class.forName(driverName);
6537            if (CmsLog.INIT.isInfoEnabled()) {
6538                CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_DRIVER_START_1, driverName));
6539            }
6540
6541            // try to create a instance
6542            driver = driverClass.newInstance();
6543            if (CmsLog.INIT.isInfoEnabled()) {
6544                CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_DRIVER_INITIALIZING_1, driverName));
6545            }
6546
6547            // invoke the init-method of this access class
6548            driver.getClass().getMethod("init", initParamClasses).invoke(driver, initParams);
6549            if (CmsLog.INIT.isInfoEnabled()) {
6550                CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_DRIVER_INIT_FINISHED_1, driverPoolUrl));
6551            }
6552
6553        } catch (Exception exc) {
6554
6555            CmsMessageContainer message = Messages.get().container(Messages.ERR_INIT_DRIVER_MANAGER_1);
6556            if (LOG.isFatalEnabled()) {
6557                LOG.fatal(message.key(), exc);
6558            }
6559            throw new CmsDbException(message, exc);
6560
6561        }
6562
6563        return driver;
6564    }
6565
6566    /**
6567     * Method to create a new instance of a pool.<p>
6568     *
6569     * @param configuration the configurations from the propertyfile
6570     * @param poolName the configuration name of the pool
6571     *
6572     * @throws CmsInitException if the pools could not be initialized
6573     */
6574    public void newPoolInstance(CmsParameterConfiguration configuration, String poolName) throws CmsInitException {
6575
6576        CmsDbPoolV11 pool;
6577
6578        try {
6579            pool = new CmsDbPoolV11(configuration, poolName);
6580        } catch (Exception e) {
6581
6582            CmsMessageContainer message = Messages.get().container(Messages.ERR_INIT_CONN_POOL_1, poolName);
6583            if (LOG.isErrorEnabled()) {
6584                LOG.error(message.key(), e);
6585            }
6586            throw new CmsInitException(message, e);
6587        }
6588        addPool(pool);
6589    }
6590
6591    /**
6592     * Publishes the given publish job.<p>
6593     *
6594     * @param cms the cms context
6595     * @param dbc the db context
6596     * @param publishList the list of resources to publish
6597     * @param report the report to write to
6598     *
6599     * @throws CmsException if something goes wrong
6600     */
6601    public void publishJob(CmsObject cms, CmsDbContext dbc, CmsPublishList publishList, I_CmsReport report)
6602    throws CmsException {
6603
6604        try {
6605            // check state and lock
6606            List<CmsResource> allResources = new ArrayList<CmsResource>(publishList.getFolderList());
6607            allResources.addAll(publishList.getDeletedFolderList());
6608            allResources.addAll(publishList.getFileList());
6609            Iterator<CmsResource> itResources = allResources.iterator();
6610            while (itResources.hasNext()) {
6611                CmsResource resource = itResources.next();
6612                try {
6613                    resource = readResource(dbc, resource.getStructureId(), CmsResourceFilter.ALL);
6614                } catch (CmsVfsResourceNotFoundException e) {
6615                    continue;
6616                }
6617                if (resource.getState().isUnchanged()) {
6618                    // remove files that were published by a concurrent job
6619                    if (LOG.isDebugEnabled()) {
6620                        LOG.debug(
6621                            Messages.get().getBundle().key(
6622                                Messages.RPT_PUBLISH_REMOVED_RESOURCE_1,
6623                                dbc.removeSiteRoot(resource.getRootPath())));
6624                    }
6625                    publishList.remove(resource);
6626                    unlockResource(dbc, resource, true, true);
6627                    continue;
6628                }
6629                if (!CmsModificationContext.isInOnlineFolder(resource.getRootPath())) {
6630                    CmsLock lock = m_lockManager.getLock(dbc, resource, false);
6631                    if (!lock.getSystemLock().isPublish()) {
6632                        // remove files that are not locked for publishing
6633                        if (LOG.isDebugEnabled()) {
6634                            LOG.debug(
6635                                Messages.get().getBundle().key(
6636                                    Messages.RPT_PUBLISH_REMOVED_RESOURCE_1,
6637                                    dbc.removeSiteRoot(resource.getRootPath())));
6638                        }
6639                        publishList.remove(resource);
6640                        continue;
6641                    }
6642                }
6643            }
6644
6645            CmsProject onlineProject = readProject(dbc, CmsProject.ONLINE_PROJECT_ID);
6646
6647            // clear the cache
6648            m_monitor.clearCacheForPublishing();
6649
6650            int publishTag = getNextPublishTag(dbc);
6651            getProjectDriver(dbc).publishProject(dbc, report, onlineProject, publishList, publishTag);
6652
6653            // iterate the initialized module action instances
6654            Iterator<String> i = OpenCms.getModuleManager().getModuleNames().iterator();
6655            while (i.hasNext()) {
6656                CmsModule module = OpenCms.getModuleManager().getModule(i.next());
6657                if ((module != null) && (module.getActionInstance() != null)) {
6658                    module.getActionInstance().publishProject(cms, publishList, publishTag, report);
6659                }
6660            }
6661
6662            boolean temporaryProject = (cms.getRequestContext().getCurrentProject().getType() == CmsProject.PROJECT_TYPE_TEMPORARY);
6663            // the project was stored in the history tables for history
6664            // it will be deleted if the project_flag is PROJECT_TYPE_TEMPORARY
6665            if ((temporaryProject) && (!publishList.isDirectPublish())) {
6666                try {
6667                    getProjectDriver(dbc).deleteProject(dbc, dbc.currentProject());
6668                } catch (CmsException e) {
6669                    LOG.error(
6670                        Messages.get().getBundle().key(
6671                            Messages.LOG_DELETE_TEMP_PROJECT_FAILED_1,
6672                            cms.getRequestContext().getCurrentProject().getName()));
6673                }
6674                // if project was temporary set context to online project
6675                cms.getRequestContext().setCurrentProject(onlineProject);
6676            }
6677        } finally {
6678            // clear the cache again
6679            m_monitor.clearCacheForPublishing();
6680        }
6681    }
6682
6683    /**
6684     * Publishes the resources of a specified publish list.<p>
6685     *
6686     * @param cms the current request context
6687     * @param dbc the current database context
6688     * @param publishList a publish list
6689     * @param report an instance of <code>{@link I_CmsReport}</code> to print messages
6690     *
6691     * @throws CmsException if something goes wrong
6692     *
6693     * @see #fillPublishList(CmsDbContext, CmsPublishList)
6694     */
6695    public synchronized void publishProject(
6696        CmsObject cms,
6697        CmsDbContext dbc,
6698        CmsPublishList publishList,
6699        I_CmsReport report)
6700    throws CmsException {
6701
6702        // check the parent folders
6703        checkParentFolders(dbc, publishList);
6704        ensureSubResourcesOfMovedFoldersPublished(cms, dbc, publishList);
6705        OpenCms.getPublishManager().getPublishListVerifier().checkPublishList(publishList);
6706
6707        try {
6708            // fire an event that a project is to be published
6709            Map<String, Object> eventData = new HashMap<String, Object>();
6710            eventData.put(I_CmsEventListener.KEY_REPORT, report);
6711            eventData.put(I_CmsEventListener.KEY_PUBLISHLIST, publishList);
6712            eventData.put(I_CmsEventListener.KEY_PROJECTID, dbc.currentProject().getUuid());
6713            eventData.put(I_CmsEventListener.KEY_DBCONTEXT, dbc);
6714            CmsEvent beforePublishEvent = new CmsEvent(I_CmsEventListener.EVENT_BEFORE_PUBLISH_PROJECT, eventData);
6715            OpenCms.fireCmsEvent(beforePublishEvent);
6716        } catch (Throwable t) {
6717            if (report != null) {
6718                report.addError(t);
6719                report.println(t);
6720            }
6721            if (LOG.isErrorEnabled()) {
6722                LOG.error(t.getLocalizedMessage(), t);
6723            }
6724        }
6725
6726        // lock all resources with the special publish lock
6727        Iterator<CmsResource> itResources = new ArrayList<CmsResource>(publishList.getAllResources()).iterator();
6728        while (itResources.hasNext()) {
6729            CmsResource resource = itResources.next();
6730            CmsLock lock = m_lockManager.getLock(dbc, resource, false);
6731            if (lock.getSystemLock().isUnlocked() && lock.isLockableBy(dbc.currentUser())) {
6732                if (getLock(dbc, resource).getEditionLock().isNullLock()) {
6733                    lockResource(dbc, resource, CmsLockType.PUBLISH);
6734                } else {
6735                    changeLock(dbc, resource, CmsLockType.PUBLISH);
6736                }
6737            } else if (lock.getSystemLock().isPublish()) {
6738                if (LOG.isWarnEnabled()) {
6739                    LOG.warn(
6740                        Messages.get().getBundle().key(
6741                            Messages.RPT_PUBLISH_REMOVED_RESOURCE_1,
6742                            dbc.removeSiteRoot(resource.getRootPath())));
6743                }
6744                // remove files that are already waiting to be published
6745                publishList.remove(resource);
6746                continue;
6747            } else {
6748                // this is needed to fix TestPublishIsssues#testPublishScenarioE
6749                changeLock(dbc, resource, CmsLockType.PUBLISH);
6750            }
6751            // now re-check the lock state
6752            lock = m_lockManager.getLock(dbc, resource, false);
6753            if (!lock.getSystemLock().isPublish()) {
6754                if (report != null) {
6755                    report.println(
6756                        Messages.get().container(
6757                            Messages.RPT_PUBLISH_REMOVED_RESOURCE_1,
6758                            dbc.removeSiteRoot(resource.getRootPath())),
6759                        I_CmsReport.FORMAT_WARNING);
6760                }
6761                if (LOG.isWarnEnabled()) {
6762                    LOG.warn(
6763                        Messages.get().getBundle().key(
6764                            Messages.RPT_PUBLISH_REMOVED_RESOURCE_1,
6765                            dbc.removeSiteRoot(resource.getRootPath())));
6766                }
6767                // remove files that could not be locked
6768                publishList.remove(resource);
6769            }
6770        }
6771
6772        // enqueue the publish job
6773        CmsException enqueueException = null;
6774        try {
6775            m_publishEngine.enqueuePublishJob(cms, publishList, report);
6776        } catch (CmsException exc) {
6777            enqueueException = exc;
6778        }
6779
6780        // if an exception was raised, remove the publish locks
6781        // and throw the exception again
6782        if (enqueueException != null) {
6783            itResources = publishList.getAllResources().iterator();
6784            while (itResources.hasNext()) {
6785                CmsResource resource = itResources.next();
6786                CmsLock lock = m_lockManager.getLock(dbc, resource, false);
6787                if (lock.getSystemLock().isPublish()
6788                    && lock.getSystemLock().isOwnedInProjectBy(
6789                        cms.getRequestContext().getCurrentUser(),
6790                        cms.getRequestContext().getCurrentProject())) {
6791                    unlockResource(dbc, resource, true, true);
6792                }
6793            }
6794
6795            throw enqueueException;
6796        }
6797    }
6798
6799    /**
6800     * Transfers the new URL name mappings (if any) for a given resource to the online project.<p>
6801     *
6802     * @param dbc the current database context
6803     * @param res the resource whose new URL name mappings should be transferred to the online project
6804     *
6805     * @throws CmsDataAccessException if something goes wrong
6806     */
6807    public void publishUrlNameMapping(CmsDbContext dbc, CmsResource res) throws CmsDataAccessException {
6808
6809        I_CmsVfsDriver vfsDriver = getVfsDriver(dbc);
6810
6811        if (res.getState().isDeleted()) {
6812            // remove both offline and online mappings
6813            CmsUrlNameMappingFilter idFilter = CmsUrlNameMappingFilter.ALL.filterStructureId(res.getStructureId());
6814            vfsDriver.deleteUrlNameMappingEntries(dbc, true, idFilter);
6815            vfsDriver.deleteUrlNameMappingEntries(dbc, false, idFilter);
6816        } else {
6817            // copy the new entries to the online table
6818            List<CmsUrlNameMappingEntry> entries = vfsDriver.readUrlNameMappingEntries(
6819                dbc,
6820                false,
6821                CmsUrlNameMappingFilter.ALL.filterStructureId(res.getStructureId()).filterStates(
6822                    CmsUrlNameMappingEntry.MAPPING_STATUS_NEW,
6823                    CmsUrlNameMappingEntry.MAPPING_STATUS_REPLACE_ON_PUBLISH));
6824
6825            boolean isReplaceOnPublish = false;
6826            for (CmsUrlNameMappingEntry entry : entries) {
6827                isReplaceOnPublish |= entry.getState() == CmsUrlNameMappingEntry.MAPPING_STATUS_REPLACE_ON_PUBLISH;
6828            }
6829
6830            if (!entries.isEmpty()) {
6831
6832                long now = System.currentTimeMillis();
6833                if (isReplaceOnPublish) {
6834                    vfsDriver.deleteUrlNameMappingEntries(
6835                        dbc,
6836                        true,
6837                        CmsUrlNameMappingFilter.ALL.filterStructureId(res.getStructureId()));
6838                    vfsDriver.deleteUrlNameMappingEntries(
6839                        dbc,
6840                        false,
6841                        CmsUrlNameMappingFilter.ALL.filterStructureId(res.getStructureId()));
6842                }
6843
6844                for (CmsUrlNameMappingEntry entry : entries) {
6845                    CmsUrlNameMappingFilter nameFilter = CmsUrlNameMappingFilter.ALL.filterName(entry.getName());
6846                    if (!isReplaceOnPublish) { // we already handled the other case above
6847                        vfsDriver.deleteUrlNameMappingEntries(dbc, true, nameFilter);
6848                        vfsDriver.deleteUrlNameMappingEntries(dbc, false, nameFilter);
6849                    }
6850                }
6851                for (CmsUrlNameMappingEntry entry : entries) {
6852                    CmsUrlNameMappingEntry newEntry = new CmsUrlNameMappingEntry(
6853                        entry.getName(),
6854                        entry.getStructureId(),
6855                        entry.getState() == CmsUrlNameMappingEntry.MAPPING_STATUS_NEW
6856                        ? CmsUrlNameMappingEntry.MAPPING_STATUS_PUBLISHED
6857                        : CmsUrlNameMappingEntry.MAPPING_STATUS_REPLACE_ON_PUBLISH_PUBLISHED,
6858                        now,
6859                        entry.getLocale());
6860                    vfsDriver.addUrlNameMappingEntry(dbc, true, newEntry);
6861                    vfsDriver.addUrlNameMappingEntry(dbc, false, newEntry);
6862                }
6863            }
6864        }
6865    }
6866
6867    /**
6868     * Reads an access control entry from the cms.<p>
6869     *
6870     * The access control entries of a resource are readable by everyone.
6871     *
6872     * @param dbc the current database context
6873     * @param resource the resource
6874     * @param principal the id of a group or a user any other entity
6875     * @return an access control entry that defines the permissions of the entity for the given resource
6876     * @throws CmsException if something goes wrong
6877     */
6878    public CmsAccessControlEntry readAccessControlEntry(CmsDbContext dbc, CmsResource resource, CmsUUID principal)
6879    throws CmsException {
6880
6881        return getUserDriver(
6882            dbc).readAccessControlEntry(dbc, dbc.currentProject(), resource.getResourceId(), principal);
6883    }
6884
6885    /**
6886     * Finds the alias with a given path.<p>
6887     *
6888     * If no alias is found, null is returned.<p>
6889     *
6890     * @param dbc the current database context
6891     * @param project the current project
6892     * @param siteRoot the site root
6893     * @param path the path of the alias
6894     *
6895     * @return the alias with the given path
6896     *
6897     * @throws CmsException if something goes wrong
6898     */
6899
6900    public CmsAlias readAliasByPath(CmsDbContext dbc, CmsProject project, String siteRoot, String path)
6901    throws CmsException {
6902
6903        List<CmsAlias> aliases = getVfsDriver(dbc).readAliases(dbc, project, new CmsAliasFilter(siteRoot, path, null));
6904        if (aliases.isEmpty()) {
6905            return null;
6906        } else {
6907            return aliases.get(0);
6908        }
6909    }
6910
6911    /**
6912     * Reads the aliases for a given site root.<p>
6913     *
6914     * @param dbc the current database context
6915     * @param currentProject the current project
6916     * @param siteRoot the site root
6917     *
6918     * @return the list of aliases for the given site root
6919     *
6920     * @throws CmsException if something goes wrong
6921     */
6922    public List<CmsAlias> readAliasesBySite(CmsDbContext dbc, CmsProject currentProject, String siteRoot)
6923    throws CmsException {
6924
6925        return getVfsDriver(dbc).readAliases(dbc, currentProject, new CmsAliasFilter(siteRoot, null, null));
6926    }
6927
6928    /**
6929     * Reads the aliases which point to a given structure id.<p>
6930     *
6931     * @param dbc the current database context
6932     * @param project the current project
6933     * @param structureId the structure id for which we want to read the aliases
6934     *
6935     * @return the list of aliases pointing to the structure id
6936     * @throws CmsException if something goes wrong
6937     */
6938    public List<CmsAlias> readAliasesByStructureId(CmsDbContext dbc, CmsProject project, CmsUUID structureId)
6939    throws CmsException {
6940
6941        return getVfsDriver(dbc).readAliases(dbc, project, new CmsAliasFilter(null, null, structureId));
6942    }
6943
6944    /**
6945     * Reads all versions of the given resource.<br>
6946     *
6947     * This method returns a list with the history of the given resource, i.e.
6948     * the historical resource entries, independent of the project they were attached to.<br>
6949     *
6950     * The reading excludes the file content.<p>
6951     *
6952     * @param dbc the current database context
6953     * @param resource the resource to read the history for
6954     *
6955     * @return a list of file headers, as <code>{@link I_CmsHistoryResource}</code> objects
6956     *
6957     * @throws CmsException if something goes wrong
6958     */
6959    public List<I_CmsHistoryResource> readAllAvailableVersions(CmsDbContext dbc, CmsResource resource)
6960    throws CmsException {
6961
6962        // read the historical resources
6963        List<I_CmsHistoryResource> versions = getHistoryDriver(dbc).readAllAvailableVersions(
6964            dbc,
6965            resource.getStructureId());
6966        if ((versions.size() > OpenCms.getSystemInfo().getHistoryVersions())
6967            && (OpenCms.getSystemInfo().getHistoryVersions() > -1)) {
6968            return versions.subList(0, OpenCms.getSystemInfo().getHistoryVersions());
6969        }
6970        return versions;
6971    }
6972
6973    /**
6974     * Reads all property definitions for the given mapping type.<p>
6975     *
6976     * @param dbc the current database context
6977     *
6978     * @return a list with the <code>{@link CmsPropertyDefinition}</code> objects (may be empty)
6979     *
6980     * @throws CmsException if something goes wrong
6981     */
6982    public List<CmsPropertyDefinition> readAllPropertyDefinitions(CmsDbContext dbc) throws CmsException {
6983
6984        List<CmsPropertyDefinition> result = getVfsDriver(dbc).readPropertyDefinitions(
6985            dbc,
6986            dbc.currentProject().getUuid());
6987        Collections.sort(result);
6988        return result;
6989    }
6990
6991    /**
6992     * Returns all resources subscribed by the given user or group.<p>
6993     *
6994     * @param dbc the database context
6995     * @param poolName the name of the database pool to use
6996     * @param principal the principal to read the subscribed resources
6997     *
6998     * @return all resources subscribed by the given user or group
6999     *
7000     * @throws CmsException if something goes wrong
7001     */
7002    public List<CmsResource> readAllSubscribedResources(CmsDbContext dbc, String poolName, CmsPrincipal principal)
7003    throws CmsException {
7004
7005        List<CmsResource> result = getSubscriptionDriver().readAllSubscribedResources(dbc, poolName, principal);
7006        result = filterPermissions(dbc, result, CmsResourceFilter.DEFAULT);
7007        return result;
7008    }
7009
7010    /**
7011     * Selects the best url name for a given resource and locale.<p>
7012     *
7013     * @param dbc the database context
7014     * @param id the resource's structure id
7015     * @param locale the requested locale
7016     * @param defaultLocales the default locales to use if the locale isn't available
7017     *
7018     * @return the URL name which was found
7019     *
7020     * @throws CmsDataAccessException if the database operation failed
7021     */
7022    public String readBestUrlName(CmsDbContext dbc, CmsUUID id, Locale locale, List<Locale> defaultLocales)
7023    throws CmsDataAccessException {
7024
7025        List<CmsUrlNameMappingEntry> entries = getVfsDriver(dbc).readUrlNameMappingEntries(
7026            dbc,
7027            dbc.currentProject().isOnlineProject(),
7028            CmsUrlNameMappingFilter.ALL.filterStructureId(id));
7029        if (entries.isEmpty()) {
7030            return null;
7031        }
7032
7033        ArrayListMultimap<String, CmsUrlNameMappingEntry> entriesByLocale = ArrayListMultimap.create();
7034        for (CmsUrlNameMappingEntry entry : entries) {
7035            entriesByLocale.put(entry.getLocale(), entry);
7036        }
7037        List<CmsUrlNameMappingEntry> lastEntries = new ArrayList<CmsUrlNameMappingEntry>();
7038        Comparator<CmsUrlNameMappingEntry> dateChangedComparator = new UrlNameMappingComparator();
7039        for (String localeKey : entriesByLocale.keySet()) {
7040            // for each locale select the latest mapping entry
7041            CmsUrlNameMappingEntry latestEntryForLocale = Collections.max(
7042                entriesByLocale.get(localeKey),
7043                dateChangedComparator);
7044            lastEntries.add(latestEntryForLocale);
7045        }
7046        CmsLocaleManager localeManager = OpenCms.getLocaleManager();
7047        List<Locale> availableLocales = new ArrayList<Locale>();
7048        for (CmsUrlNameMappingEntry entry : lastEntries) {
7049            availableLocales.add(CmsLocaleManager.getLocale(entry.getLocale()));
7050        }
7051        Locale bestLocale = localeManager.getBestMatchingLocale(locale, defaultLocales, availableLocales);
7052        String bestLocaleStr = bestLocale.toString();
7053        for (CmsUrlNameMappingEntry entry : lastEntries) {
7054            if (entry.getLocale().equals(bestLocaleStr)) {
7055                return entry.getName();
7056            }
7057        }
7058        return null;
7059    }
7060
7061    /**
7062     * Returns the child resources of a resource, that is the resources
7063     * contained in a folder.<p>
7064     *
7065     * With the parameters <code>getFolders</code> and <code>getFiles</code>
7066     * you can control what type of resources you want in the result list:
7067     * files, folders, or both.<p>
7068     *
7069     * This method is mainly used by the workplace explorer.<p>
7070     *
7071     * @param dbc the current database context
7072     * @param resource the resource to return the child resources for
7073     * @param filter the resource filter to use
7074     * @param getFolders if true the child folders are included in the result
7075     * @param getFiles if true the child files are included in the result
7076     * @param checkPermissions if the resources should be filtered with the current user permissions
7077     *
7078     * @return a list of all child resources
7079     *
7080     * @throws CmsException if something goes wrong
7081     */
7082    public List<CmsResource> readChildResources(
7083        CmsDbContext dbc,
7084        CmsResource resource,
7085        CmsResourceFilter filter,
7086        boolean getFolders,
7087        boolean getFiles,
7088        boolean checkPermissions)
7089    throws CmsException {
7090
7091        String cacheKey = null;
7092        List<CmsResource> resourceList = null;
7093        if (m_monitor.isEnabled(CmsMemoryMonitor.CacheType.RESOURCE_LIST)) { // check this here to skip the complex cache key generation
7094            String time = "";
7095            if (checkPermissions) {
7096                // ensure correct caching if site time offset is set
7097                if ((dbc.getRequestContext() != null)
7098                    && (OpenCms.getSiteManager().getSiteForSiteRoot(dbc.getRequestContext().getSiteRoot()) != null)) {
7099                    time += OpenCms.getSiteManager().getSiteForSiteRoot(
7100                        dbc.getRequestContext().getSiteRoot()).getSiteMatcher().getTimeOffset();
7101                }
7102            }
7103            // try to get the sub resources from the cache
7104            cacheKey = getCacheKey(
7105                new String[] {
7106                    dbc.currentUser().getName(),
7107                    getFolders
7108                    ? (getFiles ? CmsCacheKey.CACHE_KEY_SUBALL : CmsCacheKey.CACHE_KEY_SUBFOLDERS)
7109                    : CmsCacheKey.CACHE_KEY_SUBFILES,
7110                    checkPermissions ? "+" + time : "-",
7111                    filter.getCacheId(),
7112                    resource.getRootPath()},
7113                dbc);
7114
7115            resourceList = m_monitor.getCachedResourceList(cacheKey);
7116        }
7117        if ((resourceList == null) || !dbc.getProjectId().isNullUUID()) {
7118            // read the result form the database
7119            resourceList = getVfsDriver(
7120                dbc).readChildResources(dbc, dbc.currentProject(), resource, getFolders, getFiles);
7121
7122            if (checkPermissions) {
7123                // apply the permission filter
7124                resourceList = filterPermissions(dbc, resourceList, filter);
7125            }
7126            // cache the sub resources
7127            if (dbc.getProjectId().isNullUUID()) {
7128                m_monitor.cacheResourceList(cacheKey, resourceList);
7129            }
7130        }
7131
7132        // we must always apply the result filter and update the context dates
7133        return updateContextDates(dbc, resourceList, filter);
7134    }
7135
7136    /**
7137     * Returns the default file for the given folder.<p>
7138     *
7139     * If the given resource is a file, then this file is returned.<p>
7140     *
7141     * Otherwise, in case of a folder:<br>
7142     * <ol>
7143     *   <li>the {@link CmsPropertyDefinition#PROPERTY_DEFAULT_FILE} is checked, and
7144     *   <li>if still no file could be found, the configured default files in the
7145     *       <code>opencms-vfs.xml</code> configuration are iterated until a match is
7146     *       found, and
7147     *   <li>if still no file could be found, <code>null</code> is retuned
7148     * </ol>
7149     *
7150     * @param dbc the database context
7151     * @param resource the folder to get the default file for
7152     * @param resourceFilter the resource filter
7153     *
7154     * @return the default file for the given folder
7155     */
7156    public CmsResource readDefaultFile(CmsDbContext dbc, CmsResource resource, CmsResourceFilter resourceFilter) {
7157
7158        // resource exists, lets check if we have a file or a folder
7159        if (resource.isFolder()) {
7160            // the resource is a folder, check if PROPERTY_DEFAULT_FILE is set on folder
7161            try {
7162                String defaultFileName = readPropertyObject(
7163                    dbc,
7164                    resource,
7165                    CmsPropertyDefinition.PROPERTY_DEFAULT_FILE,
7166                    false).getValue();
7167                // check if the default file property does not match the navigation level folder marker value
7168                if ((defaultFileName != null) && !CmsJspNavBuilder.NAVIGATION_LEVEL_FOLDER.equals(defaultFileName)) {
7169                    // property was set, so look up this file first
7170                    String folderName = CmsResource.getFolderPath(resource.getRootPath());
7171                    resource = readResource(dbc, folderName + defaultFileName, resourceFilter.addRequireFile());
7172                }
7173            } catch (CmsException e) {
7174                // ignore all other exceptions and continue the lookup process
7175                if (LOG.isDebugEnabled()) {
7176                    LOG.debug(e.getLocalizedMessage(), e);
7177                }
7178            }
7179            if (resource.isFolder()) {
7180                String folderName = CmsResource.getFolderPath(resource.getRootPath());
7181                // resource is (still) a folder, check default files specified in configuration
7182                Iterator<String> it = OpenCms.getDefaultFiles().iterator();
7183                while (it.hasNext()) {
7184                    String tmpResourceName = folderName + it.next();
7185                    try {
7186                        resource = readResource(dbc, tmpResourceName, resourceFilter.addRequireFile());
7187                        // no exception? So we have found the default file
7188                        // stop looking for default files
7189                        break;
7190                    } catch (CmsException e) {
7191                        // ignore all other exceptions and continue the lookup process
7192                        if (LOG.isDebugEnabled()) {
7193                            LOG.debug(e.getLocalizedMessage(), e);
7194                        }
7195                    }
7196                }
7197            }
7198        }
7199        if (resource.isFolder()) {
7200            // we only want files as a result for further processing
7201            resource = null;
7202        }
7203        return resource;
7204    }
7205
7206    /**
7207     * Reads all deleted (historical) resources below the given path,
7208     * including the full tree below the path, if required.<p>
7209     *
7210     * @param dbc the current db context
7211     * @param resource the parent resource to read the resources from
7212     * @param readTree <code>true</code> to read all subresources
7213     * @param isVfsManager <code>true</code> if the current user has the vfs manager role
7214     *
7215     * @return a list of <code>{@link I_CmsHistoryResource}</code> objects
7216     *
7217     * @throws CmsException if something goes wrong
7218     *
7219     * @see CmsObject#readResource(CmsUUID, int)
7220     * @see CmsObject#readResources(String, CmsResourceFilter, boolean)
7221     * @see CmsObject#readDeletedResources(String, boolean)
7222     */
7223    public List<I_CmsHistoryResource> readDeletedResources(
7224        CmsDbContext dbc,
7225        CmsResource resource,
7226        boolean readTree,
7227        boolean isVfsManager)
7228    throws CmsException {
7229
7230        Set<I_CmsHistoryResource> result = new HashSet<I_CmsHistoryResource>();
7231        List<I_CmsHistoryResource> deletedResources;
7232        dbc.getRequestContext().setAttribute("ATTR_RESOURCE_NAME", resource.getRootPath());
7233        try {
7234            deletedResources = getHistoryDriver(dbc).readDeletedResources(
7235                dbc,
7236                resource.getStructureId(),
7237                isVfsManager ? null : dbc.currentUser().getId());
7238        } finally {
7239            dbc.getRequestContext().removeAttribute("ATTR_RESOURCE_NAME");
7240        }
7241        result.addAll(deletedResources);
7242        Set<I_CmsHistoryResource> newResult = new HashSet<I_CmsHistoryResource>(result.size());
7243        I_CmsVfsDriver vfsDriver = getVfsDriver(dbc);
7244        Iterator<I_CmsHistoryResource> it = result.iterator();
7245        while (it.hasNext()) {
7246            I_CmsHistoryResource histRes = it.next();
7247            // adjust the paths
7248            try {
7249                if (vfsDriver.validateStructureIdExists(
7250                    dbc,
7251                    dbc.currentProject().getUuid(),
7252                    histRes.getStructureId())) {
7253                    newResult.add(histRes);
7254                    continue;
7255                }
7256                // adjust the path in case of deleted files
7257                String resourcePath = histRes.getRootPath();
7258                String resName = CmsResource.getName(resourcePath);
7259                String path = CmsResource.getParentFolder(resourcePath);
7260
7261                CmsUUID parentId = histRes.getParentId();
7262                try {
7263                    // first look for the path through the parent id
7264                    path = readResource(dbc, parentId, CmsResourceFilter.IGNORE_EXPIRATION).getRootPath();
7265                } catch (CmsDataAccessException e) {
7266                    // if the resource with the parent id is not found, try to get a new parent id with the path
7267                    try {
7268                        parentId = readResource(dbc, path, CmsResourceFilter.IGNORE_EXPIRATION).getStructureId();
7269                    } catch (CmsDataAccessException e1) {
7270                        // ignore, the parent folder has been completely deleted
7271                    }
7272                }
7273                resourcePath = path + resName;
7274
7275                boolean isFolder = resourcePath.endsWith("/");
7276                if (isFolder) {
7277                    newResult.add(
7278                        new CmsHistoryFolder(
7279                            histRes.getPublishTag(),
7280                            histRes.getStructureId(),
7281                            histRes.getResourceId(),
7282                            resourcePath,
7283                            histRes.getTypeId(),
7284                            histRes.getFlags(),
7285                            histRes.getProjectLastModified(),
7286                            histRes.getState(),
7287                            histRes.getDateCreated(),
7288                            histRes.getUserCreated(),
7289                            histRes.getDateLastModified(),
7290                            histRes.getUserLastModified(),
7291                            histRes.getDateReleased(),
7292                            histRes.getDateExpired(),
7293                            histRes.getVersion(),
7294                            parentId,
7295                            histRes.getResourceVersion(),
7296                            histRes.getStructureVersion()));
7297                } else {
7298                    newResult.add(
7299                        new CmsHistoryFile(
7300                            histRes.getPublishTag(),
7301                            histRes.getStructureId(),
7302                            histRes.getResourceId(),
7303                            resourcePath,
7304                            histRes.getTypeId(),
7305                            histRes.getFlags(),
7306                            histRes.getProjectLastModified(),
7307                            histRes.getState(),
7308                            histRes.getDateCreated(),
7309                            histRes.getUserCreated(),
7310                            histRes.getDateLastModified(),
7311                            histRes.getUserLastModified(),
7312                            histRes.getDateReleased(),
7313                            histRes.getDateExpired(),
7314                            histRes.getLength(),
7315                            histRes.getDateContent(),
7316                            histRes.getVersion(),
7317                            parentId,
7318                            null,
7319                            histRes.getResourceVersion(),
7320                            histRes.getStructureVersion()));
7321                }
7322            } catch (CmsDataAccessException e) {
7323                // should never happen
7324                if (LOG.isErrorEnabled()) {
7325                    LOG.error(e.getLocalizedMessage(), e);
7326                }
7327            }
7328        }
7329        if (readTree) {
7330            Iterator<I_CmsHistoryResource> itDeleted = deletedResources.iterator();
7331            while (itDeleted.hasNext()) {
7332                I_CmsHistoryResource delResource = itDeleted.next();
7333                if (delResource.isFolder()) {
7334                    newResult.addAll(readDeletedResources(dbc, (CmsFolder)delResource, readTree, isVfsManager));
7335                }
7336            }
7337            try {
7338                readResource(dbc, resource.getStructureId(), CmsResourceFilter.ALL);
7339                // resource exists, so recurse
7340                Iterator<CmsResource> itResources = readResources(
7341                    dbc,
7342                    resource,
7343                    CmsResourceFilter.ALL.addRequireFolder(),
7344                    readTree).iterator();
7345                while (itResources.hasNext()) {
7346                    CmsResource subResource = itResources.next();
7347                    if (subResource.isFolder()) {
7348                        newResult.addAll(readDeletedResources(dbc, subResource, readTree, isVfsManager));
7349                    }
7350                }
7351            } catch (Exception e) {
7352                // resource does not exists
7353                if (LOG.isDebugEnabled()) {
7354                    LOG.debug(e.getLocalizedMessage(), e);
7355                }
7356            }
7357        }
7358        List<I_CmsHistoryResource> finalRes = new ArrayList<I_CmsHistoryResource>(newResult);
7359        Collections.sort(finalRes, I_CmsResource.COMPARE_ROOT_PATH);
7360        return finalRes;
7361    }
7362
7363    /**
7364     * Reads a file resource (including it's binary content) from the VFS,
7365     * using the specified resource filter.<p>
7366     *
7367     * In case you do not need the file content,
7368     * use <code>{@link #readResource(CmsDbContext, String, CmsResourceFilter)}</code> instead.<p>
7369     *
7370     * The specified filter controls what kind of resources should be "found"
7371     * during the read operation. This will depend on the application. For example,
7372     * using <code>{@link CmsResourceFilter#DEFAULT}</code> will only return currently
7373     * "valid" resources, while using <code>{@link CmsResourceFilter#IGNORE_EXPIRATION}</code>
7374     * will ignore the date release / date expired information of the resource.<p>
7375     *
7376     * @param dbc the current database context
7377     * @param resource the base file resource (without content)
7378     * @return the file read from the VFS
7379     * @throws CmsException if operation was not successful
7380     */
7381    public CmsFile readFile(CmsDbContext dbc, CmsResource resource) throws CmsException {
7382
7383        checkFileResource(dbc, resource);
7384
7385        CmsUUID projectId = dbc.currentProject().getUuid();
7386        CmsFile file = null;
7387        if (resource instanceof I_CmsHistoryResource) {
7388            file = new CmsHistoryFile((I_CmsHistoryResource)resource);
7389            file.setContents(readHistoryContent(dbc, resource));
7390        } else {
7391            file = new CmsFile(resource);
7392            file.setContents(getVfsDriver(dbc).readContent(dbc, projectId, resource.getResourceId()));
7393        }
7394        return file;
7395    }
7396
7397    /**
7398     * Reads a file resource content from the VFS and passes it to an input stream handler.<p>
7399     *
7400     * @param dbc the current database context
7401     * @param resource the base file resource
7402     * @param handler the stream handler
7403     *
7404     * @throws CmsException if operation was not successful
7405     */
7406    public void readFileContentFrom(CmsDbContext dbc, CmsResource resource, I_CmsFileContentStreamHandler handler)
7407    throws CmsException {
7408
7409        checkFileResource(dbc, resource);
7410        CmsUUID projectId = dbc.currentProject().getUuid();
7411        try {
7412            if (resource instanceof I_CmsHistoryResource) {
7413                try (ByteArrayInputStream in = new ByteArrayInputStream(readHistoryContent(dbc, resource))) {
7414                    handler.read(in);
7415                }
7416            } else {
7417                getVfsDriver(dbc).readContentFrom(dbc, projectId, resource.getResourceId(), handler);
7418            }
7419        } catch (IOException e) {
7420            throw new CmsDbIoException(Messages.get().container(Messages.ERR_READ_FILE_1, resource.getRootPath()), e);
7421        } catch (CmsException e) {
7422            throw e;
7423        } catch (Exception e) {
7424            throw new CmsDbIoException(Messages.get().container(Messages.ERR_READ_FILE_1, resource.getRootPath()), e);
7425        }
7426    }
7427
7428    /**
7429     * Reads a file resource content from the VFS and writes it to an output stream.<p>
7430     *
7431     * @param dbc the current database context
7432     * @param resource the base file resource
7433     * @param out the output stream to write to
7434     *
7435     * @throws CmsException if operation was not successful
7436     */
7437    public void readFileContentTo(CmsDbContext dbc, CmsResource resource, OutputStream out) throws CmsException {
7438
7439        checkFileResource(dbc, resource);
7440        CmsUUID projectId = dbc.currentProject().getUuid();
7441        try {
7442            if (resource instanceof I_CmsHistoryResource) {
7443                out.write(readHistoryContent(dbc, resource));
7444            } else {
7445                getVfsDriver(dbc).readContentTo(dbc, projectId, resource.getResourceId(), out);
7446            }
7447        } catch (IOException e) {
7448            throw new CmsDbIoException(Messages.get().container(Messages.ERR_READ_FILE_1, resource.getRootPath()), e);
7449        }
7450    }
7451
7452    /**
7453     * Reads a folder from the VFS,
7454     * using the specified resource filter.<p>
7455     *
7456     * @param dbc the current database context
7457     * @param resourcename the name of the folder to read (full path)
7458     * @param filter the resource filter to use while reading
7459     *
7460     * @return the folder that was read
7461     *
7462     * @throws CmsDataAccessException if something goes wrong
7463     *
7464     * @see #readResource(CmsDbContext, String, CmsResourceFilter)
7465     * @see CmsObject#readFolder(String)
7466     * @see CmsObject#readFolder(String, CmsResourceFilter)
7467     */
7468    public CmsFolder readFolder(CmsDbContext dbc, String resourcename, CmsResourceFilter filter)
7469    throws CmsDataAccessException {
7470
7471        CmsResource resource = readResource(dbc, resourcename, filter);
7472
7473        return convertResourceToFolder(resource);
7474    }
7475
7476    public List<CmsFolderSizeEntry> readFolderSizeStats(CmsDbContext dbc, CmsFolderSizeOptions options)
7477    throws CmsException {
7478
7479        I_CmsVfsDriver vfsDriver = getVfsDriver(dbc);
7480        List<CmsFolderSizeEntry> stats = vfsDriver.readFolderSizeStats(dbc, false, options);
7481        return stats;
7482
7483    }
7484
7485    /**
7486     * Reads the group of a project.<p>
7487     *
7488     * @param dbc the current database context
7489     * @param project the project to read from
7490     *
7491     * @return the group of a resource
7492     */
7493    public CmsGroup readGroup(CmsDbContext dbc, CmsProject project) {
7494
7495        try {
7496            return readGroup(dbc, project.getGroupId());
7497        } catch (CmsException exc) {
7498            return new CmsGroup(
7499                CmsUUID.getNullUUID(),
7500                CmsUUID.getNullUUID(),
7501                project.getGroupId() + "",
7502                "deleted group",
7503                0);
7504        }
7505    }
7506
7507    /**
7508     * Reads a group based on its id.<p>
7509     *
7510     * @param dbc the current database context
7511     * @param groupId the id of the group that is to be read
7512     *
7513     * @return the requested group
7514     *
7515     * @throws CmsException if operation was not successful
7516     */
7517    public CmsGroup readGroup(CmsDbContext dbc, CmsUUID groupId) throws CmsException {
7518
7519        CmsGroup group = null;
7520        // try to read group from cache
7521        group = m_monitor.getCachedGroup(groupId.toString());
7522        if (group == null) {
7523            group = getUserDriver(dbc).readGroup(dbc, groupId);
7524            m_monitor.cacheGroup(group);
7525        }
7526        return group;
7527    }
7528
7529    /**
7530     * Reads a group based on its name.<p>
7531     *
7532     * @param dbc the current database context
7533     * @param groupname the name of the group that is to be read
7534     *
7535     * @return the requested group
7536     *
7537     * @throws CmsDataAccessException if operation was not successful
7538     */
7539    public CmsGroup readGroup(CmsDbContext dbc, String groupname) throws CmsDataAccessException {
7540
7541        CmsGroup group = null;
7542        // try to read group from cache
7543        group = m_monitor.getCachedGroup(groupname);
7544        if (group == null) {
7545            group = getUserDriver(dbc).readGroup(dbc, groupname);
7546            m_monitor.cacheGroup(group);
7547        }
7548        return group;
7549    }
7550
7551    /**
7552     * Reads a principal (an user or group) from the historical archive based on its ID.<p>
7553     *
7554     * @param dbc the current database context
7555     * @param principalId the id of the principal to read
7556     *
7557     * @return the historical principal entry with the given id
7558     *
7559     * @throws CmsException if something goes wrong, ie. {@link CmsDbEntryNotFoundException}
7560     *
7561     * @see CmsObject#readUser(CmsUUID)
7562     * @see CmsObject#readGroup(CmsUUID)
7563     * @see CmsObject#readHistoryPrincipal(CmsUUID)
7564     */
7565    public CmsHistoryPrincipal readHistoricalPrincipal(CmsDbContext dbc, CmsUUID principalId) throws CmsException {
7566
7567        return getHistoryDriver(dbc).readPrincipal(dbc, principalId);
7568    }
7569
7570    /**
7571     * Returns the latest historical project entry with the given id.<p>
7572     *
7573     * @param dbc the current database context
7574     * @param projectId the project id
7575     *
7576     * @return the requested historical project entry
7577     *
7578     * @throws CmsException if something goes wrong
7579     */
7580    public CmsHistoryProject readHistoryProject(CmsDbContext dbc, CmsUUID projectId) throws CmsException {
7581
7582        return getHistoryDriver(dbc).readProject(dbc, projectId);
7583    }
7584
7585    /**
7586     * Returns a historical project entry.<p>
7587     *
7588     * @param dbc the current database context
7589     * @param publishTag the publish tag of the project
7590     *
7591     * @return the requested historical project entry
7592     *
7593     * @throws CmsException if something goes wrong
7594     */
7595    public CmsHistoryProject readHistoryProject(CmsDbContext dbc, int publishTag) throws CmsException {
7596
7597        return getHistoryDriver(dbc).readProject(dbc, publishTag);
7598    }
7599
7600    /**
7601     * Reads the list of all <code>{@link CmsProperty}</code> objects that belongs to the given historical resource.<p>
7602     *
7603     * @param dbc the current database context
7604     * @param historyResource the historical resource to read the properties for
7605     *
7606     * @return the list of <code>{@link CmsProperty}</code> objects
7607     *
7608     * @throws CmsException if something goes wrong
7609     */
7610    public List<CmsProperty> readHistoryPropertyObjects(CmsDbContext dbc, I_CmsHistoryResource historyResource)
7611    throws CmsException {
7612
7613        return getHistoryDriver(dbc).readProperties(dbc, historyResource);
7614    }
7615
7616    /**
7617     * Reads the structure id which is mapped to a given URL name.<p>
7618     *
7619     * @param dbc the current database context
7620     * @param name the name for which the mapped structure id should be looked up
7621     *
7622     * @return the structure id which is mapped to the given name, or null if there is no such id
7623     *
7624     * @throws CmsDataAccessException if something goes wrong
7625     */
7626    public CmsUUID readIdForUrlName(CmsDbContext dbc, String name) throws CmsDataAccessException {
7627
7628        List<CmsUrlNameMappingEntry> entries = getVfsDriver(dbc).readUrlNameMappingEntries(
7629            dbc,
7630            dbc.currentProject().isOnlineProject(),
7631            CmsUrlNameMappingFilter.ALL.filterName(name));
7632        if (entries.isEmpty()) {
7633            return null;
7634        }
7635        return entries.get(0).getStructureId();
7636    }
7637
7638    /**
7639     * Reads the locks that were saved to the database in the previous run of OpenCms.<p>
7640     *
7641     * @param dbc the current database context
7642     *
7643     * @throws CmsException if something goes wrong
7644     */
7645    public void readLocks(CmsDbContext dbc) throws CmsException {
7646
7647        m_lockManager.readLocks(dbc);
7648    }
7649
7650    /**
7651     * Reads the manager group of a project.<p>
7652     *
7653     * @param dbc the current database context
7654     * @param project the project to read from
7655     *
7656     * @return the group of a resource
7657     */
7658    public CmsGroup readManagerGroup(CmsDbContext dbc, CmsProject project) {
7659
7660        try {
7661            return readGroup(dbc, project.getManagerGroupId());
7662        } catch (CmsException exc) {
7663            // the group does not exist any more - return a dummy-group
7664            return new CmsGroup(
7665                CmsUUID.getNullUUID(),
7666                CmsUUID.getNullUUID(),
7667                project.getManagerGroupId() + "",
7668                "deleted group",
7669                0);
7670        }
7671    }
7672
7673    /**
7674     * Reads the URL name which has been most recently mapped to the given structure id, or null
7675     * if no URL name is mapped to the id.<p>
7676     *
7677     * @param dbc the current database context
7678     * @param id a structure id
7679     * @return the name which has been most recently mapped to the given structure id
7680     *
7681     * @throws CmsDataAccessException if something goes wrong
7682     */
7683    public String readNewestUrlNameForId(CmsDbContext dbc, CmsUUID id) throws CmsDataAccessException {
7684
7685        List<CmsUrlNameMappingEntry> entries = getVfsDriver(dbc).readUrlNameMappingEntries(
7686            dbc,
7687            dbc.currentProject().isOnlineProject(),
7688            CmsUrlNameMappingFilter.ALL.filterStructureId(id));
7689        if (entries.isEmpty()) {
7690            return null;
7691        }
7692
7693        Collections.sort(entries, new UrlNameMappingComparator());
7694        CmsUrlNameMappingEntry lastEntry = entries.get(entries.size() - 1);
7695        return lastEntry.getName();
7696    }
7697
7698    /**
7699     * Reads an organizational Unit based on its fully qualified name.<p>
7700     *
7701     * @param dbc the current db context
7702     * @param ouFqn the fully qualified name of the organizational Unit to be read
7703     *
7704     * @return the organizational Unit that with the provided fully qualified name
7705     *
7706     * @throws CmsException if something goes wrong
7707     */
7708    public CmsOrganizationalUnit readOrganizationalUnit(CmsDbContext dbc, String ouFqn) throws CmsException {
7709
7710        CmsOrganizationalUnit organizationalUnit = null;
7711        // try to read organizational unit from cache
7712        organizationalUnit = m_monitor.getCachedOrgUnit(ouFqn);
7713        if (organizationalUnit == null) {
7714            organizationalUnit = getUserDriver(dbc).readOrganizationalUnit(dbc, ouFqn);
7715            m_monitor.cacheOrgUnit(organizationalUnit);
7716        }
7717        return organizationalUnit;
7718    }
7719
7720    /**
7721     * Reads the owner of a project.<p>
7722     *
7723     * @param dbc the current database context
7724     * @param project the project to get the owner from
7725     *
7726     * @return the owner of a resource
7727     * @throws CmsException if something goes wrong
7728     */
7729    public CmsUser readOwner(CmsDbContext dbc, CmsProject project) throws CmsException {
7730
7731        return readUser(dbc, project.getOwnerId());
7732    }
7733
7734    /**
7735     * Reads the parent folder to a given structure id.<p>
7736     *
7737     * @param dbc the current database context
7738     * @param structureId the structure id of the child
7739     *
7740     * @return the parent folder resource
7741     *
7742     * @throws CmsDataAccessException if something goes wrong
7743     */
7744    public CmsResource readParentFolder(CmsDbContext dbc, CmsUUID structureId) throws CmsDataAccessException {
7745
7746        return getVfsDriver(dbc).readParentFolder(dbc, dbc.currentProject().getUuid(), structureId);
7747    }
7748
7749    /**
7750     * Builds a list of resources for a given path.<p>
7751     *
7752     * @param dbc the current database context
7753     * @param path the requested path
7754     * @param filter a filter object (only "includeDeleted" information is used!)
7755     *
7756     * @return list of <code>{@link CmsResource}</code>s
7757     *
7758     * @throws CmsException if something goes wrong
7759     */
7760    public List<CmsResource> readPath(CmsDbContext dbc, String path, CmsResourceFilter filter) throws CmsException {
7761
7762        // splits the path into folder and filename tokens
7763        List<String> tokens = CmsStringUtil.splitAsList(path, '/');
7764
7765        // the root folder is no token in the path but a resource which has to be added to the path
7766        int count = tokens.size() + 1;
7767        // holds the CmsResource instances in the path
7768        List<CmsResource> pathList = new ArrayList<CmsResource>(count);
7769
7770        // true if the path doesn't end with a folder
7771        boolean lastResourceIsFile = false;
7772        // number of folders in the path
7773        int folderCount = count;
7774        if (!path.endsWith("/")) {
7775            folderCount--;
7776            lastResourceIsFile = true;
7777        }
7778
7779        // read the root folder, because it's ID is required to read any sub-resources
7780        String currentResourceName = "/";
7781        StringBuffer currentPath = new StringBuffer(64);
7782        currentPath.append('/');
7783
7784        String cp = currentPath.toString();
7785        CmsUUID projectId = getProjectIdForContext(dbc);
7786
7787        // key to cache the resources
7788        String cacheKey = getCacheKey(null, false, projectId, cp);
7789        // the current resource
7790        CmsResource currentResource = m_monitor.getCachedResource(cacheKey);
7791        if ((currentResource == null) || !dbc.getProjectId().isNullUUID()) {
7792            currentResource = getVfsDriver(dbc).readFolder(dbc, projectId, cp);
7793            if (dbc.getProjectId().isNullUUID()) {
7794                m_monitor.cacheResource(cacheKey, currentResource);
7795            }
7796        }
7797
7798        pathList.add(0, currentResource);
7799
7800        if (count == 1) {
7801            // the root folder was requested- no further operations required
7802            return pathList;
7803        }
7804
7805        Iterator<String> it = tokens.iterator();
7806        currentResourceName = it.next();
7807
7808        // read the folder resources in the path /a/b/c/
7809        int i = 0;
7810        for (i = 1; i < folderCount; i++) {
7811            currentPath.append(currentResourceName);
7812            currentPath.append('/');
7813            // read the folder
7814            cp = currentPath.toString();
7815            cacheKey = getCacheKey(null, false, projectId, cp);
7816            currentResource = m_monitor.getCachedResource(cacheKey);
7817            if ((currentResource == null) || !dbc.getProjectId().isNullUUID()) {
7818                currentResource = getVfsDriver(dbc).readFolder(dbc, projectId, cp);
7819                if (dbc.getProjectId().isNullUUID()) {
7820                    m_monitor.cacheResource(cacheKey, currentResource);
7821                }
7822            }
7823
7824            pathList.add(i, currentResource);
7825
7826            if (i < (folderCount - 1)) {
7827                currentResourceName = it.next();
7828            }
7829        }
7830
7831        // read the (optional) last file resource in the path /x.html
7832        if (lastResourceIsFile) {
7833            if (it.hasNext()) {
7834                // this will only be false if a resource in the
7835                // top level root folder (e.g. "/index.html") was requested
7836                currentResourceName = it.next();
7837            }
7838            currentPath.append(currentResourceName);
7839
7840            // read the file
7841            cp = currentPath.toString();
7842            cacheKey = getCacheKey(null, false, projectId, cp);
7843            currentResource = m_monitor.getCachedResource(cacheKey);
7844            if ((currentResource == null) || !dbc.getProjectId().isNullUUID()) {
7845                currentResource = getVfsDriver(dbc).readResource(dbc, projectId, cp, filter.includeDeleted());
7846                if (dbc.getProjectId().isNullUUID()) {
7847                    m_monitor.cacheResource(cacheKey, currentResource);
7848                }
7849            }
7850
7851            pathList.add(i, currentResource);
7852        }
7853
7854        return pathList;
7855    }
7856
7857    /**
7858     * Reads a project given the projects id.<p>
7859     *
7860     * @param dbc the current database context
7861     * @param id the id of the project
7862     *
7863     * @return the project read
7864     *
7865     * @throws CmsDataAccessException if something goes wrong
7866     */
7867    public CmsProject readProject(CmsDbContext dbc, CmsUUID id) throws CmsDataAccessException {
7868
7869        CmsProject project = null;
7870        project = m_monitor.getCachedProject(id.toString());
7871        if (project == null) {
7872            project = getProjectDriver(dbc).readProject(dbc, id);
7873            m_monitor.cacheProject(project);
7874        }
7875        return project;
7876    }
7877
7878    /**
7879     * Reads a project.<p>
7880     *
7881     * Important: Since a project name can be used multiple times, this is NOT the most efficient
7882     * way to read the project. This is only a convenience for front end developing.
7883     * Reading a project by name will return the first project with that name.
7884     * All core classes must use the id version {@link #readProject(CmsDbContext, CmsUUID)} to ensure the right project is read.<p>
7885     *
7886     * @param dbc the current database context
7887     * @param name the name of the project
7888     *
7889     * @return the project read
7890     *
7891     * @throws CmsException if something goes wrong
7892     */
7893    public CmsProject readProject(CmsDbContext dbc, String name) throws CmsException {
7894
7895        CmsProject project = null;
7896        project = m_monitor.getCachedProject(name);
7897        if (project == null) {
7898            project = getProjectDriver(dbc).readProject(dbc, name);
7899            m_monitor.cacheProject(project);
7900        }
7901        return project;
7902    }
7903
7904    /**
7905     * Returns the list of all resource names that define the "view" of the given project.<p>
7906     *
7907     * @param dbc the current database context
7908     * @param project the project to get the project resources for
7909     *
7910     * @return the list of all resources, as <code>{@link String}</code> objects
7911     *              that define the "view" of the given project.
7912     *
7913     * @throws CmsException if something goes wrong
7914     */
7915    public List<String> readProjectResources(CmsDbContext dbc, CmsProject project) throws CmsException {
7916
7917        return getProjectDriver(dbc).readProjectResources(dbc, project);
7918    }
7919
7920    /**
7921     * Reads all resources of a project that match a given state from the VFS.<p>
7922     *
7923     * Possible values for the <code>state</code> parameter are:<br>
7924     * <ul>
7925     * <li><code>{@link CmsResource#STATE_CHANGED}</code>: Read all "changed" resources in the project</li>
7926     * <li><code>{@link CmsResource#STATE_NEW}</code>: Read all "new" resources in the project</li>
7927     * <li><code>{@link CmsResource#STATE_DELETED}</code>: Read all "deleted" resources in the project</li>
7928     * <li><code>{@link CmsResource#STATE_KEEP}</code>: Read all resources either "changed", "new" or "deleted" in the project</li>
7929     * </ul><p>
7930     *
7931     * @param dbc the current database context
7932     * @param projectId the id of the project to read the file resources for
7933     * @param state the resource state to match
7934     *
7935     * @return a list of <code>{@link CmsResource}</code> objects matching the filter criteria
7936     *
7937     * @throws CmsException if something goes wrong
7938     *
7939     * @see CmsObject#readProjectView(CmsUUID, CmsResourceState)
7940     */
7941    public List<CmsResource> readProjectView(CmsDbContext dbc, CmsUUID projectId, CmsResourceState state)
7942    throws CmsException {
7943
7944        List<CmsResource> resources;
7945        if (state.isNew() || state.isChanged() || state.isDeleted()) {
7946            // get all resources form the database that match the selected state
7947            resources = getVfsDriver(dbc).readResources(dbc, projectId, state, CmsDriverManager.READMODE_MATCHSTATE);
7948        } else {
7949            // get all resources form the database that are somehow changed (i.e. not unchanged)
7950            resources = getVfsDriver(
7951                dbc).readResources(dbc, projectId, CmsResource.STATE_UNCHANGED, CmsDriverManager.READMODE_UNMATCHSTATE);
7952        }
7953
7954        // filter the permissions
7955        List<CmsResource> result = filterPermissions(dbc, resources, CmsResourceFilter.ALL);
7956        // sort the result
7957        Collections.sort(result);
7958        // set the full resource names
7959        return updateContextDates(dbc, result);
7960    }
7961
7962    /**
7963     * Reads a property definition.<p>
7964     *
7965     * If no property definition with the given name is found,
7966     * <code>null</code> is returned.<p>
7967     *
7968     * @param dbc the current database context
7969     * @param name the name of the property definition to read
7970     *
7971     * @return the property definition that was read
7972     *
7973     * @throws CmsException a CmsDbEntryNotFoundException is thrown if the property definition does not exist
7974     */
7975    public CmsPropertyDefinition readPropertyDefinition(CmsDbContext dbc, String name) throws CmsException {
7976
7977        return getVfsDriver(dbc).readPropertyDefinition(dbc, name, dbc.currentProject().getUuid());
7978    }
7979
7980    /**
7981     * Reads a property object from a resource specified by a property name.<p>
7982     *
7983     * Returns <code>{@link CmsProperty#getNullProperty()}</code> if the property is not found.<p>
7984     *
7985     * @param dbc the current database context
7986     * @param resource the resource where the property is read from
7987     * @param key the property key name
7988     * @param search if <code>true</code>, the property is searched on all parent folders of the resource.
7989     *      if it's not found attached directly to the resource.
7990     *
7991     * @return the required property, or <code>{@link CmsProperty#getNullProperty()}</code> if the property was not found
7992     *
7993     * @throws CmsException if something goes wrong
7994     */
7995    public CmsProperty readPropertyObject(CmsDbContext dbc, CmsResource resource, String key, boolean search)
7996    throws CmsException {
7997
7998        // NOTE: Do not call readPropertyObject(dbc, resource, key, search, null) for performance reasons
7999
8000        // use the list reading method to obtain all properties for the resource
8001        List<CmsProperty> properties = readPropertyObjects(dbc, resource, search);
8002
8003        int i = properties.indexOf(new CmsProperty(key, null, null));
8004        if (i >= 0) {
8005            // property has been found in the map
8006            CmsProperty result = properties.get(i);
8007            // ensure the result value is not frozen
8008            return result.cloneAsProperty();
8009        }
8010        return CmsProperty.getNullProperty();
8011
8012    }
8013
8014    /**
8015     * Reads a property object from a resource specified by a property name.<p>
8016     *
8017     * Returns <code>{@link CmsProperty#getNullProperty()}</code> if the property is not found.<p>
8018     *
8019     * @param dbc the current database context
8020     * @param resource the resource where the property is read from
8021     * @param key the property key name
8022     * @param search if <code>true</code>, the property is searched on all parent folders of the resource.
8023     *      if it's not found attached directly to the resource.
8024     * @param locale the locale for which the property should be read.
8025     *
8026     * @return the required property, or <code>{@link CmsProperty#getNullProperty()}</code> if the property was not found
8027     *
8028     * @throws CmsException if something goes wrong
8029     */
8030    public CmsProperty readPropertyObject(
8031        CmsDbContext dbc,
8032        CmsResource resource,
8033        String key,
8034        boolean search,
8035        Locale locale)
8036    throws CmsException {
8037
8038        // use the list reading method to obtain all properties for the resource
8039        List<CmsProperty> properties = readPropertyObjects(dbc, resource, search);
8040        // create a lookup property object and look this up in the result map
8041        CmsProperty result = null;
8042        // handle the case without locale separately to improve performance
8043        for (String localizedKey : CmsLocaleManager.getLocaleVariants(key, locale, true, false)) {
8044            int i = properties.indexOf(new CmsProperty(localizedKey, null, null));
8045            if (i >= 0) {
8046                // property has been found in the map
8047                result = properties.get(i);
8048                // ensure the result value is not frozen
8049                return result.cloneAsProperty();
8050            }
8051        }
8052        return CmsProperty.getNullProperty();
8053    }
8054
8055    /**
8056     * Reads all property objects mapped to a specified resource from the database.<p>
8057     *
8058     * All properties in the result List will be in frozen (read only) state, so you can't change the values.<p>
8059     *
8060     * Returns an empty list if no properties are found at all.<p>
8061     *
8062     * @param dbc the current database context
8063     * @param resource the resource where the properties are read from
8064     * @param search true, if the properties should be searched on all parent folders  if not found on the resource
8065     *
8066     * @return a list of CmsProperty objects containing the structure and/or resource value
8067     *
8068     * @throws CmsException if something goes wrong
8069     *
8070     * @see CmsObject#readPropertyObjects(String, boolean)
8071     */
8072    public List<CmsProperty> readPropertyObjects(CmsDbContext dbc, CmsResource resource, boolean search)
8073    throws CmsException {
8074
8075        // check if we have the result already cached
8076        CmsUUID projectId = getProjectIdForContext(dbc);
8077        String cacheKey = getCacheKey(CACHE_ALL_PROPERTIES, search, projectId, resource.getRootPath());
8078
8079        List<CmsProperty> properties = m_monitor.getCachedPropertyList(cacheKey);
8080
8081        if ((properties == null) || !dbc.getProjectId().isNullUUID()) {
8082            // result not cached, let's look it up in the DB
8083            if (search) {
8084                boolean cont;
8085                properties = new ArrayList<CmsProperty>();
8086                List<CmsProperty> parentProperties = null;
8087
8088                do {
8089                    try {
8090                        parentProperties = readPropertyObjects(dbc, resource, false);
8091
8092                        // make sure properties from lower folders "overwrite" properties from upper folders
8093                        parentProperties.removeAll(properties);
8094                        parentProperties.addAll(properties);
8095
8096                        properties.clear();
8097                        properties.addAll(parentProperties);
8098
8099                        cont = resource.getRootPath().length() > 1;
8100                    } catch (CmsSecurityException se) {
8101                        // a security exception (probably no read permission) we return the current result
8102                        cont = false;
8103                    }
8104                    if (cont) {
8105                        // no permission check on parent folder is required since we must have "read"
8106                        // permissions to read the child resource anyway
8107                        resource = readResource(
8108                            dbc,
8109                            CmsResource.getParentFolder(resource.getRootPath()),
8110                            CmsResourceFilter.ALL);
8111                    }
8112                } while (cont);
8113            } else {
8114                properties = getVfsDriver(dbc).readPropertyObjects(dbc, dbc.currentProject(), resource);
8115                //                for (CmsProperty prop : properties) {
8116                //                    prop.setOrigin(resource.getRootPath());
8117                //                }
8118            }
8119
8120            // set all properties in the result list as frozen
8121            CmsProperty.setFrozen(properties);
8122            if (dbc.getProjectId().isNullUUID()) {
8123                // store the result in the cache if needed
8124                m_monitor.cachePropertyList(cacheKey, properties);
8125            }
8126        }
8127
8128        return new ArrayList<CmsProperty>(properties);
8129    }
8130
8131    /**
8132     * Reads the resources that were published in a publish task for a given publish history ID.<p>
8133     *
8134     * @param dbc the current database context
8135     * @param publishHistoryId unique int ID to identify each publish task in the publish history
8136     *
8137     * @return a list of <code>{@link org.opencms.db.CmsPublishedResource}</code> objects
8138     *
8139     * @throws CmsException if something goes wrong
8140     */
8141    public List<CmsPublishedResource> readPublishedResources(CmsDbContext dbc, CmsUUID publishHistoryId)
8142    throws CmsException {
8143
8144        String cacheKey = publishHistoryId.toString();
8145        List<CmsPublishedResource> resourceList = m_monitor.getCachedPublishedResources(cacheKey);
8146        if ((resourceList == null) || !dbc.getProjectId().isNullUUID()) {
8147            resourceList = getProjectDriver(dbc).readPublishedResources(dbc, publishHistoryId);
8148            // store the result in the cache
8149            if (dbc.getProjectId().isNullUUID()) {
8150                m_monitor.cachePublishedResources(cacheKey, resourceList);
8151            }
8152        }
8153        return resourceList;
8154    }
8155
8156    /**
8157     * Reads a single publish job identified by its publish history id.<p>
8158     *
8159     * @param dbc the current database context
8160     * @param publishHistoryId unique id to identify the publish job in the publish history
8161     * @return an object of type <code>{@link CmsPublishJobInfoBean}</code>
8162     *
8163     * @throws CmsException if something goes wrong
8164     */
8165    public CmsPublishJobInfoBean readPublishJob(CmsDbContext dbc, CmsUUID publishHistoryId) throws CmsException {
8166
8167        return getProjectDriver(dbc).readPublishJob(dbc, publishHistoryId);
8168    }
8169
8170    /**
8171     * Reads all available publish jobs.<p>
8172     *
8173     * @param dbc the current database context
8174     * @param startTime the start of the time range for finish time
8175     * @param endTime the end of the time range for finish time
8176     * @return a list of objects of type <code>{@link CmsPublishJobInfoBean}</code>
8177     *
8178     * @throws CmsException if something goes wrong
8179     */
8180    public List<CmsPublishJobInfoBean> readPublishJobs(CmsDbContext dbc, long startTime, long endTime)
8181    throws CmsException {
8182
8183        return getProjectDriver(dbc).readPublishJobs(dbc, startTime, endTime);
8184    }
8185
8186    /**
8187     * Reads the publish list assigned to a publish job.<p>
8188     *
8189     * @param dbc the current database context
8190     * @param publishHistoryId the history id identifying the publish job
8191     * @return the assigned publish list
8192     * @throws CmsException if something goes wrong
8193     */
8194    public CmsPublishList readPublishList(CmsDbContext dbc, CmsUUID publishHistoryId) throws CmsException {
8195
8196        return getProjectDriver(dbc).readPublishList(dbc, publishHistoryId);
8197    }
8198
8199    /**
8200     * Reads the publish report assigned to a publish job.<p>
8201     *
8202     * @param dbc the current database context
8203     * @param publishHistoryId the history id identifying the publish job
8204     * @return the content of the assigned publish report
8205     * @throws CmsException if something goes wrong
8206     */
8207    public byte[] readPublishReportContents(CmsDbContext dbc, CmsUUID publishHistoryId) throws CmsException {
8208
8209        return getProjectDriver(dbc).readPublishReportContents(dbc, publishHistoryId);
8210    }
8211
8212    /**
8213     * Reads an historical resource entry for the given resource and with the given version number.<p>
8214     *
8215     * @param dbc the current db context
8216     * @param resource the resource to be read
8217     * @param version the version number to retrieve
8218     *
8219     * @return the resource that was read
8220     *
8221     * @throws CmsException if the resource could not be read for any reason
8222     *
8223     * @see CmsObject#restoreResourceVersion(CmsUUID, int)
8224     * @see CmsObject#readResource(CmsUUID, int)
8225     */
8226    public I_CmsHistoryResource readResource(CmsDbContext dbc, CmsResource resource, int version) throws CmsException {
8227
8228        Iterator<I_CmsHistoryResource> itVersions = getHistoryDriver(dbc).readAllAvailableVersions(
8229            dbc,
8230            resource.getStructureId()).iterator();
8231        while (itVersions.hasNext()) {
8232            I_CmsHistoryResource histRes = itVersions.next();
8233            if (histRes.getVersion() == version) {
8234                return histRes;
8235            }
8236        }
8237        throw new CmsVfsResourceNotFoundException(
8238            org.opencms.db.generic.Messages.get().container(
8239                org.opencms.db.generic.Messages.ERR_HISTORY_FILE_NOT_FOUND_1,
8240                resource.getStructureId()));
8241    }
8242
8243    /**
8244     * Reads a resource from the VFS, using the specified resource filter.<p>
8245     *
8246     * @param dbc the current database context
8247     * @param structureID the structure id of the resource to read
8248     * @param filter the resource filter to use while reading
8249     *
8250     * @return the resource that was read
8251     *
8252     * @throws CmsDataAccessException if something goes wrong
8253     *
8254     * @see CmsObject#readResource(CmsUUID, CmsResourceFilter)
8255     * @see CmsObject#readResource(CmsUUID)
8256     */
8257    public CmsResource readResource(CmsDbContext dbc, CmsUUID structureID, CmsResourceFilter filter)
8258    throws CmsDataAccessException {
8259
8260        CmsUUID projectId = getProjectIdForContext(dbc);
8261        // please note: the filter will be applied in the security manager later
8262        CmsResource resource = getVfsDriver(dbc).readResource(dbc, projectId, structureID, filter.includeDeleted());
8263
8264        // context dates need to be updated
8265        updateContextDates(dbc, resource);
8266
8267        // return the resource
8268        return resource;
8269    }
8270
8271    /**
8272     * Reads a resource from the VFS, using the specified resource filter.<p>
8273     *
8274     * @param dbc the current database context
8275     * @param resourcePath the name of the resource to read (full path)
8276     * @param filter the resource filter to use while reading
8277     *
8278     * @return the resource that was read
8279     *
8280     * @throws CmsDataAccessException if something goes wrong
8281     *
8282     * @see CmsObject#readResource(String, CmsResourceFilter)
8283     * @see CmsObject#readResource(String)
8284     * @see CmsObject#readFile(CmsResource)
8285     */
8286    public CmsResource readResource(CmsDbContext dbc, String resourcePath, CmsResourceFilter filter)
8287    throws CmsDataAccessException {
8288
8289        CmsUUID projectId = getProjectIdForContext(dbc);
8290        // please note: the filter will be applied in the security manager later
8291        CmsResource resource = getVfsDriver(dbc).readResource(dbc, projectId, resourcePath, filter.includeDeleted());
8292
8293        // context dates need to be updated
8294        updateContextDates(dbc, resource);
8295
8296        // return the resource
8297        return resource;
8298    }
8299
8300    /**
8301     * Reads all resources below the given path matching the filter criteria,
8302     * including the full tree below the path only in case the <code>readTree</code>
8303     * parameter is <code>true</code>.<p>
8304     *
8305     * @param dbc the current database context
8306     * @param parent the parent path to read the resources from
8307     * @param filter the filter
8308     * @param readTree <code>true</code> to read all subresources
8309     *
8310     * @return a list of <code>{@link CmsResource}</code> objects matching the filter criteria
8311     *
8312     * @throws CmsDataAccessException if the bare reading of the resources fails
8313     * @throws CmsException if security and permission checks for the resources read fail
8314     */
8315    public List<CmsResource> readResources(
8316        CmsDbContext dbc,
8317        CmsResource parent,
8318        CmsResourceFilter filter,
8319        boolean readTree)
8320    throws CmsException, CmsDataAccessException {
8321
8322        // try to get the sub resources from the cache
8323        String cacheKey = getCacheKey(
8324            new String[] {dbc.currentUser().getName(), filter.getCacheId(), readTree ? "+" : "-", parent.getRootPath()},
8325            dbc);
8326
8327        List<CmsResource> resourceList = m_monitor.getCachedResourceList(cacheKey);
8328        if ((resourceList == null) || !dbc.getProjectId().isNullUUID()) {
8329            // read the result from the database
8330            resourceList = getVfsDriver(dbc).readResourceTree(
8331                dbc,
8332                dbc.currentProject().getUuid(),
8333                (readTree ? parent.getRootPath() : parent.getStructureId().toString()),
8334                filter.getType(),
8335                filter.getState(),
8336                filter.getModifiedAfter(),
8337                filter.getModifiedBefore(),
8338                filter.getReleaseAfter(),
8339                filter.getReleaseBefore(),
8340                filter.getExpireAfter(),
8341                filter.getExpireBefore(),
8342                (readTree ? CmsDriverManager.READMODE_INCLUDE_TREE : CmsDriverManager.READMODE_EXCLUDE_TREE)
8343                    | (filter.excludeType() ? CmsDriverManager.READMODE_EXCLUDE_TYPE : 0)
8344                    | (filter.excludeState() ? CmsDriverManager.READMODE_EXCLUDE_STATE : 0)
8345                    | ((filter.getOnlyFolders() != null)
8346                    ? (filter.getOnlyFolders().booleanValue()
8347                    ? CmsDriverManager.READMODE_ONLY_FOLDERS
8348                    : CmsDriverManager.READMODE_ONLY_FILES)
8349                    : 0));
8350
8351            // HACK: do not take care of permissions if reading organizational units
8352            if (!parent.getRootPath().startsWith("/system/orgunits/")) {
8353                // apply permission filter
8354                resourceList = filterPermissions(dbc, resourceList, filter);
8355            }
8356            // store the result in the resourceList cache
8357            if (dbc.getProjectId().isNullUUID()) {
8358                m_monitor.cacheResourceList(cacheKey, resourceList);
8359            }
8360        }
8361        // we must always apply the result filter and update the context dates
8362        return updateContextDates(dbc, resourceList, filter);
8363    }
8364
8365    /**
8366     * Returns the resources that were visited by a user set in the filter.<p>
8367     *
8368     * @param dbc the database context
8369     * @param poolName the name of the database pool to use
8370     * @param filter the filter that is used to get the visited resources
8371     *
8372     * @return the resources that were visited by a user set in the filter
8373     *
8374     * @throws CmsException if something goes wrong
8375     */
8376    public List<CmsResource> readResourcesVisitedBy(CmsDbContext dbc, String poolName, CmsVisitedByFilter filter)
8377    throws CmsException {
8378
8379        List<CmsResource> result = getSubscriptionDriver().readResourcesVisitedBy(dbc, poolName, filter);
8380        result = filterPermissions(dbc, result, CmsResourceFilter.DEFAULT);
8381        return result;
8382    }
8383
8384    /**
8385     * Reads all resources that have a value (containing the given value string) set
8386     * for the specified property (definition) in the given path.<p>
8387     *
8388     * Both individual and shared properties of a resource are checked.<p>
8389     *
8390     * If the <code>value</code> parameter is <code>null</code>, all resources having the
8391     * given property set are returned.<p>
8392     *
8393     * @param dbc the current database context
8394     * @param folder the folder to get the resources with the property from
8395     * @param propertyDefinition the name of the property (definition) to check for
8396     * @param value the string to search in the value of the property
8397     * @param filter the resource filter to apply to the result set
8398     *
8399     * @return a list of all <code>{@link CmsResource}</code> objects
8400     *          that have a value set for the specified property.
8401     *
8402     * @throws CmsException if something goes wrong
8403     */
8404    public List<CmsResource> readResourcesWithProperty(
8405        CmsDbContext dbc,
8406        CmsResource folder,
8407        String propertyDefinition,
8408        String value,
8409        CmsResourceFilter filter)
8410    throws CmsException {
8411
8412        String cacheKey;
8413        if (value == null) {
8414            cacheKey = getCacheKey(
8415                new String[] {
8416                    dbc.currentUser().getName(),
8417                    folder.getRootPath(),
8418                    propertyDefinition,
8419                    filter.getCacheId()},
8420                dbc);
8421        } else {
8422            cacheKey = getCacheKey(
8423                new String[] {
8424                    dbc.currentUser().getName(),
8425                    folder.getRootPath(),
8426                    propertyDefinition,
8427                    value,
8428                    filter.getCacheId()},
8429                dbc);
8430        }
8431        List<CmsResource> resourceList = m_monitor.getCachedResourceList(cacheKey);
8432        if ((resourceList == null) || !dbc.getProjectId().isNullUUID()) {
8433
8434            CmsPropertyDefinition propDef = null;
8435            try {
8436                // first read the property definition
8437                propDef = readPropertyDefinition(dbc, propertyDefinition);
8438            } catch (CmsDbEntryNotFoundException e) {
8439                LOG.debug(e.getLocalizedMessage(), e);
8440            }
8441            if (propDef != null) {
8442                // now read the list of resources that have a value set for the property definition
8443                resourceList = getVfsDriver(dbc).readResourcesWithProperty(
8444                    dbc,
8445                    dbc.currentProject().getUuid(),
8446                    propDef.getId(),
8447                    folder.getRootPath(),
8448                    value);
8449                // apply permission filter
8450                resourceList = filterPermissions(dbc, resourceList, filter);
8451            } else {
8452                resourceList = new ArrayList<>();
8453            }
8454            // store the result in the resourceList cache
8455            if (dbc.getProjectId().isNullUUID()) {
8456                m_monitor.cacheResourceList(cacheKey, resourceList);
8457            }
8458        }
8459        // we must always apply the result filter and update the context dates
8460        return updateContextDates(dbc, resourceList, filter);
8461    }
8462
8463    /**
8464     * Returns the set of users that are responsible for a specific resource.<p>
8465     *
8466     * @param dbc the current database context
8467     * @param resource the resource to get the responsible users from
8468     *
8469     * @return the set of users that are responsible for a specific resource
8470     *
8471     * @throws CmsException if something goes wrong
8472     */
8473    public Set<I_CmsPrincipal> readResponsiblePrincipals(CmsDbContext dbc, CmsResource resource) throws CmsException {
8474
8475        Set<I_CmsPrincipal> result = new HashSet<I_CmsPrincipal>();
8476        Iterator<CmsAccessControlEntry> aces = getAccessControlEntries(dbc, resource, true).iterator();
8477        while (aces.hasNext()) {
8478            CmsAccessControlEntry ace = aces.next();
8479            if (ace.isResponsible()) {
8480                I_CmsPrincipal p = lookupPrincipal(dbc, ace.getPrincipal());
8481                if (p != null) {
8482                    result.add(p);
8483                }
8484            }
8485        }
8486        return result;
8487    }
8488
8489    /**
8490     * Returns the set of users that are responsible for a specific resource.<p>
8491     *
8492     * @param dbc the current database context
8493     * @param resource the resource to get the responsible users from
8494     *
8495     * @return the set of users that are responsible for a specific resource
8496     *
8497     * @throws CmsException if something goes wrong
8498     */
8499    public Set<CmsUser> readResponsibleUsers(CmsDbContext dbc, CmsResource resource) throws CmsException {
8500
8501        Set<CmsUser> result = new HashSet<CmsUser>();
8502        Iterator<I_CmsPrincipal> principals = readResponsiblePrincipals(dbc, resource).iterator();
8503        while (principals.hasNext()) {
8504            I_CmsPrincipal principal = principals.next();
8505            if (principal.isGroup()) {
8506                try {
8507                    result.addAll(getUsersOfGroup(dbc, principal.getName(), true, false, false));
8508                } catch (CmsException e) {
8509                    if (LOG.isInfoEnabled()) {
8510                        LOG.info(e.getLocalizedMessage(), e);
8511                    }
8512                }
8513            } else {
8514                result.add((CmsUser)principal);
8515            }
8516        }
8517        return result;
8518    }
8519
8520    /**
8521     * Returns a List of all siblings of the specified resource,
8522     * the specified resource being always part of the result set.<p>
8523     *
8524     * The result is a list of <code>{@link CmsResource}</code> objects.<p>
8525     *
8526     * @param dbc the current database context
8527     * @param resource the resource to read the siblings for
8528     * @param filter a filter object
8529     *
8530     * @return a list of <code>{@link CmsResource}</code> Objects that
8531     *          are siblings to the specified resource,
8532     *          including the specified resource itself
8533     *
8534     * @throws CmsException if something goes wrong
8535     */
8536    public List<CmsResource> readSiblings(CmsDbContext dbc, CmsResource resource, CmsResourceFilter filter)
8537    throws CmsException {
8538
8539        List<CmsResource> siblings = getVfsDriver(
8540            dbc).readSiblings(dbc, dbc.currentProject().getUuid(), resource, filter.includeDeleted());
8541
8542        // important: there is no permission check done on the returned list of siblings
8543        // this is because of possible issues with the "publish all siblings" option,
8544        // moreover the user has read permission for the content through
8545        // the selected sibling anyway
8546        return updateContextDates(dbc, siblings, filter);
8547    }
8548
8549    /**
8550     * Returns the parameters of a resource in the table of all published template resources.<p>
8551     *
8552     * @param dbc the current database context
8553     * @param rfsName the rfs name of the resource
8554     *
8555     * @return the parameter string of the requested resource
8556     *
8557     * @throws CmsException if something goes wrong
8558     */
8559    public String readStaticExportPublishedResourceParameters(CmsDbContext dbc, String rfsName) throws CmsException {
8560
8561        return getProjectDriver(dbc).readStaticExportPublishedResourceParameters(dbc, rfsName);
8562    }
8563
8564    /**
8565     * Returns a list of all template resources which must be processed during a static export.<p>
8566     *
8567     * @param dbc the current database context
8568     * @param parameterResources flag for reading resources with parameters (1) or without (0)
8569     * @param timestamp for reading the data from the db
8570     *
8571     * @return a list of template resources as <code>{@link String}</code> objects
8572     *
8573     * @throws CmsException if something goes wrong
8574     */
8575    public List<String> readStaticExportResources(CmsDbContext dbc, int parameterResources, long timestamp)
8576    throws CmsException {
8577
8578        return getProjectDriver(dbc).readStaticExportResources(dbc, parameterResources, timestamp);
8579    }
8580
8581    /**
8582     * Reads information about where the file content is stored without loading the content bytes.<p>
8583     *
8584     * @param dbc the current database context
8585     * @param resource the base file resource
8586     *
8587     * @return the stored content info
8588     *
8589     * @throws CmsException if operation was not successful
8590     */
8591    public CmsStoredContentInfo readStoredContentInfo(CmsDbContext dbc, CmsResource resource) throws CmsException {
8592
8593        checkFileResource(dbc, resource);
8594        if (resource instanceof I_CmsHistoryResource) {
8595            return new CmsStoredContentInfo(resource, null, null, resource.getLength());
8596        }
8597        CmsUUID projectId = dbc.currentProject().getUuid();
8598        return getVfsDriver(dbc).readStoredContentInfo(dbc, projectId, resource);
8599    }
8600
8601    /**
8602     * Returns the subscribed history resources that were deleted.<p>
8603     *
8604     * @param dbc the database context
8605     * @param poolName the name of the database pool to use
8606     * @param user the user that subscribed to the resource
8607     * @param groups the groups to check subscribed resources for
8608     * @param parent the parent resource (folder) of the deleted resources, if <code>null</code> all deleted resources will be returned
8609     * @param includeSubFolders indicates if the sub folders of the specified folder path should be considered, too
8610     * @param deletedFrom the time stamp from which the resources should have been deleted
8611     *
8612     * @return the subscribed history resources that were deleted
8613     *
8614     * @throws CmsException if something goes wrong
8615     */
8616    public List<I_CmsHistoryResource> readSubscribedDeletedResources(
8617        CmsDbContext dbc,
8618        String poolName,
8619        CmsUser user,
8620        List<CmsGroup> groups,
8621        CmsResource parent,
8622        boolean includeSubFolders,
8623        long deletedFrom)
8624    throws CmsException {
8625
8626        List<I_CmsHistoryResource> result = getSubscriptionDriver().readSubscribedDeletedResources(
8627            dbc,
8628            poolName,
8629            user,
8630            groups,
8631            parent,
8632            includeSubFolders,
8633            deletedFrom);
8634
8635        return result;
8636    }
8637
8638    /**
8639     * Returns the resources that were subscribed by a user or group set in the filter.<p>
8640     *
8641     * @param dbc the database context
8642     * @param poolName the name of the database pool to use
8643     * @param filter the filter that is used to get the subscribed resources
8644     *
8645     * @return the resources that were subscribed by a user or group set in the filter
8646     *
8647     * @throws CmsException if something goes wrong
8648     */
8649    public List<CmsResource> readSubscribedResources(CmsDbContext dbc, String poolName, CmsSubscriptionFilter filter)
8650    throws CmsException {
8651
8652        List<CmsResource> result = getSubscriptionDriver().readSubscribedResources(dbc, poolName, filter);
8653
8654        result = filterPermissions(dbc, result, CmsResourceFilter.DEFAULT);
8655        return result;
8656    }
8657
8658    /**
8659     * Reads URL name mapping entries which match the given filter.<p>
8660     *
8661     * @param dbc the database context
8662     * @param online if true, read online URL name mappings, else offline ones
8663     * @param filter the filter for matching the URL name entries
8664     *
8665     * @return the list of URL name mapping entries which match the given filter
8666     *
8667     * @throws CmsDataAccessException if something goes wrong
8668     */
8669    public List<CmsUrlNameMappingEntry> readUrlNameMappingEntries(
8670        CmsDbContext dbc,
8671        boolean online,
8672        CmsUrlNameMappingFilter filter)
8673    throws CmsDataAccessException {
8674
8675        I_CmsVfsDriver vfsDriver = getVfsDriver(dbc);
8676        return vfsDriver.readUrlNameMappingEntries(dbc, online, filter);
8677    }
8678
8679    /**
8680     * Reads the URL name mappings matching the given filter.<p>
8681     *
8682     * @param dbc the DB context to use
8683     * @param filter the filter used to select the mapping entries
8684     * @return the entries matching the given filter
8685     *
8686     * @throws CmsDataAccessException if something goes wrong
8687     */
8688    public List<CmsUrlNameMappingEntry> readUrlNameMappings(CmsDbContext dbc, CmsUrlNameMappingFilter filter)
8689    throws CmsDataAccessException {
8690
8691        List<CmsUrlNameMappingEntry> entries = getVfsDriver(dbc).readUrlNameMappingEntries(
8692            dbc,
8693            dbc.currentProject().isOnlineProject(),
8694            filter);
8695        return entries;
8696    }
8697
8698    /**
8699     * Reads the newest URL names of a resource for all locales.<p>
8700     *
8701     * @param dbc the database context
8702     * @param id the resource's structure id
8703     *
8704     * @return the url names for the locales
8705     *
8706     * @throws CmsDataAccessException if the database operation failed
8707     */
8708    public List<String> readUrlNamesForAllLocales(CmsDbContext dbc, CmsUUID id) throws CmsDataAccessException {
8709
8710        List<String> result = new ArrayList<String>();
8711        List<CmsUrlNameMappingEntry> entries = getVfsDriver(dbc).readUrlNameMappingEntries(
8712            dbc,
8713            dbc.currentProject().isOnlineProject(),
8714            CmsUrlNameMappingFilter.ALL.filterStructureId(id));
8715        ArrayListMultimap<String, CmsUrlNameMappingEntry> entriesByLocale = ArrayListMultimap.create();
8716        for (CmsUrlNameMappingEntry entry : entries) {
8717            String localeKey = entry.getLocale();
8718            entriesByLocale.put(localeKey, entry);
8719        }
8720
8721        for (String localeKey : entriesByLocale.keySet()) {
8722            List<CmsUrlNameMappingEntry> entrs = entriesByLocale.get(localeKey);
8723            CmsUrlNameMappingEntry maxEntryForLocale = Collections.max(entrs, new UrlNameMappingComparator());
8724            result.add(maxEntryForLocale.getName());
8725        }
8726        return result;
8727    }
8728
8729    /**
8730     * Returns a user object based on the id of a user.<p>
8731     *
8732     * @param dbc the current database context
8733     * @param id the id of the user to read
8734     *
8735     * @return the user read
8736     *
8737     * @throws CmsException if something goes wrong
8738     */
8739    public CmsUser readUser(CmsDbContext dbc, CmsUUID id) throws CmsException {
8740
8741        CmsUser user = m_monitor.getCachedUser(id.toString());
8742        if (user == null) {
8743            user = getUserDriver(dbc).readUser(dbc, id);
8744            m_monitor.cacheUser(user);
8745        }
8746        // important: do not return the cached user object, but a clone to avoid unwanted changes on cached objects
8747        return user.clone();
8748    }
8749
8750    /**
8751     * Returns a user object.<p>
8752     *
8753     * @param dbc the current database context
8754     * @param username the name of the user that is to be read
8755     *
8756     * @return user read
8757     *
8758     * @throws CmsDataAccessException if operation was not successful
8759     */
8760    public CmsUser readUser(CmsDbContext dbc, String username) throws CmsDataAccessException {
8761
8762        CmsUser user = m_monitor.getCachedUser(username);
8763        if (user == null) {
8764            user = getUserDriver(dbc).readUser(dbc, username);
8765            m_monitor.cacheUser(user);
8766        }
8767        // important: do not return the cached user object, but a clone to avoid unwanted changes on cached objects
8768        return user.clone();
8769    }
8770
8771    /**
8772     * Returns a user object if the password for the user is correct.<p>
8773     *
8774     * If the user/pwd pair is not valid a <code>{@link CmsException}</code> is thrown.<p>
8775     *
8776     * @param dbc the current database context
8777     * @param username the username of the user that is to be read
8778     * @param password the password of the user that is to be read
8779     *
8780     * @return user read
8781     *
8782     * @throws CmsException if operation was not successful
8783     */
8784    public CmsUser readUser(CmsDbContext dbc, String username, String password) throws CmsException {
8785
8786        // don't read user from cache here because password may have changed
8787        CmsUser user = getUserDriver(dbc).readUser(dbc, username, password, null);
8788        m_monitor.cacheUser(user);
8789        return user;
8790    }
8791
8792    /**
8793     * Removes an access control entry for a given resource and principal.<p>
8794     *
8795     * @param dbc the current database context
8796     * @param resource the resource
8797     * @param principal the id of the principal to remove the the access control entry for
8798     *
8799     * @throws CmsException if something goes wrong
8800     */
8801    public void removeAccessControlEntry(CmsDbContext dbc, CmsResource resource, CmsUUID principal)
8802    throws CmsException {
8803
8804        // remove the ace
8805        getUserDriver(dbc).removeAccessControlEntry(dbc, dbc.currentProject(), resource.getResourceId(), principal);
8806
8807        // log it
8808        log(
8809            dbc,
8810            new CmsLogEntry(
8811                dbc,
8812                resource.getStructureId(),
8813                CmsLogEntryType.RESOURCE_PERMISSIONS,
8814                new String[] {resource.getRootPath()}),
8815            false);
8816
8817        // update the "last modified" information
8818        setDateLastModified(dbc, resource, resource.getDateLastModified());
8819
8820        // clear the cache
8821        m_monitor.clearAccessControlListCache();
8822
8823        // fire a resource modification event
8824        Map<String, Object> data = new HashMap<String, Object>(2);
8825        data.put(I_CmsEventListener.KEY_RESOURCE, resource);
8826        data.put(I_CmsEventListener.KEY_CHANGE, Integer.valueOf(CHANGED_ACCESSCONTROL));
8827        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_MODIFIED, data));
8828    }
8829
8830    /**
8831     * Removes a resource from the given organizational unit.<p>
8832     *
8833     * @param dbc the current db context
8834     * @param orgUnit the organizational unit to remove the resource from
8835     * @param resource the resource that is to be removed from the organizational unit
8836     *
8837     * @throws CmsException if something goes wrong
8838     *
8839     * @see org.opencms.security.CmsOrgUnitManager#addResourceToOrgUnit(CmsObject, String, String)
8840     * @see org.opencms.security.CmsOrgUnitManager#addResourceToOrgUnit(CmsObject, String, String)
8841     */
8842    public void removeResourceFromOrgUnit(CmsDbContext dbc, CmsOrganizationalUnit orgUnit, CmsResource resource)
8843    throws CmsException {
8844
8845        m_monitor.flushCache(CmsMemoryMonitor.CacheType.HAS_ROLE, CmsMemoryMonitor.CacheType.ROLE_LIST);
8846        getUserDriver(dbc).removeResourceFromOrganizationalUnit(dbc, orgUnit, resource);
8847    }
8848
8849    /**
8850     * Removes a resource from the current project of the user.<p>
8851     *
8852     * @param dbc the current database context
8853     * @param resource the resource to apply this operation to
8854     *
8855     * @throws CmsException if something goes wrong
8856     *
8857     * @see CmsObject#copyResourceToProject(String)
8858     * @see I_CmsResourceType#copyResourceToProject(CmsObject, CmsSecurityManager, CmsResource)
8859     */
8860    public void removeResourceFromProject(CmsDbContext dbc, CmsResource resource) throws CmsException {
8861
8862        // remove the resource to the project only if the resource is already in the project
8863        if (isInsideCurrentProject(dbc, resource.getRootPath())) {
8864            // check if there are already any subfolders of this resource
8865            I_CmsProjectDriver projectDriver = getProjectDriver(dbc);
8866            if (resource.isFolder()) {
8867                List<String> projectResources = projectDriver.readProjectResources(dbc, dbc.currentProject());
8868                for (int i = 0; i < projectResources.size(); i++) {
8869                    String resname = projectResources.get(i);
8870                    if (resname.startsWith(resource.getRootPath())) {
8871                        // delete the existing project resource first
8872                        projectDriver.deleteProjectResource(dbc, dbc.currentProject().getUuid(), resname);
8873                    }
8874                }
8875            }
8876            try {
8877                projectDriver.deleteProjectResource(dbc, dbc.currentProject().getUuid(), resource.getRootPath());
8878            } catch (CmsException exc) {
8879                // if the subfolder exists already - all is ok
8880            } finally {
8881                m_monitor.flushCache(CmsMemoryMonitor.CacheType.PROJECT_RESOURCES);
8882
8883                OpenCms.fireCmsEvent(
8884                    new CmsEvent(
8885                        I_CmsEventListener.EVENT_PROJECT_MODIFIED,
8886                        Collections.<String, Object> singletonMap("project", dbc.currentProject())));
8887            }
8888        }
8889    }
8890
8891    /**
8892     * Removes the given resource to the given user's publish list.<p>
8893     *
8894     * @param dbc the database context
8895     * @param userId the user's id
8896     * @param structureIds the collection of structure IDs to remove
8897     *
8898     * @throws CmsDataAccessException if something goes wrong
8899     */
8900    public void removeResourceFromUsersPubList(CmsDbContext dbc, CmsUUID userId, Collection<CmsUUID> structureIds)
8901    throws CmsDataAccessException {
8902
8903        for (CmsUUID structureId : structureIds) {
8904            CmsLogEntry entry = new CmsLogEntry(
8905                userId,
8906                System.currentTimeMillis(),
8907                structureId,
8908                CmsLogEntryType.RESOURCE_HIDDEN,
8909                new String[] {readResource(dbc, structureId, CmsResourceFilter.ALL).getRootPath()});
8910            log(dbc, entry, true);
8911        }
8912    }
8913
8914    /**
8915     * Removes a user from a group.<p>
8916     *
8917     * @param dbc the current database context
8918     * @param username the name of the user that is to be removed from the group
8919     * @param groupname the name of the group
8920     * @param readRoles if to read roles or groups
8921     *
8922     * @throws CmsException if operation was not successful
8923     * @throws CmsIllegalArgumentException if the given user was not member in the given group
8924     * @throws CmsDbEntryNotFoundException if the given group was not found
8925     * @throws CmsSecurityException if the given user was <b>read as 'null' from the database</b>
8926     *
8927     * @see #addUserToGroup(CmsDbContext, String, String, boolean)
8928     */
8929    public void removeUserFromGroup(CmsDbContext dbc, String username, String groupname, boolean readRoles)
8930    throws CmsException, CmsIllegalArgumentException, CmsDbEntryNotFoundException, CmsSecurityException {
8931
8932        CmsGroup group = readGroup(dbc, groupname);
8933        //check if group exists
8934        if (group == null) {
8935            // the group does not exists
8936            throw new CmsDbEntryNotFoundException(Messages.get().container(Messages.ERR_UNKNOWN_GROUP_1, groupname));
8937        }
8938        if (group.isVirtual() && !readRoles) {
8939            // if removing a user from a virtual role treat it as removing the user from the role
8940            removeUserFromGroup(dbc, username, CmsRole.valueOf(group).getGroupName(), true);
8941            return;
8942        }
8943        if (group.isVirtual()) {
8944            // this is an hack so to prevent a unlimited recursive calls
8945            readRoles = false;
8946        }
8947        if ((readRoles && !group.isRole()) || (!readRoles && group.isRole())) {
8948            // we want a role but we got a group, or the other way
8949            throw new CmsDbEntryNotFoundException(Messages.get().container(Messages.ERR_UNKNOWN_GROUP_1, groupname));
8950        }
8951
8952        boolean skipRemove = false;
8953        // test if this user is existing in the group
8954        if (!userInGroup(dbc, username, groupname, readRoles)) {
8955            if (readRoles) {
8956                // Sometimes users can end up with the default groups corresponding to roles (Administrators, Users) without the actual roles.
8957                // 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
8958                // want to throw an exception then, because it would prevent the code that actually removes the user from the group from running.
8959                LOG.warn(
8960                    "Trying to remove user from role that they are not a member of (user: "
8961                        + username
8962                        + ", group: "
8963                        + groupname
8964                        + ")");
8965                skipRemove = true;
8966            } else {
8967                // user is not in the group, throw exception
8968                throw new CmsIllegalArgumentException(
8969                    Messages.get().container(Messages.ERR_USER_NOT_IN_GROUP_2, username, groupname));
8970            }
8971        }
8972
8973        CmsUser user = readUser(dbc, username);
8974        //check if the user exists
8975        if (user == null) {
8976            // the user does not exists
8977            throw new CmsIllegalArgumentException(
8978                Messages.get().container(Messages.ERR_USER_NOT_IN_GROUP_2, username, groupname));
8979        }
8980
8981        if (readRoles) {
8982            CmsRole role = CmsRole.valueOf(group);
8983            // update virtual groups
8984            Iterator<CmsGroup> it = getVirtualGroupsForRole(dbc, role).iterator();
8985            while (it.hasNext()) {
8986                CmsGroup virtualGroup = it.next();
8987                if (userInGroup(dbc, username, virtualGroup.getName(), false)) {
8988                    // here we say readroles = true, to prevent an unlimited recursive calls
8989                    removeUserFromGroup(dbc, username, virtualGroup.getName(), true);
8990                }
8991            }
8992        }
8993        if (!skipRemove) {
8994            getUserDriver(dbc).deleteUserInGroup(dbc, user.getId(), group.getId());
8995        }
8996
8997        // flush relevant caches
8998        if (readRoles) {
8999            m_monitor.flushCache(CmsMemoryMonitor.CacheType.HAS_ROLE, CmsMemoryMonitor.CacheType.ROLE_LIST);
9000        }
9001        m_monitor.flushUserGroups(user.getId());
9002        m_monitor.flushCache(CmsMemoryMonitor.CacheType.USER_LIST);
9003
9004        if (!dbc.getProjectId().isNullUUID()) {
9005            // user modified event is not needed
9006            return;
9007        }
9008        // fire user modified event
9009        Map<String, Object> eventData = new HashMap<String, Object>();
9010        eventData.put(I_CmsEventListener.KEY_USER_ID, user.getId().toString());
9011        eventData.put(I_CmsEventListener.KEY_USER_NAME, user.getName());
9012        eventData.put(I_CmsEventListener.KEY_GROUP_ID, group.getId().toString());
9013        eventData.put(I_CmsEventListener.KEY_GROUP_NAME, group.getName());
9014        eventData.put(
9015            I_CmsEventListener.KEY_USER_ACTION,
9016            I_CmsEventListener.VALUE_USER_MODIFIED_ACTION_REMOVE_USER_FROM_GROUP);
9017        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_USER_MODIFIED, eventData));
9018
9019    }
9020
9021    /**
9022     * Repairs broken categories.<p>
9023     *
9024     * @param dbc the database context
9025     * @param projectId the project id
9026     * @param resource the resource to repair the categories for
9027     *
9028     * @throws CmsException if something goes wrong
9029     */
9030    public void repairCategories(CmsDbContext dbc, CmsUUID projectId, CmsResource resource) throws CmsException {
9031
9032        CmsObject cms = OpenCms.initCmsObject(new CmsObject(getSecurityManager(), dbc.getRequestContext()));
9033        cms.getRequestContext().setSiteRoot("");
9034        cms.getRequestContext().setCurrentProject(readProject(dbc, projectId));
9035        CmsCategoryService.getInstance().repairRelations(cms, resource);
9036    }
9037
9038    /**
9039     * Replaces the content, type and properties of a resource.<p>
9040     *
9041     * @param dbc the current database context
9042     * @param resource the name of the resource to apply this operation to
9043     * @param type the new type of the resource
9044     * @param content the new content of the resource
9045     * @param properties the new properties of the resource
9046     *
9047     * @throws CmsException if something goes wrong
9048     *
9049     * @see CmsObject#replaceResource(String, int, byte[], List)
9050     * @see I_CmsResourceType#replaceResource(CmsObject, CmsSecurityManager, CmsResource, int, byte[], List)
9051     */
9052    @SuppressWarnings("javadoc")
9053    public void replaceResource(
9054        CmsDbContext dbc,
9055        CmsResource resource,
9056        int type,
9057        byte[] content,
9058        List<CmsProperty> properties)
9059    throws CmsException {
9060
9061        // replace the existing with the new file content
9062        getVfsDriver(dbc).replaceResource(dbc, resource, content, type);
9063
9064        if ((properties != null) && !properties.isEmpty()) {
9065            // write the properties
9066            getVfsDriver(dbc).writePropertyObjects(dbc, dbc.currentProject(), resource, properties);
9067            m_monitor.flushCache(CmsMemoryMonitor.CacheType.PROPERTY, CmsMemoryMonitor.CacheType.PROPERTY_LIST);
9068        }
9069
9070        // update the resource state
9071        if (resource.getState().isUnchanged()) {
9072            resource.setState(CmsResource.STATE_CHANGED);
9073        }
9074        resource.setUserLastModified(dbc.currentUser().getId());
9075
9076        // log it
9077        log(
9078            dbc,
9079            new CmsLogEntry(
9080                dbc,
9081                resource.getStructureId(),
9082                CmsLogEntryType.RESOURCE_CONTENT_MODIFIED,
9083                new String[] {resource.getRootPath()}),
9084            false);
9085
9086        setDateLastModified(dbc, resource, System.currentTimeMillis());
9087
9088        getVfsDriver(dbc).writeResourceState(dbc, dbc.currentProject(), resource, UPDATE_RESOURCE, false);
9089
9090        deleteRelationsWithSiblings(dbc, resource);
9091
9092        // clear the cache
9093        m_monitor.clearResourceCache();
9094
9095        if ((properties != null) && !properties.isEmpty()) {
9096            // resource and properties were modified
9097            OpenCms.fireCmsEvent(
9098                new CmsEvent(
9099                    I_CmsEventListener.EVENT_RESOURCE_AND_PROPERTIES_MODIFIED,
9100                    Collections.<String, Object> singletonMap(I_CmsEventListener.KEY_RESOURCE, resource)));
9101        } else {
9102            // only the resource was modified
9103            Map<String, Object> data = new HashMap<String, Object>(2);
9104            data.put(I_CmsEventListener.KEY_RESOURCE, resource);
9105            data.put(I_CmsEventListener.KEY_CHANGE, Integer.valueOf(CHANGED_RESOURCE | CHANGED_CONTENT));
9106            OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_MODIFIED, data));
9107        }
9108    }
9109
9110    /**
9111     * Resets the password for a specified user.<p>
9112     *
9113     * @param dbc the current database context
9114     * @param username the name of the user
9115     * @param oldPassword the old password
9116     * @param secondFactor the second factor data used for 2FA
9117     * @param newPassword the new password
9118     *
9119     * @throws CmsException if the user data could not be read from the database
9120     * @throws CmsSecurityException if the specified username and old password could not be verified
9121     */
9122    public void resetPassword(
9123        CmsDbContext dbc,
9124        String username,
9125        String oldPassword,
9126        CmsSecondFactorInfo secondFactor,
9127        String newPassword)
9128    throws CmsException, CmsSecurityException {
9129
9130        if ((oldPassword != null) && (newPassword != null)) {
9131
9132            CmsUser user = null;
9133
9134            if (dbc.getRequestContext().getAttribute(CmsUserDriver.REQ_ATTR_DONT_DIGEST_PASSWORD) == null) {
9135                validatePassword(newPassword);
9136            }
9137
9138            // read the user as a system user to verify that the specified old password is correct
9139            try {
9140                user = getUserDriver(dbc).readUser(dbc, username, oldPassword, null);
9141            } catch (CmsDbEntryNotFoundException e) {
9142                throw new CmsDataAccessException(Messages.get().container(Messages.ERR_RESET_PASSWORD_1, username), e);
9143            }
9144
9145            if ((user == null) || user.isManaged()) {
9146                throw new CmsDataAccessException(Messages.get().container(Messages.ERR_RESET_PASSWORD_1, username));
9147            }
9148
9149            CmsTwoFactorAuthenticationHandler twoFactorHandler = OpenCms.getTwoFactorAuthenticationHandler();
9150            if (twoFactorHandler.needsTwoFactorAuthentication(user) && twoFactorHandler.hasSecondFactor(user)) {
9151                if (!twoFactorHandler.verifySecondFactor(user, secondFactor)) {
9152                    throw new CmsDataAccessException(
9153                        Messages.get().container(Messages.ERR_RESET_PASSWORD_1, username),
9154                        new RuntimeException("Verification code mismatch"));
9155                }
9156            }
9157
9158            getUserDriver(dbc).writePassword(dbc, username, oldPassword, newPassword);
9159            user.getAdditionalInfo().put(
9160                CmsUserSettings.ADDITIONAL_INFO_LAST_PASSWORD_CHANGE,
9161                "" + System.currentTimeMillis());
9162            user.deleteAdditionalInfo(CmsUserSettings.ADDITIONAL_INFO_PASSWORD_RESET);
9163            getUserDriver(dbc).writeUser(dbc, user);
9164
9165            if (!dbc.getProjectId().isNullUUID()) {
9166                // user modified event is not needed
9167                return;
9168            }
9169            // fire user modified event
9170            Map<String, Object> eventData = new HashMap<String, Object>();
9171            eventData.put(I_CmsEventListener.KEY_USER_ID, user.getId().toString());
9172            eventData.put(
9173                I_CmsEventListener.KEY_USER_ACTION,
9174                I_CmsEventListener.VALUE_USER_MODIFIED_ACTION_RESET_PASSWORD);
9175            eventData.put(
9176                I_CmsEventListener.KEY_USER_CHANGES,
9177                Integer.valueOf(CmsUser.FLAG_CORE_DATA | CmsUser.FLAG_CORE_DATA));
9178            OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_USER_MODIFIED, eventData));
9179
9180        } else if (CmsStringUtil.isEmpty(oldPassword)) {
9181            throw new CmsDataAccessException(Messages.get().container(Messages.ERR_PWD_OLD_MISSING_0));
9182        } else if (CmsStringUtil.isEmpty(newPassword)) {
9183            throw new CmsDataAccessException(Messages.get().container(Messages.ERR_PWD_NEW_MISSING_0));
9184        }
9185    }
9186
9187    /**
9188     * Restores a deleted resource identified by its structure id from the historical archive.<p>
9189     *
9190     * @param dbc the current database context
9191     * @param structureId the structure id of the resource to restore
9192     *
9193     * @throws CmsException if something goes wrong
9194     *
9195     * @see CmsObject#restoreDeletedResource(CmsUUID)
9196     */
9197    public CmsResource restoreDeletedResource(CmsDbContext dbc, CmsUUID structureId) throws CmsException {
9198
9199        // get the last version, which should be the deleted one
9200        int version = getHistoryDriver(dbc).readLastVersion(dbc, structureId);
9201        // get that version
9202        I_CmsHistoryResource histRes = getHistoryDriver(dbc).readResource(dbc, structureId, version);
9203
9204        // check the parent path
9205        CmsResource parent;
9206        try {
9207            // try to read the parent resource by id
9208            parent = getVfsDriver(dbc).readResource(dbc, dbc.currentProject().getUuid(), histRes.getParentId(), true);
9209        } catch (CmsVfsResourceNotFoundException e) {
9210            // if not found try to read the parent resource by name
9211            try {
9212                // try to read the parent resource by id
9213                parent = getVfsDriver(dbc).readResource(
9214                    dbc,
9215                    dbc.currentProject().getUuid(),
9216                    CmsResource.getParentFolder(histRes.getRootPath()),
9217                    true);
9218            } catch (CmsVfsResourceNotFoundException e1) {
9219                // if not found try to restore the parent resource
9220                restoreDeletedResource(dbc, histRes.getParentId());
9221                parent = readResource(dbc, histRes.getParentId(), CmsResourceFilter.IGNORE_EXPIRATION);
9222            }
9223        }
9224        // check write permissions
9225        m_securityManager.checkPermissions(
9226            dbc,
9227            parent,
9228            CmsPermissionSet.ACCESS_WRITE,
9229            false,
9230            CmsResourceFilter.IGNORE_EXPIRATION);
9231
9232        // check the name
9233        String path = parent.getRootPath();
9234        String resName = CmsResource.getName(histRes.getRootPath()); // name
9235        String ext = "";
9236        if (resName.charAt(resName.length() - 1) == '/') {
9237            resName = resName.substring(0, resName.length() - 1);
9238        } else {
9239            ext = CmsFileUtil.getExtension(resName); // extension
9240        }
9241        String nameWOExt = resName.substring(0, resName.length() - ext.length()); // name without extension
9242        for (int i = 1; true; i++) {
9243            try {
9244                readResource(dbc, path + resName, CmsResourceFilter.ALL);
9245                resName = nameWOExt + "_" + i + ext;
9246                // try the next resource name with following schema: path/name_{i}.ext
9247            } catch (CmsVfsResourceNotFoundException e) {
9248                // ok, we found a not used resource name
9249                break;
9250            }
9251        }
9252
9253        // check structure id
9254        CmsUUID id = structureId;
9255        if (getVfsDriver(dbc).validateStructureIdExists(dbc, dbc.currentProject().getUuid(), structureId)) {
9256            // should never happen, but if already exists create a new one
9257            id = new CmsUUID();
9258        }
9259
9260        byte[] contents = null;
9261        boolean isFolder = true;
9262
9263        // do we need the contents?
9264        if (histRes instanceof CmsFile) {
9265            contents = ((CmsFile)histRes).getContents();
9266            if ((contents == null) || (contents.length == 0)) {
9267                contents = getHistoryDriver(dbc).readContent(dbc, histRes.getResourceId(), histRes.getPublishTag());
9268            }
9269            isFolder = false;
9270        }
9271
9272        // now read the historical properties
9273        List<CmsProperty> properties = getHistoryDriver(dbc).readProperties(dbc, histRes);
9274
9275        // create the object to create
9276        CmsResource newResource = new CmsResource(
9277            id,
9278            histRes.getResourceId(),
9279            path + resName,
9280            histRes.getTypeId(),
9281            isFolder,
9282            histRes.getFlags(),
9283            dbc.currentProject().getUuid(),
9284            CmsResource.STATE_NEW,
9285            histRes.getDateCreated(),
9286            histRes.getUserCreated(),
9287            histRes.getDateLastModified(),
9288            dbc.currentUser().getId(),
9289            histRes.getDateReleased(),
9290            histRes.getDateExpired(),
9291            histRes.getSiblingCount(),
9292            histRes.getLength(),
9293            histRes.getDateContent(),
9294            histRes.getVersion());
9295
9296        // log it
9297        log(
9298            dbc,
9299            new CmsLogEntry(
9300                dbc,
9301                newResource.getStructureId(),
9302                CmsLogEntryType.RESOURCE_RESTORE_DELETED,
9303                new String[] {newResource.getRootPath()}),
9304            false);
9305
9306        // prevent the date last modified is set to the current time
9307        newResource.setDateLastModified(newResource.getDateLastModified());
9308        // restore the resource!
9309        CmsResource resource = createResource(dbc, path + resName, newResource, contents, properties, true);
9310        // set resource state to changed
9311        newResource.setState(CmsResource.STATE_CHANGED);
9312        getVfsDriver(dbc).writeResourceState(dbc, dbc.currentProject(), newResource, UPDATE_RESOURCE_STATE, false);
9313        newResource.setState(CmsResource.STATE_NEW);
9314        // fire the event
9315        Map<String, Object> data = new HashMap<String, Object>(2);
9316        data.put(I_CmsEventListener.KEY_RESOURCE, resource);
9317        data.put(I_CmsEventListener.KEY_CHANGE, Integer.valueOf(CHANGED_RESOURCE | CHANGED_CONTENT));
9318        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_MODIFIED, data));
9319        return newResource;
9320    }
9321
9322    /**
9323     * Restores a resource in the current project with a version from the historical archive.<p>
9324     *
9325     * @param dbc the current database context
9326     * @param resource the resource to restore from the archive
9327     * @param version the version number to restore from the archive
9328     *
9329     * @throws CmsException if something goes wrong
9330     *
9331     * @see CmsObject#restoreResourceVersion(CmsUUID, int)
9332     * @see I_CmsResourceType#restoreResource(CmsObject, CmsSecurityManager, CmsResource, int)
9333     */
9334    public CmsResource restoreResource(CmsDbContext dbc, CmsResource resource, int version) throws CmsException {
9335
9336        I_CmsHistoryResource historyResource = readResource(dbc, resource, version);
9337        CmsResourceState state = CmsResource.STATE_CHANGED;
9338        if (resource.getState().isNew()) {
9339            state = CmsResource.STATE_NEW;
9340        }
9341        int newVersion = resource.getVersion();
9342        if (resource.getState().isUnchanged()) {
9343            newVersion++;
9344        }
9345        CmsResource newResource = null;
9346        // is the resource a file?
9347        if (historyResource instanceof CmsFile) {
9348            // get the historical up flags
9349            int flags = historyResource.getFlags();
9350            if (resource.isLabeled()) {
9351                // set the flag for labeled links on the restored file
9352                flags |= CmsResource.FLAG_LABELED;
9353            }
9354            CmsFile newFile = new CmsFile(
9355                resource.getStructureId(),
9356                resource.getResourceId(),
9357                resource.getRootPath(),
9358                historyResource.getTypeId(),
9359                flags,
9360                dbc.currentProject().getUuid(),
9361                state,
9362                resource.getDateCreated(),
9363                historyResource.getUserCreated(),
9364                resource.getDateLastModified(),
9365                dbc.currentUser().getId(),
9366                historyResource.getDateReleased(),
9367                historyResource.getDateExpired(),
9368                resource.getSiblingCount(),
9369                historyResource.getLength(),
9370                historyResource.getDateContent(),
9371                newVersion,
9372                readFile(dbc, (CmsHistoryFile)historyResource).getContents());
9373
9374            // log it
9375            log(
9376                dbc,
9377                new CmsLogEntry(
9378                    dbc,
9379                    newFile.getStructureId(),
9380                    CmsLogEntryType.RESOURCE_HISTORY,
9381                    new String[] {newFile.getRootPath()}),
9382                false);
9383
9384            newResource = writeFile(dbc, newFile);
9385        } else {
9386            // it is a folder!
9387            newResource = new CmsFolder(
9388                resource.getStructureId(),
9389                resource.getResourceId(),
9390                resource.getRootPath(),
9391                historyResource.getTypeId(),
9392                historyResource.getFlags(),
9393                dbc.currentProject().getUuid(),
9394                state,
9395                resource.getDateCreated(),
9396                historyResource.getUserCreated(),
9397                resource.getDateLastModified(),
9398                dbc.currentUser().getId(),
9399                historyResource.getDateReleased(),
9400                historyResource.getDateExpired(),
9401                newVersion);
9402
9403            // log it
9404            log(
9405                dbc,
9406                new CmsLogEntry(
9407                    dbc,
9408                    newResource.getStructureId(),
9409                    CmsLogEntryType.RESOURCE_HISTORY,
9410                    new String[] {newResource.getRootPath()}),
9411                false);
9412
9413            writeResource(dbc, newResource);
9414        }
9415        if (newResource != null) {
9416            // now read the historical properties
9417            List<CmsProperty> historyProperties = getHistoryDriver(dbc).readProperties(dbc, historyResource);
9418            // remove all properties
9419            deleteAllProperties(dbc, newResource.getRootPath());
9420            // write them to the restored resource
9421            writePropertyObjects(dbc, newResource, historyProperties, false);
9422
9423            m_monitor.clearResourceCache();
9424        }
9425
9426        Map<String, Object> data = new HashMap<String, Object>(2);
9427        data.put(I_CmsEventListener.KEY_RESOURCE, resource);
9428        data.put(I_CmsEventListener.KEY_CHANGE, Integer.valueOf(CHANGED_RESOURCE | CHANGED_CONTENT));
9429        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_MODIFIED, data));
9430        return newResource;
9431    }
9432
9433    /**
9434     * Saves a list of aliases for the same structure id, replacing any aliases for the same structure id.<p>
9435     *
9436     * @param dbc the current database context
9437     * @param project the current project
9438     * @param structureId the structure id for which the aliases should be saved
9439     * @param aliases the list of aliases to save
9440     *
9441     * @throws CmsException if something goes wrong
9442     */
9443    public void saveAliases(CmsDbContext dbc, CmsProject project, CmsUUID structureId, List<CmsAlias> aliases)
9444    throws CmsException {
9445
9446        for (CmsAlias alias : aliases) {
9447            if (!structureId.equals(alias.getStructureId())) {
9448                throw new IllegalArgumentException("Aliases to replace must have the same structure id!");
9449            }
9450        }
9451        I_CmsVfsDriver vfsDriver = getVfsDriver(dbc);
9452        vfsDriver.deleteAliases(dbc, project, new CmsAliasFilter(null, null, structureId));
9453        for (CmsAlias alias : aliases) {
9454            String aliasPath = alias.getAliasPath();
9455            if (CmsAlias.ALIAS_PATTERN.matcher(aliasPath).matches()) {
9456                vfsDriver.insertAlias(dbc, project, alias);
9457            } else {
9458                LOG.error("Invalid alias path: " + aliasPath);
9459            }
9460        }
9461    }
9462
9463    /**
9464     * Replaces the complete list of rewrite aliases for a given site root.<p>
9465     *
9466     * @param dbc the current database context
9467     * @param siteRoot the site root for which the rewrite aliases should be replaced
9468     * @param newAliases the new aliases for the given site root
9469     * @throws CmsException if something goes wrong
9470     */
9471    public void saveRewriteAliases(CmsDbContext dbc, String siteRoot, List<CmsRewriteAlias> newAliases)
9472    throws CmsException {
9473
9474        CmsRewriteAliasFilter filter = new CmsRewriteAliasFilter().setSiteRoot(siteRoot);
9475        getVfsDriver(dbc).deleteRewriteAliases(dbc, filter);
9476        getVfsDriver(dbc).insertRewriteAliases(dbc, newAliases);
9477    }
9478
9479    /**
9480     * Searches for users which fit the given criteria.<p>
9481     *
9482     * @param dbc the database context
9483     * @param searchParams the search criteria
9484     *
9485     * @return the users which fit the search criteria
9486     *
9487     * @throws CmsDataAccessException if something goes wrong
9488     */
9489    public List<CmsUser> searchUsers(CmsDbContext dbc, CmsUserSearchParameters searchParams
9490
9491    ) throws CmsDataAccessException {
9492
9493        return getUserDriver(dbc).searchUsers(dbc, searchParams);
9494    }
9495
9496    /**
9497     * Changes the "expire" date of a resource.<p>
9498     *
9499     * @param dbc the current database context
9500     * @param resource the resource to touch
9501     * @param dateExpired the new expire date of the resource
9502     *
9503     * @throws CmsDataAccessException if something goes wrong
9504     *
9505     * @see CmsObject#setDateExpired(String, long, boolean)
9506     * @see I_CmsResourceType#setDateExpired(CmsObject, CmsSecurityManager, CmsResource, long, boolean)
9507     */
9508    public void setDateExpired(CmsDbContext dbc, CmsResource resource, long dateExpired) throws CmsDataAccessException {
9509
9510        resource.setDateExpired(dateExpired);
9511        if (resource.getState().isUnchanged()) {
9512            resource.setState(CmsResource.STATE_CHANGED);
9513        }
9514        getVfsDriver(dbc).writeResourceState(dbc, dbc.currentProject(), resource, UPDATE_STRUCTURE, false);
9515
9516        // modify the last modified project reference
9517        getVfsDriver(dbc).writeResourceState(dbc, dbc.currentProject(), resource, UPDATE_RESOURCE_PROJECT, false);
9518        // log
9519        log(
9520            dbc,
9521            new CmsLogEntry(
9522                dbc,
9523                resource.getStructureId(),
9524                CmsLogEntryType.RESOURCE_DATE_EXPIRED,
9525                new String[] {resource.getRootPath()}),
9526            false);
9527
9528        // clear the cache
9529        m_monitor.clearResourceCache();
9530
9531        // fire the event
9532        Map<String, Object> data = new HashMap<String, Object>(2);
9533        data.put(I_CmsEventListener.KEY_RESOURCE, resource);
9534        data.put(I_CmsEventListener.KEY_CHANGE, Integer.valueOf(CHANGED_TIMEFRAME));
9535        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_MODIFIED, data));
9536    }
9537
9538    /**
9539     * Changes the "last modified" timestamp of a resource.<p>
9540     *
9541     * @param dbc the current database context
9542     * @param resource the resource to touch
9543     * @param dateLastModified the new last modified date of the resource
9544     *
9545     * @throws CmsDataAccessException if something goes wrong
9546     *
9547     * @see CmsObject#setDateLastModified(String, long, boolean)
9548     * @see I_CmsResourceType#setDateLastModified(CmsObject, CmsSecurityManager, CmsResource, long, boolean)
9549     */
9550    public void setDateLastModified(CmsDbContext dbc, CmsResource resource, long dateLastModified)
9551    throws CmsDataAccessException {
9552
9553        // modify the last modification date
9554        resource.setDateLastModified(dateLastModified);
9555        if (resource.getState().isUnchanged()) {
9556            resource.setState(CmsResource.STATE_CHANGED);
9557        } else if (resource.getState().isNew() && (resource.getSiblingCount() > 1)) {
9558            // in case of new resources with siblings make sure the state is correct
9559            resource.setState(CmsResource.STATE_CHANGED);
9560        }
9561        resource.setUserLastModified(dbc.currentUser().getId());
9562        getVfsDriver(dbc).writeResourceState(dbc, dbc.currentProject(), resource, UPDATE_RESOURCE, false);
9563
9564        log(
9565            dbc,
9566            new CmsLogEntry(
9567                dbc,
9568                resource.getStructureId(),
9569                CmsLogEntryType.RESOURCE_TOUCHED,
9570                new String[] {resource.getRootPath()}),
9571            false);
9572
9573        // clear the cache
9574        m_monitor.clearResourceCache();
9575
9576        // fire the event
9577        Map<String, Object> data = new HashMap<String, Object>(2);
9578        data.put(I_CmsEventListener.KEY_RESOURCE, resource);
9579        data.put(I_CmsEventListener.KEY_CHANGE, Integer.valueOf(CHANGED_LASTMODIFIED));
9580        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_MODIFIED, data));
9581    }
9582
9583    /**
9584     * Changes the "release" date of a resource.<p>
9585     *
9586     * @param dbc the current database context
9587     * @param resource the resource to touch
9588     * @param dateReleased the new release date of the resource
9589     *
9590     * @throws CmsDataAccessException if something goes wrong
9591     *
9592     * @see CmsObject#setDateReleased(String, long, boolean)
9593     * @see I_CmsResourceType#setDateReleased(CmsObject, CmsSecurityManager, CmsResource, long, boolean)
9594     */
9595    public void setDateReleased(CmsDbContext dbc, CmsResource resource, long dateReleased)
9596    throws CmsDataAccessException {
9597
9598        // modify the last modification date
9599        resource.setDateReleased(dateReleased);
9600        if (resource.getState().isUnchanged()) {
9601            resource.setState(CmsResource.STATE_CHANGED);
9602        }
9603        getVfsDriver(dbc).writeResourceState(dbc, dbc.currentProject(), resource, UPDATE_STRUCTURE, false);
9604
9605        // modify the last modified project reference
9606        getVfsDriver(dbc).writeResourceState(dbc, dbc.currentProject(), resource, UPDATE_RESOURCE_PROJECT, false);
9607        // log it
9608        log(
9609            dbc,
9610            new CmsLogEntry(
9611                dbc,
9612                resource.getStructureId(),
9613                CmsLogEntryType.RESOURCE_DATE_RELEASED,
9614                new String[] {resource.getRootPath()}),
9615            false);
9616
9617        // clear the cache
9618        m_monitor.clearResourceCache();
9619
9620        // fire the event
9621        Map<String, Object> data = new HashMap<String, Object>(2);
9622        data.put(I_CmsEventListener.KEY_RESOURCE, resource);
9623        data.put(I_CmsEventListener.KEY_CHANGE, Integer.valueOf(CHANGED_TIMEFRAME));
9624        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_MODIFIED, data));
9625    }
9626
9627    /**
9628     * Sets a new parent group for an already existing group.<p>
9629     *
9630     * @param dbc the current database context
9631     * @param groupName the name of the group that should be written
9632     * @param parentGroupName the name of the parent group to set,
9633     *                      or <code>null</code> if the parent
9634     *                      group should be deleted.
9635     *
9636     * @throws CmsException if operation was not successful
9637     * @throws CmsDataAccessException if the group with <code>groupName</code> could not be read from VFS
9638     */
9639    public void setParentGroup(CmsDbContext dbc, String groupName, String parentGroupName)
9640    throws CmsException, CmsDataAccessException {
9641
9642        CmsGroup group = readGroup(dbc, groupName);
9643        CmsUUID parentGroupId = CmsUUID.getNullUUID();
9644
9645        // if the group exists, use its id, else set to unknown.
9646        if (parentGroupName != null) {
9647            parentGroupId = readGroup(dbc, parentGroupName).getId();
9648        }
9649
9650        group.setParentId(parentGroupId);
9651
9652        // write the changes to the cms
9653        writeGroup(dbc, group);
9654    }
9655
9656    /**
9657     * Sets the password for a user.<p>
9658     *
9659     * @param dbc the current database context
9660     * @param username the name of the user
9661     * @param newPassword the new password
9662     *
9663     * @throws CmsException if operation was not successful
9664     * @throws CmsIllegalArgumentException if the user with the <code>username</code> was not found
9665     */
9666    public void setPassword(CmsDbContext dbc, String username, String newPassword)
9667    throws CmsException, CmsIllegalArgumentException {
9668
9669        if (dbc.getRequestContext().getAttribute(CmsUserDriver.REQ_ATTR_DONT_DIGEST_PASSWORD) == null) {
9670            validatePassword(newPassword);
9671        }
9672
9673        // read the user as a system user to verify that the specified old password is correct
9674        CmsUser user = getUserDriver(dbc).readUser(dbc, username);
9675        // only continue if not found and read user from web might succeed
9676        getUserDriver(dbc).writePassword(dbc, username, null, newPassword);
9677        user.getAdditionalInfo().put(
9678            CmsUserSettings.ADDITIONAL_INFO_LAST_PASSWORD_CHANGE,
9679            "" + System.currentTimeMillis());
9680        getUserDriver(dbc).writeUser(dbc, user);
9681
9682        // fire user modified event
9683        Map<String, Object> eventData = new HashMap<String, Object>();
9684        eventData.put(I_CmsEventListener.KEY_USER_ID, user.getId().toString());
9685        eventData.put(I_CmsEventListener.KEY_USER_ACTION, I_CmsEventListener.VALUE_USER_MODIFIED_ACTION_RESET_PASSWORD);
9686        eventData.put(
9687            I_CmsEventListener.KEY_USER_CHANGES,
9688            Integer.valueOf(CmsUser.FLAG_CORE_DATA | CmsUser.FLAG_CORE_DATA));
9689        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_USER_MODIFIED, eventData));
9690    }
9691
9692    /**
9693     * Marks a subscribed resource as deleted.<p>
9694     *
9695     * @param dbc the database context
9696     * @param poolName the name of the database pool to use
9697     * @param resource the subscribed resource to mark as deleted
9698     *
9699     * @throws CmsException if something goes wrong
9700     */
9701    public void setSubscribedResourceAsDeleted(CmsDbContext dbc, String poolName, CmsResource resource)
9702    throws CmsException {
9703
9704        getSubscriptionDriver().setSubscribedResourceAsDeleted(dbc, poolName, resource);
9705    }
9706
9707    /**
9708     * Moves an user to the given organizational unit.<p>
9709     *
9710     * @param dbc the current db context
9711     * @param orgUnit the organizational unit to add the resource to
9712     * @param user the user that is to be moved to the organizational unit
9713     *
9714     * @throws CmsException if something goes wrong
9715     *
9716     * @see org.opencms.security.CmsOrgUnitManager#setUsersOrganizationalUnit(CmsObject, String, String)
9717     */
9718    public void setUsersOrganizationalUnit(CmsDbContext dbc, CmsOrganizationalUnit orgUnit, CmsUser user)
9719    throws CmsException {
9720
9721        if (!getGroupsOfUser(dbc, user.getName(), false).isEmpty()) {
9722            throw new CmsDbConsistencyException(
9723                Messages.get().container(Messages.ERR_ORGUNIT_MOVE_USER_2, orgUnit.getName(), user.getName()));
9724        }
9725
9726        // move the principal
9727        getUserDriver(dbc).setUsersOrganizationalUnit(dbc, orgUnit, user);
9728        // remove the principal from cache
9729        m_monitor.clearUserCache(user);
9730
9731        if (!dbc.getProjectId().isNullUUID()) {
9732            // user modified event is not needed
9733            return;
9734        }
9735        // fire user modified event
9736        Map<String, Object> eventData = new HashMap<String, Object>();
9737        eventData.put(I_CmsEventListener.KEY_USER_ID, user.getId().toString());
9738        eventData.put(I_CmsEventListener.KEY_OU_NAME, user.getOuFqn());
9739        eventData.put(I_CmsEventListener.KEY_USER_ACTION, I_CmsEventListener.VALUE_USER_MODIFIED_ACTION_SET_OU);
9740        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_USER_MODIFIED, eventData));
9741    }
9742
9743    /**
9744     * Subscribes the user or group to the resource.<p>
9745     *
9746     * @param dbc the database context
9747     * @param poolName the name of the database pool to use
9748     * @param principal the principal that subscribes to the resource
9749     * @param resource the resource to subscribe to
9750     *
9751     * @throws CmsException if something goes wrong
9752     */
9753    public void subscribeResourceFor(CmsDbContext dbc, String poolName, CmsPrincipal principal, CmsResource resource)
9754    throws CmsException {
9755
9756        getSubscriptionDriver().subscribeResourceFor(dbc, poolName, principal, resource);
9757    }
9758
9759    /**
9760     * Undelete the resource.<p>
9761     *
9762     * @param dbc the current database context
9763     * @param resource the name of the resource to apply this operation to
9764     *
9765     * @throws CmsException if something goes wrong
9766     *
9767     * @see CmsObject#undeleteResource(String, boolean)
9768     * @see I_CmsResourceType#undelete(CmsObject, CmsSecurityManager, CmsResource, boolean)
9769     */
9770    public void undelete(CmsDbContext dbc, CmsResource resource) throws CmsException {
9771
9772        if (!resource.getState().isDeleted()) {
9773            throw new CmsVfsException(
9774                Messages.get().container(
9775                    Messages.ERR_UNDELETE_FOR_RESOURCE_DELETED_1,
9776                    dbc.removeSiteRoot(resource.getRootPath())));
9777        }
9778
9779        // set the state to changed
9780        resource.setState(CmsResourceState.STATE_CHANGED);
9781        // perform the changes
9782        updateState(dbc, resource, false);
9783        // log it
9784        log(
9785            dbc,
9786            new CmsLogEntry(
9787                dbc,
9788                resource.getStructureId(),
9789                CmsLogEntryType.RESOURCE_UNDELETED,
9790                new String[] {resource.getRootPath()}),
9791            false);
9792        // clear the cache
9793        m_monitor.clearResourceCache();
9794
9795        // fire change event
9796        Map<String, Object> data = new HashMap<String, Object>(2);
9797        data.put(I_CmsEventListener.KEY_RESOURCE, resource);
9798        data.put(I_CmsEventListener.KEY_CHANGE, Integer.valueOf(CHANGED_RESOURCE));
9799        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_MODIFIED, data));
9800    }
9801
9802    /**
9803     * Undos all changes in the resource by restoring the version from the
9804     * online project to the current offline project.<p>
9805     *
9806     * @param dbc the current database context
9807     * @param resource the name of the resource to apply this operation to
9808     * @param mode the undo mode, one of the <code>{@link org.opencms.file.CmsResource.CmsResourceUndoMode}#UNDO_XXX</code> constants
9809     *      please note that the recursive flag is ignored at this level
9810     *
9811     * @throws CmsException if something goes wrong
9812     *
9813     * @see CmsObject#undoChanges(String, CmsResource.CmsResourceUndoMode)
9814     * @see I_CmsResourceType#undoChanges(CmsObject, CmsSecurityManager, CmsResource, CmsResource.CmsResourceUndoMode)
9815     */
9816    public void undoChanges(CmsDbContext dbc, CmsResource resource, CmsResource.CmsResourceUndoMode mode)
9817    throws CmsException {
9818
9819        if (resource.getState().isNew()) {
9820            // undo changes is impossible on a new resource
9821            throw new CmsVfsException(Messages.get().container(Messages.ERR_UNDO_CHANGES_FOR_RESOURCE_NEW_0));
9822        }
9823
9824        // we need this for later use
9825        CmsProject onlineProject = readProject(dbc, CmsProject.ONLINE_PROJECT_ID);
9826        // read the resource from the online project
9827        CmsResource onlineResource = getVfsDriver(
9828            dbc).readResource(dbc, CmsProject.ONLINE_PROJECT_ID, resource.getStructureId(), true);
9829
9830        CmsResource onlineResourceByPath = null;
9831        try {
9832            // this is needed to figure out if a moved resource overwrote a deleted one
9833            onlineResourceByPath = getVfsDriver(
9834                dbc).readResource(dbc, CmsProject.ONLINE_PROJECT_ID, resource.getRootPath(), true);
9835
9836            // force undo move operation if needed
9837            if (!mode.isUndoMove() && !onlineResourceByPath.getRootPath().equals(onlineResource.getRootPath())) {
9838                mode = mode.includeMove();
9839            }
9840        } catch (Exception e) {
9841            // ok
9842        }
9843
9844        boolean moved = !onlineResource.getRootPath().equals(resource.getRootPath());
9845        // undo move operation if required
9846        if (moved && mode.isUndoMove()) {
9847            moveResource(dbc, resource, onlineResource.getRootPath(), true);
9848            if ((onlineResourceByPath != null)
9849                && !onlineResourceByPath.getRootPath().equals(onlineResource.getRootPath())) {
9850                // was moved over deleted, so the deleted file has to be undone
9851                undoContentChanges(dbc, onlineProject, null, onlineResourceByPath, CmsResource.STATE_UNCHANGED, true);
9852            }
9853        }
9854        // undo content changes
9855        CmsResourceState newState = CmsResource.STATE_UNCHANGED;
9856        if (moved && !mode.isUndoMove()) {
9857            newState = CmsResource.STATE_CHANGED;
9858        }
9859        undoContentChanges(dbc, onlineProject, resource, onlineResource, newState, moved && mode.isUndoMove());
9860        // because undoContentChanges deletes the offline resource internally, we have
9861        // to write an entry to the log table to prevent the resource from appearing in the
9862        // user's publish list.
9863        log(
9864            dbc,
9865            new CmsLogEntry(
9866                dbc,
9867                resource.getStructureId(),
9868                CmsLogEntryType.RESOURCE_CHANGES_UNDONE,
9869                new String[] {resource.getRootPath()}),
9870            true);
9871
9872    }
9873
9874    /**
9875     * Unlocks all resources in the given project.<p>
9876     *
9877     * @param project the project to unlock the resources in
9878     */
9879    public void unlockProject(CmsProject project) {
9880
9881        // unlock all resources in the project
9882        m_lockManager.removeResourcesInProject(project.getUuid(), false);
9883        m_monitor.clearResourceCache();
9884        m_monitor.flushCache(CmsMemoryMonitor.CacheType.PROJECT, CmsMemoryMonitor.CacheType.PERMISSION);
9885    }
9886
9887    /**
9888     * Unlocks a resource.<p>
9889     *
9890     * @param dbc the current database context
9891     * @param resource the resource to unlock
9892     * @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
9893     * @param removeSystemLock <code>true</code>, if you also want to remove system locks
9894     *
9895     * @throws CmsException if something goes wrong
9896     *
9897     * @see CmsObject#unlockResource(String)
9898     * @see I_CmsResourceType#unlockResource(CmsObject, CmsSecurityManager, CmsResource)
9899     */
9900    public void unlockResource(CmsDbContext dbc, CmsResource resource, boolean force, boolean removeSystemLock)
9901    throws CmsException {
9902
9903        // update the resource cache
9904        m_monitor.clearResourceCache();
9905
9906        // now update lock status
9907        m_lockManager.removeResource(dbc, resource, force, removeSystemLock);
9908
9909        // we must also clear the permission cache
9910        m_monitor.flushCache(CmsMemoryMonitor.CacheType.PERMISSION);
9911
9912        // fire resource modification event
9913        Map<String, Object> data = new HashMap<String, Object>(2);
9914        data.put(I_CmsEventListener.KEY_RESOURCE, resource);
9915        data.put(I_CmsEventListener.KEY_CHANGE, Integer.valueOf(NOTHING_CHANGED));
9916        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_MODIFIED, data));
9917    }
9918
9919    /**
9920     * Unsubscribes all deleted resources that were deleted before the specified time stamp.<p>
9921     *
9922     * @param dbc the database context
9923     * @param poolName the name of the database pool to use
9924     * @param deletedTo the time stamp to which the resources have been deleted
9925     *
9926     * @throws CmsException if something goes wrong
9927     */
9928    public void unsubscribeAllDeletedResources(CmsDbContext dbc, String poolName, long deletedTo) throws CmsException {
9929
9930        getSubscriptionDriver().unsubscribeAllDeletedResources(dbc, poolName, deletedTo);
9931    }
9932
9933    /**
9934     * Unsubscribes the principal from all resources.<p>
9935     *
9936     * @param dbc the database context
9937     * @param poolName the name of the database pool to use
9938     * @param principal the principal that unsubscribes from all resources
9939     *
9940     * @throws CmsException if something goes wrong
9941     */
9942    public void unsubscribeAllResourcesFor(CmsDbContext dbc, String poolName, CmsPrincipal principal)
9943    throws CmsException {
9944
9945        getSubscriptionDriver().unsubscribeAllResourcesFor(dbc, poolName, principal);
9946
9947    }
9948
9949    /**
9950     * Unsubscribes the principal from the resource.<p>
9951     *
9952     * @param dbc the database context
9953     * @param poolName the name of the database pool to use
9954     * @param principal the principal that unsubscribes from the resource
9955     * @param resource the resource to unsubscribe from
9956     *
9957     * @throws CmsException if something goes wrong
9958     */
9959    public void unsubscribeResourceFor(CmsDbContext dbc, String poolName, CmsPrincipal principal, CmsResource resource)
9960    throws CmsException {
9961
9962        getSubscriptionDriver().unsubscribeResourceFor(dbc, poolName, principal, resource);
9963    }
9964
9965    /**
9966     * Unsubscribes all groups and users from the resource.<p>
9967     *
9968     * @param dbc the database context
9969     * @param poolName the name of the database pool to use
9970     * @param resource the resource to unsubscribe all groups and users from
9971     *
9972     * @throws CmsException if something goes wrong
9973     */
9974    public void unsubscribeResourceForAll(CmsDbContext dbc, String poolName, CmsResource resource) throws CmsException {
9975
9976        getSubscriptionDriver().unsubscribeResourceForAll(dbc, poolName, resource);
9977    }
9978
9979    /**
9980     * Update the export points.<p>
9981     *
9982     * All files and folders "inside" an export point are written.<p>
9983     *
9984     * @param dbc the current database context
9985     */
9986    public void updateExportPoints(CmsDbContext dbc) {
9987
9988        try {
9989            // read the export points and return immediately if there are no export points at all
9990            Set<CmsExportPoint> exportPoints = new HashSet<CmsExportPoint>();
9991            exportPoints.addAll(OpenCms.getExportPoints());
9992            exportPoints.addAll(OpenCms.getModuleManager().getExportPoints());
9993            if (exportPoints.size() == 0) {
9994                if (LOG.isWarnEnabled()) {
9995                    LOG.warn(Messages.get().getBundle().key(Messages.LOG_NO_EXPORT_POINTS_CONFIGURED_0));
9996                }
9997                return;
9998            }
9999
10000            // create the driver to write the export points
10001            I_CmsExportPointDriver exportPointDriver = OpenCms.getImportExportManager().createExportPointDriver(
10002                exportPoints);
10003
10004            // the export point hash table contains RFS export paths keyed by their internal VFS paths
10005            Iterator<String> i = exportPointDriver.getExportPointPaths().iterator();
10006            I_CmsVfsDriver vfsDriver = getVfsDriver(dbc);
10007            while (i.hasNext()) {
10008                String currentExportPoint = i.next();
10009
10010                // print some report messages
10011                if (LOG.isInfoEnabled()) {
10012                    LOG.info(Messages.get().getBundle().key(Messages.LOG_WRITE_EXPORT_POINT_1, currentExportPoint));
10013                }
10014
10015                try {
10016                    CmsResourceFilter filter = CmsResourceFilter.DEFAULT;
10017                    List<CmsResource> resources = vfsDriver.readResourceTree(
10018                        dbc,
10019                        CmsProject.ONLINE_PROJECT_ID,
10020                        currentExportPoint,
10021                        filter.getType(),
10022                        filter.getState(),
10023                        filter.getModifiedAfter(),
10024                        filter.getModifiedBefore(),
10025                        filter.getReleaseAfter(),
10026                        filter.getReleaseBefore(),
10027                        filter.getExpireAfter(),
10028                        filter.getExpireBefore(),
10029                        CmsDriverManager.READMODE_INCLUDE_TREE
10030                            | (filter.excludeType() ? CmsDriverManager.READMODE_EXCLUDE_TYPE : 0)
10031                            | (filter.excludeState() ? CmsDriverManager.READMODE_EXCLUDE_STATE : 0));
10032
10033                    Iterator<CmsResource> j = resources.iterator();
10034                    while (j.hasNext()) {
10035                        CmsResource currentResource = j.next();
10036
10037                        if (currentResource.isFolder()) {
10038                            // export the folder
10039                            exportPointDriver.createFolder(currentResource.getRootPath(), currentExportPoint);
10040                        } else {
10041                            // try to create the exportpoint folder
10042                            exportPointDriver.createFolder(currentExportPoint, currentExportPoint);
10043                            byte[] onlineContent = vfsDriver.readContent(
10044                                dbc,
10045                                CmsProject.ONLINE_PROJECT_ID,
10046                                currentResource.getResourceId());
10047                            // export the file content online
10048                            exportPointDriver.writeFile(
10049                                currentResource.getRootPath(),
10050                                currentExportPoint,
10051                                onlineContent);
10052                        }
10053                    }
10054                } catch (CmsException e) {
10055                    // there might exist export points without corresponding resources in the VFS
10056                    // -> ignore exceptions which are not "resource not found" exception quiet here
10057                    if (e instanceof CmsVfsResourceNotFoundException) {
10058                        if (LOG.isErrorEnabled()) {
10059                            LOG.error(Messages.get().getBundle().key(Messages.LOG_UPDATE_EXORT_POINTS_ERROR_0), e);
10060                        }
10061                    }
10062                }
10063            }
10064        } catch (Exception e) {
10065            if (LOG.isErrorEnabled()) {
10066                LOG.error(Messages.get().getBundle().key(Messages.LOG_UPDATE_EXORT_POINTS_ERROR_0), e);
10067            }
10068        }
10069    }
10070
10071    /**
10072     * Updates the last login date on the given user to the current time.<p>
10073     *
10074     * @param dbc the current database context
10075     * @param user the user to be updated
10076     *
10077     * @throws CmsException if operation was not successful
10078     */
10079    public void updateLastLoginDate(CmsDbContext dbc, CmsUser user) throws CmsException {
10080
10081        m_monitor.clearUserCache(user);
10082        // set the last login time to the current time
10083        user.setLastlogin(System.currentTimeMillis());
10084        dbc.setAttribute(ATTRIBUTE_LOGIN, user.getName());
10085        getUserDriver(dbc).writeUser(dbc, user);
10086        // update cache
10087        m_monitor.cacheUser(user);
10088
10089        // invalidate all user dependent caches
10090        m_monitor.flushCache(
10091            CmsMemoryMonitor.CacheType.ACL,
10092            CmsMemoryMonitor.CacheType.GROUP,
10093            CmsMemoryMonitor.CacheType.ORG_UNIT,
10094            CmsMemoryMonitor.CacheType.USER_LIST,
10095            CmsMemoryMonitor.CacheType.PERMISSION,
10096            CmsMemoryMonitor.CacheType.RESOURCE_LIST);
10097
10098        // fire user modified event
10099        Map<String, Object> eventData = new HashMap<String, Object>();
10100        eventData.put(I_CmsEventListener.KEY_USER_ID, user.getId().toString());
10101        eventData.put(I_CmsEventListener.KEY_USER_NAME, user.getName());
10102        eventData.put(I_CmsEventListener.KEY_USER_ACTION, I_CmsEventListener.VALUE_USER_MODIFIED_ACTION_WRITE_USER);
10103        eventData.put(I_CmsEventListener.KEY_USER_CHANGES, Integer.valueOf(CmsUser.FLAG_LAST_LOGIN));
10104        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_USER_MODIFIED, eventData));
10105    }
10106
10107    /**
10108     * Logs everything that has not been written to DB jet.<p>
10109     *
10110     * @param dbc the current db context
10111     *
10112     * @throws CmsDataAccessException if something goes wrong
10113     */
10114    public void updateLog(CmsDbContext dbc) throws CmsDataAccessException {
10115
10116        synchronized (m_publishListUpdateLock) {
10117
10118            if (m_log.isEmpty()) {
10119                return;
10120            }
10121
10122            List<CmsLogEntry> log = new ArrayList<CmsLogEntry>(m_log);
10123            m_log.clear();
10124            String logTableEnabledStr = (String)OpenCms.getRuntimeProperty(PARAM_LOG_TABLE_ENABLED);
10125            if (Boolean.parseBoolean(logTableEnabledStr)) { // defaults to 'false' if value not set
10126                m_projectDriver.log(dbc, log);
10127            }
10128            A_CmsLogPublishListConverter converter = null;
10129            switch (OpenCms.getPublishManager().getPublishListRemoveMode()) {
10130                case currentUser:
10131                    converter = new CmsLogPublishListConverterCurrentUser();
10132                    break;
10133                case allUsers:
10134                default:
10135                    converter = new CmsLogPublishListConverterAllUsers();
10136                    break;
10137            }
10138            for (CmsLogEntry entry : log) {
10139                converter.add(entry);
10140            }
10141            converter.writeChangesToDatabase(dbc, m_projectDriver);
10142        }
10143    }
10144
10145    /**
10146     * Updates/Creates the given relations for the given resource.<p>
10147     *
10148     * @param dbc the db context
10149     * @param resource the resource to update the relations for
10150     * @param links the links to consider for updating
10151     * @param updateSiblingState if true, sets the state of siblings whose relations have changed to 'changed' (unless they are new or deleted)
10152     *
10153     * @throws CmsException if something goes wrong
10154     *
10155     * @see CmsSecurityManager#updateRelationsForResource(CmsRequestContext, CmsResource, List, boolean)
10156     */
10157    public void updateRelationsForResource(
10158        CmsDbContext dbc,
10159        CmsResource resource,
10160        List<CmsLink> links,
10161        boolean updateSiblingState)
10162    throws CmsException {
10163
10164        if (links == null) {
10165            links = new ArrayList<>();
10166        }
10167
10168        // create new relation information
10169        I_CmsVfsDriver vfsDriver = getVfsDriver(dbc);
10170        Iterator<CmsLink> itLinks = links.iterator();
10171        Set<CmsRelation> relationsForOriginalResource = new HashSet<>();
10172        while (itLinks.hasNext()) {
10173            CmsLink link = itLinks.next();
10174            if (link.isInternal()) { // only update internal links
10175                if (CmsStringUtil.isEmptyOrWhitespaceOnly(link.getTarget())) {
10176                    // only an anchor
10177                    continue;
10178                }
10179                CmsUUID targetId = link.getStructureId();
10180                String destPath = link.getTarget();
10181
10182                if (targetId != null) {
10183                    // the link target may not be a VFS path even if the link id is a structure id,
10184                    // so if possible, we read the resource for the id and set the relation target to its
10185                    // real root path.
10186                    try {
10187                        CmsResource destRes = readResource(dbc, targetId, CmsResourceFilter.ALL);
10188                        destPath = destRes.getRootPath();
10189                    } catch (CmsVfsResourceNotFoundException e) {
10190                        // ignore
10191                    }
10192                }
10193
10194                CmsRelation originalRelation = new CmsRelation(
10195                    resource.getStructureId(),
10196                    resource.getRootPath(),
10197                    link.getStructureId(),
10198                    destPath,
10199                    link.getType());
10200                relationsForOriginalResource.add(originalRelation);
10201            }
10202        }
10203        List<CmsResource> siblings = resource.getSiblingCount() == 1
10204        ? Arrays.asList(resource)
10205        : readSiblings(dbc, resource, CmsResourceFilter.ALL);
10206
10207        for (CmsResource sibling : siblings) {
10208            // For each sibling, we determine which 'defined in content' relations it SHOULD have,
10209            // and only update the relations if that set differs from the ones it actually has.
10210            // If the updateSiblingState flag is set, then for siblings, we update the structure
10211            // state to changed (unless the state was 'deleted' or 'new').
10212            // This is so that even if the user later publishes only one sibling, the other sibling will still
10213            // show up as changed in the GUI, so the user can publish it separately (with its updated relations).
10214            Set<CmsRelation> relationsForSibling = relationsForOriginalResource.stream().map(
10215                relation -> new CmsRelation(
10216                    sibling.getStructureId(),
10217                    sibling.getRootPath(),
10218                    relation.getTargetId(),
10219                    relation.getTargetPath(),
10220                    relation.getType())).collect(Collectors.toSet());
10221            Set<CmsRelation> existingRelations = new HashSet<>(
10222                vfsDriver.readRelations(
10223                    dbc,
10224                    dbc.currentProject().getUuid(),
10225                    sibling,
10226                    CmsRelationFilter.TARGETS.filterDefinedInContent()));
10227            if (!existingRelations.equals(relationsForSibling)) {
10228                vfsDriver.deleteRelations(
10229                    dbc,
10230                    dbc.currentProject().getUuid(),
10231                    sibling,
10232                    CmsRelationFilter.TARGETS.filterDefinedInContent());
10233                for (CmsRelation relation : relationsForSibling) {
10234                    vfsDriver.createRelation(dbc, dbc.currentProject().getUuid(), relation);
10235                }
10236                if (!sibling.getState().isDeleted()
10237                    && !sibling.getState().isNew()
10238                    && (siblings.size() > 1)
10239                    && updateSiblingState) {
10240                    sibling.setState(CmsResource.STATE_CHANGED);
10241                    vfsDriver.writeResourceState(
10242                        dbc,
10243                        dbc.currentProject(),
10244                        sibling,
10245                        CmsDriverManager.UPDATE_STRUCTURE_STATE,
10246                        false);
10247                }
10248            }
10249        }
10250    }
10251
10252    /**
10253     * Returns <code>true</code> if a user is member of the given group.<p>
10254     *
10255     * @param dbc the current database context
10256     * @param username the name of the user to check
10257     * @param groupname the name of the group to check
10258     * @param readRoles if to read roles or groups
10259     *
10260     * @return <code>true</code>, if the user is in the group, <code>false</code> otherwise
10261     *
10262     * @throws CmsException if something goes wrong
10263     */
10264    public boolean userInGroup(CmsDbContext dbc, String username, String groupname, boolean readRoles)
10265    throws CmsException {
10266
10267        List<CmsGroup> groups = getGroupsOfUser(dbc, username, readRoles);
10268        for (int i = 0; i < groups.size(); i++) {
10269            CmsGroup group = groups.get(i);
10270            if (groupname.equals(group.getName()) || groupname.substring(1).equals(group.getName())) {
10271                return true;
10272            }
10273        }
10274        return false;
10275    }
10276
10277    /**
10278     * This method checks if a new password follows the rules for
10279     * new passwords, which are defined by a Class implementing the
10280     * <code>{@link org.opencms.security.I_CmsPasswordHandler}</code>
10281     * interface and configured in the opencms.properties file.<p>
10282     *
10283     * If this method throws no exception the password is valid.<p>
10284     *
10285     * @param password the new password that has to be checked
10286     *
10287     * @throws CmsSecurityException if the password is not valid
10288     */
10289    public void validatePassword(String password) throws CmsSecurityException {
10290
10291        OpenCms.getPasswordHandler().validatePassword(password);
10292    }
10293
10294    /**
10295     * Validates the relations for the given resources.<p>
10296     *
10297     * @param dbc the database context
10298     * @param publishList the resources to validate during publishing
10299     * @param report a report to write the messages to
10300     *
10301     * @return a map with lists of invalid links
10302     *          (<code>{@link org.opencms.relations.CmsRelation}}</code> objects)
10303     *          keyed by root paths
10304     *
10305     * @throws Exception if something goes wrong
10306     */
10307    public Map<String, List<CmsRelation>> validateRelations(
10308        CmsDbContext dbc,
10309        CmsPublishList publishList,
10310        I_CmsReport report)
10311    throws Exception {
10312
10313        return m_htmlLinkValidator.validateResources(dbc, publishList, report);
10314    }
10315
10316    /**
10317     * Writes an access control entries to a given resource.<p>
10318     *
10319     * @param dbc the current database context
10320     * @param resource the resource
10321     * @param ace the entry to write
10322     *
10323     * @throws CmsException if something goes wrong
10324     */
10325    public void writeAccessControlEntry(CmsDbContext dbc, CmsResource resource, CmsAccessControlEntry ace)
10326    throws CmsException {
10327
10328        // write the new ace
10329        getUserDriver(dbc).writeAccessControlEntry(dbc, dbc.currentProject(), ace);
10330
10331        // log it
10332        log(
10333            dbc,
10334            new CmsLogEntry(
10335                dbc,
10336                resource.getStructureId(),
10337                CmsLogEntryType.RESOURCE_PERMISSIONS,
10338                new String[] {resource.getRootPath()}),
10339            false);
10340
10341        // update the "last modified" information
10342        setDateLastModified(dbc, resource, resource.getDateLastModified());
10343
10344        // clear the cache
10345        m_monitor.clearAccessControlListCache();
10346
10347        // fire a resource modification event
10348        Map<String, Object> data = new HashMap<String, Object>(2);
10349        data.put(I_CmsEventListener.KEY_RESOURCE, resource);
10350        data.put(I_CmsEventListener.KEY_CHANGE, Integer.valueOf(CHANGED_ACCESSCONTROL));
10351        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_MODIFIED, data));
10352    }
10353
10354    /**
10355     * Writes all export points into the file system for the publish task
10356     * specified by trhe given publish history ID.<p>
10357     *
10358     * @param dbc the current database context
10359     * @param report an I_CmsReport instance to print output message, or null to write messages to the log file
10360     * @param publishHistoryId ID to identify the publish task in the publish history
10361     */
10362    public void writeExportPoints(CmsDbContext dbc, I_CmsReport report, CmsUUID publishHistoryId) {
10363
10364        boolean printReportHeaders = false;
10365        List<CmsPublishedResource> publishedResources = null;
10366        try {
10367            // read the "published resources" for the specified publish history ID
10368            publishedResources = getProjectDriver(dbc).readPublishedResources(dbc, publishHistoryId);
10369        } catch (CmsException e) {
10370            if (LOG.isErrorEnabled()) {
10371                LOG.error(
10372                    Messages.get().getBundle().key(Messages.ERR_READ_PUBLISHED_RESOURCES_FOR_ID_1, publishHistoryId),
10373                    e);
10374            }
10375        }
10376        if ((publishedResources == null) || publishedResources.isEmpty()) {
10377            if (LOG.isWarnEnabled()) {
10378                LOG.warn(Messages.get().getBundle().key(Messages.LOG_EMPTY_PUBLISH_HISTORY_1, publishHistoryId));
10379            }
10380            return;
10381        }
10382
10383        // read the export points and return immediately if there are no export points at all
10384        Set<CmsExportPoint> exportPoints = new HashSet<CmsExportPoint>();
10385        exportPoints.addAll(OpenCms.getExportPoints());
10386        exportPoints.addAll(OpenCms.getModuleManager().getExportPoints());
10387        if (exportPoints.size() == 0) {
10388            if (LOG.isWarnEnabled()) {
10389                LOG.warn(Messages.get().getBundle().key(Messages.LOG_NO_EXPORT_POINTS_CONFIGURED_0));
10390            }
10391            return;
10392        }
10393
10394        // create the driver to write the export points
10395        I_CmsExportPointDriver exportPointDriver = OpenCms.getImportExportManager().createExportPointDriver(
10396            exportPoints);
10397
10398        // the report may be null if the export point write was started by an event
10399        if (report == null) {
10400            if (dbc.getRequestContext() != null) {
10401                report = new CmsLogReport(dbc.getRequestContext().getLocale(), getClass());
10402            } else {
10403                report = new CmsLogReport(CmsLocaleManager.getDefaultLocale(), getClass());
10404            }
10405        }
10406
10407        // iterate over all published resources to export them
10408        I_CmsVfsDriver vfsDriver = getVfsDriver(dbc);
10409        Iterator<CmsPublishedResource> i = publishedResources.iterator();
10410        while (i.hasNext()) {
10411            CmsPublishedResource currentPublishedResource = i.next();
10412            String currentExportPoint = exportPointDriver.getExportPoint(currentPublishedResource.getRootPath());
10413
10414            if (currentExportPoint != null) {
10415                if (!printReportHeaders) {
10416                    report.println(
10417                        Messages.get().container(Messages.RPT_EXPORT_POINTS_WRITE_BEGIN_0),
10418                        I_CmsReport.FORMAT_HEADLINE);
10419                    printReportHeaders = true;
10420                }
10421
10422                // print report message
10423                if (currentPublishedResource.getState().isDeleted()) {
10424                    report.print(
10425                        Messages.get().container(Messages.RPT_EXPORT_POINTS_DELETE_0),
10426                        I_CmsReport.FORMAT_NOTE);
10427                } else {
10428                    report.print(Messages.get().container(Messages.RPT_EXPORT_POINTS_WRITE_0), I_CmsReport.FORMAT_NOTE);
10429                }
10430                report.print(
10431                    org.opencms.report.Messages.get().container(
10432                        org.opencms.report.Messages.RPT_ARGUMENT_1,
10433                        currentPublishedResource.getRootPath()));
10434                report.print(org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_DOTS_0));
10435
10436                if (currentPublishedResource.isFolder()) {
10437                    // export the folder
10438                    if (currentPublishedResource.getState().isDeleted()) {
10439                        exportPointDriver.deleteResource(currentPublishedResource.getRootPath(), currentExportPoint);
10440                    } else {
10441                        exportPointDriver.createFolder(currentPublishedResource.getRootPath(), currentExportPoint);
10442                    }
10443                    report.println(
10444                        org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_OK_0),
10445                        I_CmsReport.FORMAT_OK);
10446                } else {
10447                    // export the file
10448                    try {
10449                        if (currentPublishedResource.getState().isDeleted()) {
10450                            exportPointDriver.deleteResource(
10451                                currentPublishedResource.getRootPath(),
10452                                currentExportPoint);
10453                        } else {
10454                            // read the file content online
10455                            byte[] onlineContent = vfsDriver.readContent(
10456                                dbc,
10457                                CmsProject.ONLINE_PROJECT_ID,
10458                                currentPublishedResource.getResourceId());
10459                            exportPointDriver.writeFile(
10460                                currentPublishedResource.getRootPath(),
10461                                currentExportPoint,
10462                                onlineContent);
10463                        }
10464                        report.println(
10465                            org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_OK_0),
10466                            I_CmsReport.FORMAT_OK);
10467                    } catch (CmsException e) {
10468                        if (LOG.isErrorEnabled()) {
10469                            LOG.error(
10470                                Messages.get().getBundle().key(
10471                                    Messages.LOG_WRITE_EXPORT_POINT_ERROR_1,
10472                                    currentPublishedResource.getRootPath()),
10473                                e);
10474                        }
10475                        report.println(
10476                            org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_FAILED_0),
10477                            I_CmsReport.FORMAT_ERROR);
10478                    }
10479                }
10480            }
10481        }
10482        if (printReportHeaders) {
10483            report.println(
10484                Messages.get().container(Messages.RPT_EXPORT_POINTS_WRITE_END_0),
10485                I_CmsReport.FORMAT_HEADLINE);
10486        }
10487    }
10488
10489    /**
10490     * Writes a resource to the OpenCms VFS, including it's content.<p>
10491     *
10492     * Applies only to resources of type <code>{@link CmsFile}</code>
10493     * i.e. resources that have a binary content attached.<p>
10494     *
10495     * Certain resource types might apply content validation or transformation rules
10496     * before the resource is actually written to the VFS. The returned result
10497     * might therefore be a modified version from the provided original.<p>
10498     *
10499     * @param dbc the current database context
10500     * @param resource the resource to apply this operation to
10501     *
10502     * @return the written resource (may have been modified)
10503     *
10504     * @throws CmsException if something goes wrong
10505     *
10506     * @see CmsObject#writeFile(CmsFile)
10507     * @see I_CmsResourceType#writeFile(CmsObject, CmsSecurityManager, CmsFile)
10508     */
10509    public CmsFile writeFile(CmsDbContext dbc, CmsFile resource) throws CmsException {
10510
10511        resource.setUserLastModified(dbc.currentUser().getId());
10512        resource.setContents(resource.getContents()); // to be sure the content date is updated
10513
10514        getVfsDriver(dbc).writeResource(dbc, dbc.currentProject().getUuid(), resource, UPDATE_RESOURCE_STATE);
10515
10516        byte[] contents = resource.getContents();
10517        getVfsDriver(dbc).writeContent(dbc, resource, contents);
10518        // log it
10519        log(
10520            dbc,
10521            new CmsLogEntry(
10522                dbc,
10523                resource.getStructureId(),
10524                CmsLogEntryType.RESOURCE_CONTENT_MODIFIED,
10525                new String[] {resource.getRootPath()}),
10526            false);
10527
10528        // read the file back from db
10529        resource = new CmsFile(readResource(dbc, resource.getStructureId(), CmsResourceFilter.ALL));
10530        resource.setContents(contents);
10531
10532        deleteRelationsWithSiblings(dbc, resource);
10533
10534        // update the cache
10535        m_monitor.clearResourceCache();
10536
10537        Map<String, Object> data = new HashMap<String, Object>(2);
10538        data.put(I_CmsEventListener.KEY_RESOURCE, resource);
10539        data.put(I_CmsEventListener.KEY_CHANGE, Integer.valueOf(CHANGED_CONTENT));
10540        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_MODIFIED, data));
10541
10542        return resource;
10543    }
10544
10545    /**
10546     * Writes an already existing group.<p>
10547     *
10548     * The group id has to be a valid OpenCms group id.<br>
10549     *
10550     * The group with the given id will be completely overridden
10551     * by the given data.<p>
10552     *
10553     * @param dbc the current database context
10554     * @param group the group that should be written
10555     *
10556     * @throws CmsException if operation was not successful
10557     */
10558    public void writeGroup(CmsDbContext dbc, CmsGroup group) throws CmsException {
10559
10560        CmsGroup oldGroup = readGroup(dbc, group.getName());
10561        m_monitor.uncacheGroup(oldGroup);
10562        getUserDriver(dbc).writeGroup(dbc, group);
10563        m_monitor.cacheGroup(group);
10564
10565        if (!dbc.getProjectId().isNullUUID()) {
10566            // group modified event is not needed
10567            return;
10568        }
10569        // fire group modified event
10570        Map<String, Object> eventData = new HashMap<String, Object>();
10571        eventData.put(I_CmsEventListener.KEY_GROUP_ID, group.getId().toString());
10572        eventData.put(I_CmsEventListener.KEY_GROUP_NAME, oldGroup.getName());
10573        eventData.put(I_CmsEventListener.KEY_USER_ACTION, I_CmsEventListener.VALUE_GROUP_MODIFIED_ACTION_WRITE);
10574        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_GROUP_MODIFIED, eventData));
10575    }
10576
10577    /**
10578     * Creates an historical entry of the current project.<p>
10579     *
10580     * @param dbc the current database context
10581     * @param publishTag the version
10582     * @param publishDate the date of publishing
10583     *
10584     * @throws CmsDataAccessException if operation was not successful
10585     */
10586    public void writeHistoryProject(CmsDbContext dbc, int publishTag, long publishDate) throws CmsDataAccessException {
10587
10588        getHistoryDriver(dbc).writeProject(dbc, publishTag, publishDate);
10589    }
10590
10591    /**
10592     * Writes the locks that are currently stored in-memory to the database to allow restoring them
10593     * in future server startups.<p>
10594     *
10595     * This overwrites the locks previously stored in the underlying database table.<p>
10596     *
10597     * @param dbc the current database context
10598     *
10599     * @throws CmsException if something goes wrong
10600     */
10601    public void writeLocks(CmsDbContext dbc) throws CmsException {
10602
10603        m_lockManager.writeLocks(dbc);
10604    }
10605
10606    /**
10607     * Writes an already existing organizational unit.<p>
10608     *
10609     * The organizational unit id has to be a valid OpenCms organizational unit id.<br>
10610     *
10611     * The organizational unit with the given id will be completely overridden
10612     * by the given data.<p>
10613     *
10614     * @param dbc the current db context
10615     * @param organizationalUnit the organizational unit that should be written
10616     *
10617     * @throws CmsException if operation was not successful
10618     *
10619     * @see org.opencms.security.CmsOrgUnitManager#writeOrganizationalUnit(CmsObject, CmsOrganizationalUnit)
10620     */
10621    public void writeOrganizationalUnit(CmsDbContext dbc, CmsOrganizationalUnit organizationalUnit)
10622    throws CmsException {
10623
10624        m_monitor.uncacheOrgUnit(organizationalUnit);
10625        getUserDriver(dbc).writeOrganizationalUnit(dbc, organizationalUnit);
10626
10627        // create a publish list for the 'virtual' publish event
10628        CmsResource ouRes = readResource(dbc, organizationalUnit.getId(), CmsResourceFilter.DEFAULT);
10629        CmsPublishList pl = new CmsPublishList(ouRes, false);
10630        pl.add(ouRes, false);
10631
10632        getProjectDriver(dbc).writePublishHistory(
10633            dbc,
10634            pl.getPublishHistoryId(),
10635            new CmsPublishedResource(ouRes, -1, CmsResourceState.STATE_NEW));
10636
10637        // fire the 'virtual' publish event
10638        Map<String, Object> eventData = new HashMap<String, Object>();
10639        eventData.put(I_CmsEventListener.KEY_PUBLISHID, pl.getPublishHistoryId().toString());
10640        eventData.put(I_CmsEventListener.KEY_PROJECTID, dbc.currentProject().getUuid());
10641        eventData.put(I_CmsEventListener.KEY_DBCONTEXT, dbc);
10642        CmsEvent afterPublishEvent = new CmsEvent(I_CmsEventListener.EVENT_PUBLISH_PROJECT, eventData);
10643        OpenCms.fireCmsEvent(afterPublishEvent);
10644
10645        m_monitor.cacheOrgUnit(organizationalUnit);
10646    }
10647
10648    /**
10649     * Writes an already existing project.<p>
10650     *
10651     * The project id has to be a valid OpenCms project id.<br>
10652     *
10653     * The project with the given id will be completely overridden
10654     * by the given data.<p>
10655     *
10656     * @param dbc the current database context
10657     * @param project the project that should be written
10658     *
10659     * @throws CmsException if operation was not successful
10660     */
10661    public void writeProject(CmsDbContext dbc, CmsProject project) throws CmsException {
10662
10663        m_monitor.uncacheProject(project);
10664        getProjectDriver(dbc).writeProject(dbc, project);
10665        m_monitor.cacheProject(project);
10666    }
10667
10668    /**
10669     * Writes a new project into the PROJECT_LASTMODIFIED field of a resource record.<p>
10670     *
10671     * @param dbc the current database context
10672     * @param resource the resource which should be modified
10673     * @param projectId the project id to write
10674     *
10675     * @throws CmsDataAccessException if the database access fails
10676     */
10677    public void writeProjectLastModified(CmsDbContext dbc, CmsResource resource, CmsUUID projectId)
10678    throws CmsDataAccessException {
10679
10680        I_CmsVfsDriver vfsDriver = getVfsDriver(dbc);
10681        vfsDriver.writeLastModifiedProjectId(dbc, dbc.currentProject(), projectId, resource);
10682    }
10683
10684    /**
10685     * Writes a property for a specified resource.<p>
10686     *
10687     * @param dbc the current database context
10688     * @param resource the resource to write the property for
10689     * @param property the property to write
10690     *
10691     * @throws CmsException if something goes wrong
10692     *
10693     * @see CmsObject#writePropertyObject(String, CmsProperty)
10694     * @see I_CmsResourceType#writePropertyObject(CmsObject, CmsSecurityManager, CmsResource, CmsProperty)
10695     */
10696    public void writePropertyObject(CmsDbContext dbc, CmsResource resource, CmsProperty property) throws CmsException {
10697
10698        try {
10699            if (property == CmsProperty.getNullProperty()) {
10700                // skip empty or null properties
10701                return;
10702            }
10703
10704            // test if and what state should be updated
10705            // 0: none, 1: structure, 2: resource
10706            int updateState = getUpdateState(dbc, resource, Collections.singletonList(property));
10707
10708            // write the property
10709            getVfsDriver(dbc).writePropertyObject(dbc, dbc.currentProject(), resource, property);
10710
10711            if (updateState > 0) {
10712                updateState(dbc, resource, updateState == 2);
10713            }
10714            // log it
10715            log(
10716                dbc,
10717                new CmsLogEntry(
10718                    dbc,
10719                    resource.getStructureId(),
10720                    CmsLogEntryType.RESOURCE_PROPERTIES,
10721                    new String[] {resource.getRootPath()}),
10722                false);
10723
10724        } finally {
10725            // update the driver manager cache
10726            m_monitor.clearResourceCache();
10727            m_monitor.flushCache(CmsMemoryMonitor.CacheType.PROPERTY, CmsMemoryMonitor.CacheType.PROPERTY_LIST);
10728
10729            // fire an event that a property of a resource has been modified
10730            Map<String, Object> data = new HashMap<String, Object>();
10731            data.put(I_CmsEventListener.KEY_RESOURCE, resource);
10732            data.put("property", property);
10733            OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_PROPERTY_MODIFIED, data));
10734        }
10735    }
10736
10737    /**
10738     * Writes a list of properties for a specified resource.<p>
10739     *
10740     * Code calling this method has to ensure that the no properties
10741     * <code>a, b</code> are contained in the specified list so that <code>a.equals(b)</code>,
10742     * otherwise an exception is thrown.<p>
10743     *
10744     * @param dbc the current database context
10745     * @param resource the resource to write the properties for
10746     * @param properties the list of properties to write
10747     * @param updateState if <code>true</code> the state of the resource will be updated
10748     *
10749     * @throws CmsException if something goes wrong
10750     *
10751     * @see CmsObject#writePropertyObjects(String, List)
10752     * @see I_CmsResourceType#writePropertyObjects(CmsObject, CmsSecurityManager, CmsResource, List)
10753     */
10754    public void writePropertyObjects(
10755        CmsDbContext dbc,
10756        CmsResource resource,
10757        List<CmsProperty> properties,
10758        boolean updateState)
10759    throws CmsException {
10760
10761        if ((properties == null) || (properties.size() == 0)) {
10762            // skip empty or null lists
10763            return;
10764        }
10765
10766        try {
10767            // the specified list must not contain two or more equal property objects
10768            for (int i = 0, n = properties.size(); i < n; i++) {
10769                Set<String> keyValidationSet = new HashSet<String>();
10770                CmsProperty property = properties.get(i);
10771                if (!keyValidationSet.contains(property.getName())) {
10772                    keyValidationSet.add(property.getName());
10773                } else {
10774                    throw new CmsVfsException(
10775                        Messages.get().container(Messages.ERR_VFS_INVALID_PROPERTY_LIST_1, property.getName()));
10776                }
10777            }
10778
10779            // test if and what state should be updated
10780            // 0: none, 1: structure, 2: resource
10781            int updateStateValue = 0;
10782            if (updateState) {
10783                updateStateValue = getUpdateState(dbc, resource, properties);
10784            }
10785            I_CmsVfsDriver vfsDriver = getVfsDriver(dbc);
10786            for (int i = 0; i < properties.size(); i++) {
10787                // write the property
10788                CmsProperty property = properties.get(i);
10789                vfsDriver.writePropertyObject(dbc, dbc.currentProject(), resource, property);
10790            }
10791
10792            if (updateStateValue > 0) {
10793                // update state
10794                updateState(dbc, resource, (updateStateValue == 2));
10795            }
10796
10797            if (updateState) {
10798                // log it
10799                log(
10800                    dbc,
10801                    new CmsLogEntry(
10802                        dbc,
10803                        resource.getStructureId(),
10804                        CmsLogEntryType.RESOURCE_PROPERTIES,
10805                        new String[] {resource.getRootPath()}),
10806                    false);
10807            }
10808        } finally {
10809            // update the driver manager cache
10810            m_monitor.clearResourceCache();
10811            m_monitor.flushCache(CmsMemoryMonitor.CacheType.PROPERTY, CmsMemoryMonitor.CacheType.PROPERTY_LIST);
10812
10813            // fire an event that the properties of a resource have been modified
10814            OpenCms.fireCmsEvent(
10815                new CmsEvent(
10816                    I_CmsEventListener.EVENT_RESOURCE_AND_PROPERTIES_MODIFIED,
10817                    Collections.<String, Object> singletonMap(I_CmsEventListener.KEY_RESOURCE, resource)));
10818        }
10819    }
10820
10821    /**
10822     * Updates a publish job.<p>
10823     *
10824     * @param dbc the current database context
10825     * @param publishJob the publish job to update
10826     *
10827     * @throws CmsException if something goes wrong
10828     */
10829    public void writePublishJob(CmsDbContext dbc, CmsPublishJobInfoBean publishJob) throws CmsException {
10830
10831        getProjectDriver(dbc).writePublishJob(dbc, publishJob);
10832    }
10833
10834    /**
10835     * Writes the publish report for a publish job.<p>
10836     *
10837     * @param dbc the current database context
10838     * @param publishJob the publish job
10839     * @throws CmsException if something goes wrong
10840     */
10841    public void writePublishReport(CmsDbContext dbc, CmsPublishJobInfoBean publishJob) throws CmsException {
10842
10843        CmsPublishReport report = (CmsPublishReport)publishJob.removePublishReport();
10844
10845        if (report != null) {
10846            getProjectDriver(dbc).writePublishReport(dbc, publishJob.getPublishHistoryId(), report.getContents());
10847        }
10848    }
10849
10850    /**
10851     * Writes a resource to the OpenCms VFS.<p>
10852     *
10853     * @param dbc the current database context
10854     * @param resource the resource to write
10855     *
10856     * @throws CmsException if something goes wrong
10857     */
10858    public void writeResource(CmsDbContext dbc, CmsResource resource) throws CmsException {
10859
10860        // access was granted - write the resource
10861        resource.setUserLastModified(dbc.currentUser().getId());
10862        CmsUUID projectId = ((dbc.getProjectId() == null) || dbc.getProjectId().isNullUUID())
10863        ? dbc.currentProject().getUuid()
10864        : dbc.getProjectId();
10865
10866        getVfsDriver(dbc).writeResource(dbc, projectId, resource, UPDATE_RESOURCE_STATE);
10867
10868        // make sure the written resource has the state correctly set
10869        if (resource.getState().isUnchanged()) {
10870            resource.setState(CmsResource.STATE_CHANGED);
10871        }
10872
10873        // delete in content relations if the new type is not parseable
10874        if (!(OpenCms.getResourceManager().getResourceType(resource.getTypeId()) instanceof I_CmsLinkParseable)) {
10875            deleteRelationsWithSiblings(dbc, resource);
10876        }
10877
10878        // update the cache
10879        m_monitor.clearResourceCache();
10880        Map<String, Object> data = new HashMap<String, Object>(2);
10881        data.put(I_CmsEventListener.KEY_RESOURCE, resource);
10882        data.put(I_CmsEventListener.KEY_CHANGE, Integer.valueOf(CHANGED_RESOURCE));
10883        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_MODIFIED, data));
10884    }
10885
10886    /**
10887     * Inserts an entry in the published resource table.<p>
10888     *
10889     * This is done during static export.<p>
10890     *
10891     * @param dbc the current database context
10892     * @param resourceName The name of the resource to be added to the static export
10893     * @param linkType the type of resource exported (0= non-parameter, 1=parameter)
10894     * @param linkParameter the parameters added to the resource
10895     * @param timestamp a time stamp for writing the data into the db
10896     *
10897     * @throws CmsException if something goes wrong
10898     */
10899    public void writeStaticExportPublishedResource(
10900        CmsDbContext dbc,
10901        String resourceName,
10902        int linkType,
10903        String linkParameter,
10904        long timestamp)
10905    throws CmsException {
10906
10907        getProjectDriver(dbc).writeStaticExportPublishedResource(dbc, resourceName, linkType, linkParameter, timestamp);
10908    }
10909
10910    /**
10911     * Adds a new url name mapping for a structure id.<p>
10912     *
10913     * Instead of taking the name directly, this method takes an iterator of strings
10914     * which generates candidate URL names on-the-fly. The first generated name which is
10915     * not already mapped to another structure id will be chosen for the new URL name mapping.
10916     *
10917     * @param dbc the current database context
10918     * @param nameSeq the sequence of URL name candidates
10919     * @param structureId the structure id to which the url name should be mapped
10920     * @param locale the locale for which the mapping should be written
10921     * @param replaceOnPublish name mappings for which this is set will replace all other mappings for the same resource on publishing
10922     *
10923     * @return the actual name which was mapped to the structure id
10924     *
10925     * @throws CmsDataAccessException if something goes wrong
10926     */
10927    public String writeUrlNameMapping(
10928        CmsDbContext dbc,
10929        Iterator<String> nameSeq,
10930        CmsUUID structureId,
10931        String locale,
10932        boolean replaceOnPublish)
10933    throws CmsDataAccessException {
10934
10935        String bestName = findBestNameForUrlNameMapping(dbc, nameSeq, structureId, locale);
10936        addOrReplaceUrlNameMapping(dbc, bestName, structureId, locale, replaceOnPublish);
10937        return bestName;
10938    }
10939
10940    /**
10941     * Updates the user information. <p>
10942     *
10943     * The user id has to be a valid OpenCms user id.<br>
10944     *
10945     * The user with the given id will be completely overridden
10946     * by the given data.<p>
10947     *
10948     * @param dbc the current database context
10949     * @param user the user to be updated
10950     *
10951     * @throws CmsException if operation was not successful
10952     */
10953    public void writeUser(CmsDbContext dbc, CmsUser user) throws CmsException {
10954
10955        CmsUser oldUser = readUser(dbc, user.getId());
10956        m_monitor.clearUserCache(oldUser);
10957        getUserDriver(dbc).writeUser(dbc, user);
10958        m_monitor.flushCache(CmsMemoryMonitor.CacheType.USER_LIST);
10959
10960        if (!dbc.getProjectId().isNullUUID()) {
10961            // user modified event is not needed
10962            return;
10963        }
10964        // fire user modified event
10965        Map<String, Object> eventData = new HashMap<String, Object>();
10966        eventData.put(I_CmsEventListener.KEY_USER_ID, user.getId().toString());
10967        eventData.put(I_CmsEventListener.KEY_USER_NAME, oldUser.getName());
10968        eventData.put(I_CmsEventListener.KEY_USER_ACTION, I_CmsEventListener.VALUE_USER_MODIFIED_ACTION_WRITE_USER);
10969        eventData.put(I_CmsEventListener.KEY_USER_CHANGES, Integer.valueOf(user.getChanges(oldUser)));
10970        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_USER_MODIFIED, eventData));
10971        OpenCms.getTwoFactorAuthenticationHandler().trackUserChange(dbc.getRequestContext(), oldUser, user);
10972    }
10973
10974    /**
10975     * Adds or replaces a new url name mapping in the offline project.<p>
10976     *
10977     * @param dbc the current database context
10978     * @param name the URL name of the mapping
10979     * @param structureId the structure id of the mapping
10980     * @param locale the locale of the mapping
10981     * @param replaceOnPublish if the mapping shoudl replace previous URL name mappings when published
10982     *
10983     * @throws CmsDataAccessException if something goes wrong
10984     */
10985    protected void addOrReplaceUrlNameMapping(
10986        CmsDbContext dbc,
10987        String name,
10988        CmsUUID structureId,
10989        String locale,
10990        boolean replaceOnPublish)
10991    throws CmsDataAccessException {
10992
10993        getVfsDriver(dbc).deleteUrlNameMappingEntries(
10994            dbc,
10995            false,
10996            CmsUrlNameMappingFilter.ALL.filterStructureId(structureId).filterLocale(locale).filterStates(
10997                CmsUrlNameMappingEntry.MAPPING_STATUS_NEW,
10998                CmsUrlNameMappingEntry.MAPPING_STATUS_REPLACE_ON_PUBLISH));
10999        CmsUrlNameMappingEntry newEntry = new CmsUrlNameMappingEntry(
11000            name,
11001            structureId,
11002            replaceOnPublish
11003            ? CmsUrlNameMappingEntry.MAPPING_STATUS_REPLACE_ON_PUBLISH
11004            : CmsUrlNameMappingEntry.MAPPING_STATUS_NEW,
11005            System.currentTimeMillis(),
11006            locale);
11007        getVfsDriver(dbc).addUrlNameMappingEntry(dbc, false, newEntry);
11008    }
11009
11010    /**
11011     * Converts a resource to a folder (if possible).<p>
11012     *
11013     * @param resource the resource to convert
11014     * @return the converted resource
11015     *
11016     * @throws CmsVfsResourceNotFoundException if the resource is not a folder
11017     */
11018    protected CmsFolder convertResourceToFolder(CmsResource resource) throws CmsVfsResourceNotFoundException {
11019
11020        if (resource.isFolder()) {
11021            return new CmsFolder(resource);
11022        }
11023
11024        throw new CmsVfsResourceNotFoundException(
11025            Messages.get().container(Messages.ERR_ACCESS_FILE_AS_FOLDER_1, resource.getRootPath()));
11026    }
11027
11028    /**
11029     * Helper method for creating a driver from configuration data.<p>
11030     *
11031     * @param dbc the db context
11032     * @param configManager the configuration manager
11033     * @param config the configuration
11034     * @param driverChainKey the configuration key under which the driver chain is stored
11035     * @param suffix the suffix to append to a driver chain entry to get the key for the driver class
11036     *
11037     * @return the newly created driver
11038     */
11039    protected Object createDriver(
11040        CmsDbContext dbc,
11041        CmsConfigurationManager configManager,
11042        CmsParameterConfiguration config,
11043        String driverChainKey,
11044        String suffix) {
11045
11046        // read the vfs driver class properties and initialize a new instance
11047        List<String> drivers = config.getList(driverChainKey);
11048        String driverKey = drivers.get(0) + suffix;
11049        String driverName = config.get(driverKey);
11050        drivers = (drivers.size() > 1) ? drivers.subList(1, drivers.size()) : null;
11051        if (driverName == null) {
11052            CmsLog.INIT.error(Messages.get().getBundle().key(Messages.INIT_DRIVER_FAILED_1, driverKey));
11053        }
11054        Object result = newDriverInstance(dbc, configManager, driverName, drivers);
11055        if ("true".equalsIgnoreCase(System.getProperty("opencms.profile.drivers"))) {
11056            result = wrapDriverInProfilingProxy(result);
11057        }
11058        return result;
11059    }
11060
11061    /**
11062     * Deletes all relations for the given resource and all its siblings.<p>
11063     *
11064     * @param dbc the current database context
11065     * @param resource the resource to delete the resource for
11066     *
11067     * @throws CmsException if something goes wrong
11068     */
11069    protected void deleteRelationsWithSiblings(CmsDbContext dbc, CmsResource resource) throws CmsException {
11070
11071        // get all siblings
11072        List<CmsResource> siblings;
11073        if (resource.getSiblingCount() > 1) {
11074            siblings = readSiblings(dbc, resource, CmsResourceFilter.ALL);
11075        } else {
11076            siblings = new ArrayList<CmsResource>();
11077            siblings.add(resource);
11078        }
11079        // clean the relations in content for all siblings
11080        I_CmsVfsDriver vfsDriver = getVfsDriver(dbc);
11081        Iterator<CmsResource> it = siblings.iterator();
11082        while (it.hasNext()) {
11083            CmsResource sibling = it.next();
11084            // clean the relation information for this sibling
11085            vfsDriver.deleteRelations(
11086                dbc,
11087                dbc.currentProject().getUuid(),
11088                sibling,
11089                CmsRelationFilter.TARGETS.filterDefinedInContent());
11090        }
11091    }
11092
11093    /**
11094     * Tries to add sub-resources of moved folders to the publish list and throws an exception if the publish list still does
11095     * not contain some  sub-resources of the moved folders.<p>
11096     *
11097     * @param cms the current CMS context
11098     * @param dbc the current database context
11099     * @param pubList the publish list
11100     * @throws CmsException if something goes wrong
11101     */
11102    protected void ensureSubResourcesOfMovedFoldersPublished(CmsObject cms, CmsDbContext dbc, CmsPublishList pubList)
11103    throws CmsException {
11104
11105        List<CmsResource> topMovedFolders = pubList.getTopMovedFolders(cms);
11106        Iterator<CmsResource> folderIt = topMovedFolders.iterator();
11107        while (folderIt.hasNext()) {
11108            CmsResource folder = folderIt.next();
11109            addSubResources(dbc, pubList, folder, resource -> !resource.getState().isNew());
11110        }
11111        List<CmsResource> missingSubResources = pubList.getMissingSubResources(cms, topMovedFolders);
11112        if (missingSubResources.isEmpty()) {
11113            return;
11114        }
11115
11116        StringBuffer pathBuffer = new StringBuffer();
11117
11118        for (CmsResource missing : missingSubResources) {
11119            pathBuffer.append(missing.getRootPath());
11120            pathBuffer.append(" ");
11121        }
11122        throw new CmsVfsException(
11123            Messages.get().container(Messages.RPT_CHILDREN_OF_MOVED_FOLDER_NOT_PUBLISHED_1, pathBuffer.toString()));
11124
11125    }
11126
11127    /**
11128     * Tries to find the best name for an URL name mapping for the given structure id.<p>
11129     *
11130     * @param dbc the database context
11131     * @param nameSeq the sequence of name candidates
11132     * @param structureId the structure id to which an URL name should be mapped
11133     * @param locale the locale for which the URL name should be mapped
11134     *
11135     * @return the selected URL name candidate
11136     *
11137     * @throws CmsDataAccessException if something goes wrong
11138     */
11139    protected String findBestNameForUrlNameMapping(
11140        CmsDbContext dbc,
11141        Iterator<String> nameSeq,
11142        CmsUUID structureId,
11143        String locale)
11144    throws CmsDataAccessException {
11145
11146        String newName;
11147        boolean alreadyInUse;
11148        do {
11149            newName = nameSeq.next();
11150            alreadyInUse = false;
11151            CmsUrlNameMappingFilter filter = CmsUrlNameMappingFilter.ALL.filterName(newName);
11152            List<CmsUrlNameMappingEntry> entriesWithSameName = getVfsDriver(dbc).readUrlNameMappingEntries(
11153                dbc,
11154                false,
11155                filter);
11156            for (CmsUrlNameMappingEntry entry : entriesWithSameName) {
11157                boolean sameId = entry.getStructureId().equals(structureId);
11158                if (!sameId) {
11159                    // name already used for other resource, or for different locale of the same resource
11160                    alreadyInUse = true;
11161                    break;
11162                }
11163            }
11164        } while (alreadyInUse);
11165        return newName;
11166    }
11167
11168    /**
11169     * Helper method for finding the 'best' URL name to use for a new URL name mapping.<p>
11170     *
11171     * Since the name given as a parameter may be already used, this method will try to append numeric suffixes
11172     * to the name to find a mapping name which is not used.<p>
11173     *
11174     * @param dbc the current database context
11175     * @param name the name of the mapping
11176     * @param structureId the structure id to which the name is mapped
11177     *
11178     * @return the best name which was found for the new mapping
11179     *
11180     * @throws CmsDataAccessException if something goes wrong
11181     */
11182    protected String findBestNameForUrlNameMapping(CmsDbContext dbc, String name, CmsUUID structureId)
11183    throws CmsDataAccessException {
11184
11185        List<CmsUrlNameMappingEntry> entriesStartingWithName = getVfsDriver(dbc).readUrlNameMappingEntries(
11186            dbc,
11187            false,
11188            CmsUrlNameMappingFilter.ALL.filterNamePattern(name + "%").filterRejectStructureId(structureId));
11189        Set<String> usedNames = new HashSet<String>();
11190        for (CmsUrlNameMappingEntry entry : entriesStartingWithName) {
11191            usedNames.add(entry.getName());
11192        }
11193        int counter = 0;
11194        String numberedName;
11195        do {
11196            numberedName = getNumberedName(name, counter);
11197            counter += 1;
11198        } while (usedNames.contains(numberedName));
11199        return numberedName;
11200    }
11201
11202    /**
11203     * Returns the lock manager instance.<p>
11204     *
11205     * @return the lock manager instance
11206     */
11207    protected CmsLockManager getLockManager() {
11208
11209        return m_lockManager;
11210    }
11211
11212    /**
11213     * Adds a numeric suffix to the end of a string, unless the number passed as a parameter is 0.<p>
11214     *
11215     * @param name the base name
11216     * @param number the number from which to form the suffix
11217     *
11218     * @return the concatenation of the base name and possibly the numeric suffix
11219     */
11220    protected String getNumberedName(String name, int number) {
11221
11222        if (number == 0) {
11223            return name;
11224        }
11225        PrintfFormat fmt = new PrintfFormat("%0.6d");
11226        return name + "_" + fmt.sprintf(number);
11227    }
11228
11229    /**
11230     * Resets the resources in a project to their online state.<p>
11231     *
11232     * @param dbc the database context
11233     * @param projectId the project id
11234     * @param modifiedFiles the modified files
11235     * @param modifiedFolders the modified folders
11236     * @throws CmsException if something goes wrong
11237     * @throws CmsSecurityException if we don't have the permissions
11238     * @throws CmsDataAccessException if something goes wrong with the database
11239     */
11240    protected void resetResourcesInProject(
11241        CmsDbContext dbc,
11242        CmsUUID projectId,
11243        List<CmsResource> modifiedFiles,
11244        List<CmsResource> modifiedFolders)
11245    throws CmsException, CmsSecurityException, CmsDataAccessException {
11246
11247        // all resources inside the project have to be be reset to their online state.
11248        // 1. step: delete all new files
11249        for (int i = 0; i < modifiedFiles.size(); i++) {
11250            CmsResource currentFile = modifiedFiles.get(i);
11251            if (currentFile.getState().isNew()) {
11252                CmsLock lock = getLock(dbc, currentFile);
11253                if (lock.isNullLock()) {
11254                    // lock the resource
11255                    lockResource(dbc, currentFile, CmsLockType.EXCLUSIVE);
11256                } else if (!lock.isOwnedBy(dbc.currentUser()) || !lock.isInProject(dbc.currentProject())) {
11257                    changeLock(dbc, currentFile, CmsLockType.EXCLUSIVE);
11258                }
11259                // delete the properties
11260                getVfsDriver(dbc).deletePropertyObjects(
11261                    dbc,
11262                    projectId,
11263                    currentFile,
11264                    CmsProperty.DELETE_OPTION_DELETE_STRUCTURE_AND_RESOURCE_VALUES);
11265                // delete the file
11266                getVfsDriver(dbc).removeFile(dbc, dbc.currentProject().getUuid(), currentFile);
11267                // remove the access control entries
11268                getUserDriver(dbc).removeAccessControlEntries(dbc, dbc.currentProject(), currentFile.getResourceId());
11269                // fire the corresponding event
11270                OpenCms.fireCmsEvent(
11271                    new CmsEvent(
11272                        I_CmsEventListener.EVENT_RESOURCE_AND_PROPERTIES_MODIFIED,
11273                        Collections.<String, Object> singletonMap(I_CmsEventListener.KEY_RESOURCE, currentFile)));
11274            }
11275        }
11276
11277        // 2. step: delete all new folders
11278        for (int i = 0; i < modifiedFolders.size(); i++) {
11279            CmsResource currentFolder = modifiedFolders.get(i);
11280            if (currentFolder.getState().isNew()) {
11281                // delete the properties
11282                getVfsDriver(dbc).deletePropertyObjects(
11283                    dbc,
11284                    projectId,
11285                    currentFolder,
11286                    CmsProperty.DELETE_OPTION_DELETE_STRUCTURE_AND_RESOURCE_VALUES);
11287                // delete the folder
11288                getVfsDriver(dbc).removeFolder(dbc, dbc.currentProject(), currentFolder);
11289                // remove the access control entries
11290                getUserDriver(dbc).removeAccessControlEntries(dbc, dbc.currentProject(), currentFolder.getResourceId());
11291                // fire the corresponding event
11292                OpenCms.fireCmsEvent(
11293                    new CmsEvent(
11294                        I_CmsEventListener.EVENT_RESOURCE_AND_PROPERTIES_MODIFIED,
11295                        Collections.<String, Object> singletonMap(I_CmsEventListener.KEY_RESOURCE, currentFolder)));
11296            }
11297        }
11298
11299        // 3. step: undo changes on all changed or deleted folders
11300        for (int i = 0; i < modifiedFolders.size(); i++) {
11301            CmsResource currentFolder = modifiedFolders.get(i);
11302            if ((currentFolder.getState().isChanged()) || (currentFolder.getState().isDeleted())) {
11303                CmsLock lock = getLock(dbc, currentFolder);
11304                if (lock.isNullLock()) {
11305                    // lock the resource
11306                    lockResource(dbc, currentFolder, CmsLockType.EXCLUSIVE);
11307                } else if (!lock.isOwnedBy(dbc.currentUser()) || !lock.isInProject(dbc.currentProject())) {
11308                    changeLock(dbc, currentFolder, CmsLockType.EXCLUSIVE);
11309                }
11310                // undo all changes in the folder
11311                undoChanges(dbc, currentFolder, CmsResource.UNDO_CONTENT);
11312                // fire the corresponding event
11313                OpenCms.fireCmsEvent(
11314                    new CmsEvent(
11315                        I_CmsEventListener.EVENT_RESOURCE_AND_PROPERTIES_MODIFIED,
11316                        Collections.<String, Object> singletonMap(I_CmsEventListener.KEY_RESOURCE, currentFolder)));
11317            }
11318        }
11319
11320        // 4. step: undo changes on all changed or deleted files
11321        for (int i = 0; i < modifiedFiles.size(); i++) {
11322            CmsResource currentFile = modifiedFiles.get(i);
11323            if (currentFile.getState().isChanged() || currentFile.getState().isDeleted()) {
11324                CmsLock lock = getLock(dbc, currentFile);
11325                if (lock.isNullLock()) {
11326                    // lock the resource
11327                    lockResource(dbc, currentFile, CmsLockType.EXCLUSIVE);
11328                } else if (!lock.isOwnedInProjectBy(dbc.currentUser(), dbc.currentProject())) {
11329                    if (lock.isLockableBy(dbc.currentUser())) {
11330                        changeLock(dbc, currentFile, CmsLockType.EXCLUSIVE);
11331                    }
11332                }
11333                // undo all changes in the file
11334                undoChanges(dbc, currentFile, CmsResource.UNDO_CONTENT);
11335                // fire the corresponding event
11336                OpenCms.fireCmsEvent(
11337                    new CmsEvent(
11338                        I_CmsEventListener.EVENT_RESOURCE_AND_PROPERTIES_MODIFIED,
11339                        Collections.<String, Object> singletonMap(I_CmsEventListener.KEY_RESOURCE, currentFile)));
11340            }
11341        }
11342    }
11343
11344    /**
11345     * Counts the total number of users which fit the given criteria.<p>
11346     *
11347     * @param dbc the database context
11348     * @param searchParams the user search criteria
11349     *
11350     * @return the total number of users matching the criteria
11351     *
11352     * @throws CmsDataAccessException if something goes wrong
11353     */
11354    long countUsers(CmsDbContext dbc, CmsUserSearchParameters searchParams) throws CmsDataAccessException {
11355
11356        return getUserDriver(dbc).countUsers(dbc, searchParams);
11357    }
11358
11359    /**
11360     * Adds a pool to the static pool map.<p>
11361     *
11362     * @param pool the pool to add
11363     */
11364    private void addPool(CmsDbPoolV11 pool) {
11365
11366        m_pools.put(pool.getPoolUrl(), pool);
11367    }
11368
11369    /**
11370     * Adds all sub-resources of the given resource to the publish list.<p>
11371     *
11372     * @param dbc the database context
11373     * @param publishList the publish list
11374     * @param directPublishResource the resource to get the sub-resources for
11375     * @param additionalFilter an additional test for resources to pass before they are added to the publish list
11376     *
11377     * @throws CmsDataAccessException if something goes wrong accessing the database
11378     */
11379    private void addSubResources(
11380        CmsDbContext dbc,
11381        CmsPublishList publishList,
11382        CmsResource directPublishResource,
11383        Predicate<CmsResource> additionalFilter)
11384    throws CmsDataAccessException {
11385
11386        int flags = CmsDriverManager.READMODE_INCLUDE_TREE | CmsDriverManager.READMODE_EXCLUDE_STATE;
11387        if (!directPublishResource.getState().isDeleted()) {
11388            // fix for org.opencms.file.TestPublishIssues#testPublishFolderWithDeletedFileFromOtherProject
11389            flags = flags | CmsDriverManager.READMODE_INCLUDE_PROJECT;
11390        }
11391
11392        // add all sub resources of the folder
11393        List<CmsResource> folderList = getVfsDriver(dbc).readResourceTree(
11394            dbc,
11395            dbc.currentProject().getUuid(),
11396            directPublishResource.getRootPath(),
11397            CmsDriverManager.READ_IGNORE_TYPE,
11398            CmsResource.STATE_UNCHANGED,
11399            CmsDriverManager.READ_IGNORE_TIME,
11400            CmsDriverManager.READ_IGNORE_TIME,
11401            CmsDriverManager.READ_IGNORE_TIME,
11402            CmsDriverManager.READ_IGNORE_TIME,
11403            CmsDriverManager.READ_IGNORE_TIME,
11404            CmsDriverManager.READ_IGNORE_TIME,
11405            flags | CmsDriverManager.READMODE_ONLY_FOLDERS);
11406
11407        publishList.addAll(
11408            filterResources(dbc, publishList, folderList).stream().filter(additionalFilter).collect(
11409                Collectors.toList()),
11410            true);
11411
11412        List<CmsResource> fileList = getVfsDriver(dbc).readResourceTree(
11413            dbc,
11414            dbc.currentProject().getUuid(),
11415            directPublishResource.getRootPath(),
11416            CmsDriverManager.READ_IGNORE_TYPE,
11417            CmsResource.STATE_UNCHANGED,
11418            CmsDriverManager.READ_IGNORE_TIME,
11419            CmsDriverManager.READ_IGNORE_TIME,
11420            CmsDriverManager.READ_IGNORE_TIME,
11421            CmsDriverManager.READ_IGNORE_TIME,
11422            CmsDriverManager.READ_IGNORE_TIME,
11423            CmsDriverManager.READ_IGNORE_TIME,
11424            flags | CmsDriverManager.READMODE_ONLY_FILES);
11425
11426        publishList.addAll(
11427            filterResources(dbc, publishList, fileList).stream().filter(additionalFilter).collect(Collectors.toList()),
11428            true);
11429    }
11430
11431    /**
11432     * Helper method to check whether we should bother with reading the group for a given role in a given OU.<p>
11433     *
11434     * 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.
11435     *
11436     * @param ou the OU
11437     * @param role the role
11438     * @return true if we should read the role in the OU
11439     */
11440    private boolean canReadRoleInOu(CmsOrganizationalUnit ou, CmsRole role) {
11441
11442        if (ou.hasFlagWebuser() && !role.getRoleName().equals(CmsRole.ACCOUNT_MANAGER.getRoleName())) {
11443            return false;
11444        }
11445        return true;
11446    }
11447
11448    /**
11449     * Checks the parent of a resource during publishing.<p>
11450     *
11451     * @param dbc the current database context
11452     * @param deletedFolders a list of deleted folders
11453     * @param res a resource to check the parent for
11454     *
11455     * @return <code>true</code> if the parent resource will be deleted during publishing
11456     */
11457    private boolean checkDeletedParentFolder(CmsDbContext dbc, List<CmsResource> deletedFolders, CmsResource res) {
11458
11459        String parentPath = CmsResource.getParentFolder(res.getRootPath());
11460
11461        if (parentPath == null) {
11462            // resource has no parent
11463            return false;
11464        }
11465
11466        CmsResource parent;
11467        try {
11468            parent = readResource(dbc, parentPath, CmsResourceFilter.ALL);
11469        } catch (Exception e) {
11470            // failure: if we cannot read the parent, we should not publish the resource
11471            return false;
11472        }
11473
11474        if (!parent.getState().isDeleted()) {
11475            // parent is not deleted
11476            return false;
11477        }
11478
11479        for (int j = 0; j < deletedFolders.size(); j++) {
11480            if ((deletedFolders.get(j)).getStructureId().equals(parent.getStructureId())) {
11481                // parent is deleted, and it will get published
11482                return true;
11483            }
11484        }
11485
11486        // parent is new, but it will not get published
11487        return false;
11488    }
11489
11490    /**
11491     * Checks that the given resource can be read as a file.<p>
11492     *
11493     * @param dbc the current database context
11494     * @param resource the resource to check
11495     * @throws CmsVfsResourceNotFoundException if the resource is a folder
11496     */
11497    private void checkFileResource(CmsDbContext dbc, CmsResource resource) throws CmsVfsResourceNotFoundException {
11498
11499        if (resource.isFolder()) {
11500            throw new CmsVfsResourceNotFoundException(
11501                Messages.get().container(
11502                    Messages.ERR_ACCESS_FOLDER_AS_FILE_1,
11503                    dbc.removeSiteRoot(resource.getRootPath())));
11504        }
11505    }
11506
11507    /**
11508     * Checks that no one of the resources to be published has a 'new' parent (that has not been published yet).<p>
11509     *
11510     * @param dbc the db context
11511     * @param publishList the publish list to check
11512     *
11513     * @throws CmsVfsException if there is a resource to be published with a 'new' parent
11514     */
11515    private void checkParentFolders(CmsDbContext dbc, CmsPublishList publishList) throws CmsVfsException {
11516
11517        boolean directPublish = publishList.isDirectPublish();
11518        // if we direct publish a file, check if all parent folders are already published
11519        if (directPublish) {
11520            // first get the names of all parent folders
11521            Iterator<CmsResource> it = publishList.getDirectPublishResources().iterator();
11522            List<String> parentFolderNames = new ArrayList<String>();
11523            while (it.hasNext()) {
11524                CmsResource res = it.next();
11525                String parentFolderName = CmsResource.getParentFolder(res.getRootPath());
11526                if (parentFolderName != null) {
11527                    parentFolderNames.add(parentFolderName);
11528                }
11529            }
11530            // remove duplicate parent folder names
11531            parentFolderNames = CmsFileUtil.removeRedundancies(parentFolderNames);
11532            String parentFolderName = null;
11533            try {
11534                I_CmsVfsDriver vfsDriver = getVfsDriver(dbc);
11535                // now check all folders if they exist in the online project
11536                Iterator<String> parentIt = parentFolderNames.iterator();
11537                while (parentIt.hasNext()) {
11538                    parentFolderName = parentIt.next();
11539                    vfsDriver.readFolder(dbc, CmsProject.ONLINE_PROJECT_ID, parentFolderName);
11540                }
11541            } catch (CmsException e) {
11542                throw new CmsVfsException(
11543                    Messages.get().container(Messages.RPT_PARENT_FOLDER_NOT_PUBLISHED_1, parentFolderName));
11544            }
11545        }
11546    }
11547
11548    /**
11549     * Checks the parent of a resource during publishing.<p>
11550     *
11551     * @param dbc the current database context
11552     * @param folderList a list of folders
11553     * @param res a resource to check the parent for
11554     *
11555     * @return true if the resource should be published
11556     */
11557    private boolean checkParentResource(CmsDbContext dbc, List<CmsResource> folderList, CmsResource res) {
11558
11559        String parentPath = CmsResource.getParentFolder(res.getRootPath());
11560
11561        if (parentPath == null) {
11562            // resource has no parent
11563            return true;
11564        }
11565
11566        CmsResource parent;
11567        try {
11568            parent = readResource(dbc, parentPath, CmsResourceFilter.ALL);
11569        } catch (Exception e) {
11570            // failure: if we cannot read the parent, we should not publish the resource
11571            return false;
11572        }
11573
11574        if (!parent.getState().isNew()) {
11575            // parent is already published
11576            return true;
11577        }
11578
11579        for (int j = 0; j < folderList.size(); j++) {
11580            if (folderList.get(j).getStructureId().equals(parent.getStructureId())) {
11581                // parent is new, but it will get published
11582                return true;
11583            }
11584        }
11585
11586        // parent is new, but it will not get published
11587        return false;
11588    }
11589
11590    /**
11591     * Copies all relations from the source resource to the target resource.<p>
11592     *
11593     * @param dbc the database context
11594     * @param source the source
11595     * @param target the target
11596     *
11597     * @throws CmsException if something goes wrong
11598     */
11599    private void copyRelations(CmsDbContext dbc, CmsResource source, CmsResource target) throws CmsException {
11600
11601        // copy relations all relations
11602        CmsObject cms = new CmsObject(getSecurityManager(), dbc.getRequestContext());
11603        Iterator<CmsRelation> itRelations = getRelationsForResource(
11604            dbc,
11605            source,
11606            CmsRelationFilter.TARGETS.filterNotDefinedInContent()).iterator();
11607        while (itRelations.hasNext()) {
11608            CmsRelation relation = itRelations.next();
11609            if (relation.getType().getCopyBehavior() == CopyBehavior.copy) {
11610                try {
11611                    CmsResource relTarget = relation.getTarget(cms, CmsResourceFilter.ALL);
11612                    addRelationToResource(dbc, target, relTarget, relation.getType(), true);
11613                } catch (CmsVfsResourceNotFoundException e) {
11614                    // ignore this broken relation
11615                    if (LOG.isWarnEnabled()) {
11616                        LOG.warn(e.getLocalizedMessage(), e);
11617                    }
11618                }
11619            }
11620        }
11621        // repair categories
11622        repairCategories(dbc, getProjectIdForContext(dbc), target);
11623    }
11624
11625    /**
11626     * Filters the given list of resources, removes all resources where the current user
11627     * does not have READ permissions, plus the filter is applied.<p>
11628     *
11629     * @param dbc the current database context
11630     * @param resourceList a list of CmsResources
11631     * @param filter the resource filter to use
11632     *
11633     * @return the filtered list of resources
11634     *
11635     * @throws CmsException in case errors testing the permissions
11636     */
11637    private List<CmsResource> filterPermissions(
11638        CmsDbContext dbc,
11639        List<CmsResource> resourceList,
11640        CmsResourceFilter filter)
11641    throws CmsException {
11642
11643        if (filter.requireTimerange()) {
11644            // never check time range here - this must be done later in #updateContextDates(...)
11645            filter = filter.addExcludeTimerange();
11646        }
11647        ResourceListWithCacheability result = new ResourceListWithCacheability();
11648        boolean nocacheWasSet = false;
11649        if (null == dbc.getAttribute(ATTR_PERMISSION_NOCACHE)) {
11650            // The attribute will be used by the permission handler to tell us that lists containing the resource
11651            // should not be cached.
11652            dbc.setAttribute(ATTR_PERMISSION_NOCACHE, new boolean[] {false});
11653            // insurance against potential indirect recursive calls introduced by future code changes:
11654            // make sure we only remove the attribute later if we were the one who set it
11655            nocacheWasSet = true;
11656        }
11657        try {
11658            for (int i = 0; i < resourceList.size(); i++) {
11659                // check the permission of all resources
11660                CmsResource currentResource = resourceList.get(i);
11661                if (m_securityManager.hasPermissions(
11662                    dbc,
11663                    currentResource,
11664                    CmsPermissionSet.ACCESS_READ,
11665                    LockCheck.yes,
11666                    filter).isAllowed()) {
11667                    // only return resources where permission was granted
11668                    result.add(currentResource);
11669                }
11670            }
11671        } finally {
11672            if (nocacheWasSet) {
11673                boolean[] nocache = (boolean[])dbc.getAttribute(ATTR_PERMISSION_NOCACHE);
11674                if (nocache != null) {
11675                    dbc.removeAttribute(ATTR_PERMISSION_NOCACHE);
11676                    if (nocache[0]) {
11677                        result.setCacheable(false);
11678                    }
11679                }
11680            }
11681        }
11682        // return the result
11683        return result;
11684    }
11685
11686    /**
11687     * Returns a filtered list of resources for publishing.<p>
11688     * Contains all resources, which are not locked
11689     * and which have a parent folder that is already published or will be published, too.<p>
11690     *
11691     * @param dbc the current database context
11692     * @param publishList the filling publish list
11693     * @param resourceList the list of resources to filter
11694     *
11695     * @return a filtered list of resources
11696     */
11697    private List<CmsResource> filterResources(
11698        CmsDbContext dbc,
11699        CmsPublishList publishList,
11700        List<CmsResource> resourceList) {
11701
11702        List<CmsResource> result = new ArrayList<CmsResource>();
11703
11704        // local folder list for adding new publishing subfolders
11705        // this solves the {@link org.opencms.file.TestPublishIssues#testPublishScenarioD} problem.
11706        List<CmsResource> newFolderList = new ArrayList<CmsResource>(
11707            publishList == null ? resourceList : publishList.getFolderList());
11708
11709        for (int i = 0; i < resourceList.size(); i++) {
11710            CmsResource res = resourceList.get(i);
11711            try {
11712                CmsLock lock = getLock(dbc, res);
11713                if (lock.isPublish()) {
11714                    // if already enqueued
11715                    continue;
11716                }
11717                if (!lock.isLockableBy(dbc.currentUser())) {
11718                    // checks if there is a shared lock and if the resource is deleted
11719                    // this solves the {@link org.opencms.file.TestPublishIssues#testPublishScenarioE} problem.
11720                    if (lock.isShared() && (publishList != null)) {
11721                        if (!res.getState().isDeleted()
11722                            || !checkDeletedParentFolder(dbc, publishList.getDeletedFolderList(), res)) {
11723                            continue;
11724                        }
11725                    } else {
11726                        // don't add locked resources
11727                        continue;
11728                    }
11729                }
11730                if (!"/".equals(res.getRootPath()) && !checkParentResource(dbc, newFolderList, res)) {
11731                    continue;
11732                }
11733                // check permissions
11734                try {
11735                    m_securityManager.checkPermissions(
11736                        dbc,
11737                        res,
11738                        CmsPermissionSet.ACCESS_DIRECT_PUBLISH,
11739                        false,
11740                        CmsResourceFilter.ALL);
11741                } catch (CmsException e) {
11742                    // skip if not enough permissions
11743                    continue;
11744                }
11745                if (res.isFolder()) {
11746                    newFolderList.add(res);
11747                }
11748                result.add(res);
11749            } catch (Exception e) {
11750                // should never happen
11751                LOG.error(e.getLocalizedMessage(), e);
11752            }
11753        }
11754        return result;
11755    }
11756
11757    /**
11758     * Returns a filtered list of sibling resources for publishing.<p>
11759     *
11760     * Contains all siblings of the given resources, which are not locked
11761     * and which have a parent folder that is already published or will be published, too.<p>
11762     *
11763     * @param dbc the current database context
11764     * @param publishList the unfinished publish list
11765     * @param resourceList the list of siblings to filter
11766     *
11767     * @return a filtered list of sibling resources for publishing
11768     */
11769    private List<CmsResource> filterSiblings(
11770        CmsDbContext dbc,
11771        CmsPublishList publishList,
11772        Collection<CmsResource> resourceList) {
11773
11774        List<CmsResource> result = new ArrayList<CmsResource>();
11775
11776        // removed internal extendible folder list, since iterated (sibling) resources are files in any case, never folders
11777
11778        for (CmsResource res : resourceList) {
11779            try {
11780                CmsLock lock = getLock(dbc, res);
11781                if (lock.isPublish()) {
11782                    // if already enqueued
11783                    continue;
11784                }
11785                if (!lock.isLockableBy(dbc.currentUser())) {
11786                    // checks if there is a shared lock and if the resource is deleted
11787                    // this solves the {@link org.opencms.file.TestPublishIssues#testPublishScenarioE} problem.
11788                    if (lock.isShared() && (publishList != null)) {
11789                        if (!res.getState().isDeleted()
11790                            || !checkDeletedParentFolder(dbc, publishList.getDeletedFolderList(), res)) {
11791                            continue;
11792                        }
11793                    } else {
11794                        // don't add locked resources
11795                        continue;
11796                    }
11797                }
11798                if (!"/".equals(res.getRootPath()) && !checkParentResource(dbc, publishList.getFolderList(), res)) {
11799                    // don't add resources that have no parent in the online project
11800                    continue;
11801                }
11802                // check permissions
11803                try {
11804                    m_securityManager.checkPermissions(
11805                        dbc,
11806                        res,
11807                        CmsPermissionSet.ACCESS_DIRECT_PUBLISH,
11808                        false,
11809                        CmsResourceFilter.ALL);
11810                } catch (CmsException e) {
11811                    // skip if not enough permissions
11812                    continue;
11813                }
11814                result.add(res);
11815            } catch (Exception e) {
11816                // should never happen
11817                LOG.error(e.getLocalizedMessage(), e);
11818            }
11819        }
11820        return result;
11821    }
11822
11823    /**
11824     * Returns the access control list of a given resource.<p>
11825     *
11826     * @param dbc the current database context
11827     * @param resource the resource
11828     * @param forFolder should be true if resource is a folder
11829     * @param depth the depth to include non-inherited access entries, also
11830     * @param inheritedOnly flag indicates to collect inherited permissions only
11831     *
11832     * @return the access control list of the resource
11833     *
11834     * @throws CmsException if something goes wrong
11835     */
11836    private CmsAccessControlList getAccessControlList(
11837        CmsDbContext dbc,
11838        CmsResource resource,
11839        boolean inheritedOnly,
11840        boolean forFolder,
11841        int depth)
11842    throws CmsException {
11843
11844        String cacheKey = getCacheKey(
11845            new String[] {
11846                inheritedOnly ? "+" : "-",
11847                forFolder ? "+" : "-",
11848                Integer.toString(depth),
11849                resource.getStructureId().toString()},
11850            dbc);
11851
11852        CmsAccessControlList acl = m_monitor.getCachedACL(cacheKey);
11853
11854        // return the cached acl if already available
11855        if ((acl != null) && dbc.getProjectId().isNullUUID()) {
11856            return acl;
11857        }
11858
11859        List<CmsAccessControlEntry> aces = getUserDriver(dbc).readAccessControlEntries(
11860            dbc,
11861            dbc.currentProject(),
11862            resource.getResourceId(),
11863            (depth > 1) || ((depth > 0) && forFolder));
11864
11865        // sort the list of aces
11866        boolean overwriteAll = sortAceList(aces);
11867
11868        // if no 'overwrite all' ace was found
11869        if (!overwriteAll) {
11870            // get the acl of the parent
11871            CmsResource parentResource = null;
11872            try {
11873                // try to recurse over the id
11874                parentResource = getVfsDriver(dbc).readParentFolder(
11875                    dbc,
11876                    dbc.currentProject().getUuid(),
11877                    resource.getStructureId());
11878            } catch (CmsVfsResourceNotFoundException e) {
11879                // should never happen, but try with the path
11880                String parentPath = CmsResource.getParentFolder(resource.getRootPath());
11881                if (parentPath != null) {
11882                    parentResource = getVfsDriver(dbc).readFolder(dbc, dbc.currentProject().getUuid(), parentPath);
11883                }
11884            }
11885            if (parentResource != null) {
11886                acl = (CmsAccessControlList)getAccessControlList(
11887                    dbc,
11888                    parentResource,
11889                    inheritedOnly,
11890                    forFolder,
11891                    depth + 1).clone();
11892            }
11893        }
11894        if (acl == null) {
11895            acl = new CmsAccessControlList();
11896        }
11897
11898        Set<CmsUUID> exclusiveAccessPrincipals = new HashSet<>();
11899        if (!((depth == 0) && inheritedOnly)) {
11900            Iterator<CmsAccessControlEntry> itAces = aces.iterator();
11901            while (itAces.hasNext()) {
11902                CmsAccessControlEntry acEntry = itAces.next();
11903                if (depth > 0) {
11904                    acEntry.setFlags(CmsAccessControlEntry.ACCESS_FLAGS_INHERITED);
11905                }
11906                if ((depth == 0)
11907                    && resource.isFile()
11908                    && (0 != (acEntry.getFlags() & CmsAccessControlEntry.ACCESS_FLAGS_RESPONSIBLE))) {
11909
11910                    // 'responsible' flag is only interpreted as exclusive access if it's not inherited and set directly on a file
11911                    exclusiveAccessPrincipals.add(acEntry.getPrincipal());
11912                }
11913
11914                acl.add(acEntry);
11915
11916                // if the overwrite flag is set, reset the allowed permissions to the permissions of this entry
11917                // denied permissions are kept or extended
11918                if ((acEntry.getFlags() & CmsAccessControlEntry.ACCESS_FLAGS_OVERWRITE) > 0) {
11919                    acl.setAllowedPermissions(acEntry);
11920                }
11921            }
11922        }
11923        if (exclusiveAccessPrincipals.size() > 0) {
11924            acl.setExclusiveAccessPrincipals(exclusiveAccessPrincipals);
11925        }
11926
11927        if (dbc.getProjectId().isNullUUID()) {
11928            m_monitor.cacheACL(cacheKey, acl);
11929        }
11930        return acl;
11931    }
11932
11933    /**
11934     * Return a cache key build from the provided information.<p>
11935     *
11936     * @param prefix a prefix for the key
11937     * @param flag a boolean flag for the key (only used if prefix is not null)
11938     * @param projectId the project for which to generate the key
11939     * @param resource the resource for which to generate the key
11940     *
11941     * @return String a cache key build from the provided information
11942     */
11943    private String getCacheKey(String prefix, boolean flag, CmsUUID projectId, String resource) {
11944
11945        StringBuffer b = new StringBuffer(64);
11946        if (prefix != null) {
11947            b.append(prefix);
11948            b.append(flag ? '+' : '-');
11949        }
11950        b.append(CmsProject.isOnlineProject(projectId) ? '+' : '-');
11951        return b.append(resource).toString();
11952    }
11953
11954    /**
11955     * Return a cache key build from the provided information.<p>
11956     *
11957     * @param keys an array of keys to generate the cache key from
11958     * @param dbc the database context for which to generate the key
11959     *
11960     * @return String a cache key build from the provided information
11961     */
11962    private String getCacheKey(String[] keys, CmsDbContext dbc) {
11963
11964        if (!dbc.getProjectId().isNullUUID()) {
11965            return "";
11966        }
11967        StringBuffer b = new StringBuffer(64);
11968        int len = keys.length;
11969        if (len > 0) {
11970            for (int i = 0; i < len; i++) {
11971                b.append(keys[i]);
11972                b.append('_');
11973            }
11974        }
11975        if (dbc.currentProject().isOnlineProject()) {
11976            b.append("+");
11977        } else {
11978            b.append("-");
11979        }
11980        return b.toString();
11981    }
11982
11983    /**
11984     * Gets the correct driver interface to use for proxying a specific driver instance.<p>
11985     *
11986     * @param obj the driver instance
11987     * @return the interface to use for proxying
11988     */
11989    private Class<?> getDriverInterfaceForProxy(Object obj) {
11990
11991        for (Class<?> interfaceClass : new Class[] {
11992            I_CmsUserDriver.class,
11993            I_CmsVfsDriver.class,
11994            I_CmsProjectDriver.class,
11995            I_CmsHistoryDriver.class,
11996            I_CmsSubscriptionDriver.class}) {
11997            if (interfaceClass.isAssignableFrom(obj.getClass())) {
11998                return interfaceClass;
11999            }
12000        }
12001        return null;
12002    }
12003
12004    /**
12005     * Returns the correct project id.<p>
12006     *
12007     * @param dbc the database context
12008     *
12009     * @return the correct project id
12010     */
12011    private CmsUUID getProjectIdForContext(CmsDbContext dbc) {
12012
12013        CmsUUID projectId = dbc.getProjectId();
12014        if (projectId.isNullUUID()) {
12015            projectId = dbc.currentProject().getUuid();
12016        }
12017        return projectId;
12018    }
12019
12020    /**
12021     * Returns if and what state needs to be updated.<p>
12022     *
12023     * @param dbc the db context
12024     * @param resource the resource
12025     * @param properties the properties to check
12026     *
12027     * @return 0: none, 1: structure, 2: resource
12028     *
12029     * @throws CmsDataAccessException if something goes wrong
12030     */
12031    private int getUpdateState(CmsDbContext dbc, CmsResource resource, List<CmsProperty> properties)
12032    throws CmsDataAccessException {
12033
12034        int updateState = 0;
12035        I_CmsVfsDriver vfsDriver = getVfsDriver(dbc);
12036        Iterator<CmsProperty> it = properties.iterator();
12037        while (it.hasNext() && (updateState < 2)) {
12038            CmsProperty property = it.next();
12039
12040            // read existing property
12041            CmsProperty existingProperty = vfsDriver.readPropertyObject(
12042                dbc,
12043                property.getName(),
12044                dbc.currentProject(),
12045                resource);
12046
12047            // check the shared property
12048            if (property.getResourceValue() != null) {
12049                if (property.isDeleteResourceValue()) {
12050                    if (existingProperty.getResourceValue() != null) {
12051                        updateState = 2; // deleted
12052                    }
12053                } else {
12054                    if (existingProperty.getResourceValue() == null) {
12055                        updateState = 2; // created
12056                    } else {
12057                        if (!property.getResourceValue().equals(existingProperty.getResourceValue())) {
12058                            updateState = 2; // updated
12059                        }
12060                    }
12061                }
12062            }
12063            if (updateState == 0) {
12064                // check the individual property only if needed
12065                if (property.getStructureValue() != null) {
12066                    if (property.isDeleteStructureValue()) {
12067                        if (existingProperty.getStructureValue() != null) {
12068                            updateState = 1; // deleted
12069                        }
12070                    } else {
12071                        if (existingProperty.getStructureValue() == null) {
12072                            updateState = 1; // created
12073                        } else {
12074                            if (!property.getStructureValue().equals(existingProperty.getStructureValue())) {
12075                                updateState = 1; // updated
12076                            }
12077                        }
12078                    }
12079                }
12080            }
12081        }
12082        return updateState;
12083    }
12084
12085    /**
12086     * Returns all groups that are virtualizing the given role in the given ou.<p>
12087     *
12088     * @param dbc the database context
12089     * @param role the role
12090     *
12091     * @return all groups that are virtualizing the given role (or a child of it)
12092     *
12093     * @throws CmsException if something goes wrong
12094     */
12095    private List<CmsGroup> getVirtualGroupsForRole(CmsDbContext dbc, CmsRole role) throws CmsException {
12096
12097        Set<Integer> roleFlags = new HashSet<Integer>();
12098        // add role flag
12099        Integer flags = Integer.valueOf(role.getVirtualGroupFlags());
12100        roleFlags.add(flags);
12101        // collect all child role flags
12102        Iterator<CmsRole> itChildRoles = role.getChildren(true).iterator();
12103        while (itChildRoles.hasNext()) {
12104            CmsRole child = itChildRoles.next();
12105            flags = Integer.valueOf(child.getVirtualGroupFlags());
12106            roleFlags.add(flags);
12107        }
12108        // iterate all groups matching the flags
12109        List<CmsGroup> groups = new ArrayList<CmsGroup>();
12110        Iterator<CmsGroup> it = getGroups(dbc, readOrganizationalUnit(dbc, role.getOuFqn()), false, false).iterator();
12111        while (it.hasNext()) {
12112            CmsGroup group = it.next();
12113            if (group.isVirtual()) {
12114                CmsRole r = CmsRole.valueOf(group);
12115                if (roleFlags.contains(Integer.valueOf(r.getVirtualGroupFlags()))) {
12116                    groups.add(group);
12117                }
12118            }
12119        }
12120        return groups;
12121    }
12122
12123    /**
12124     * Returns a list of users in a group.<p>
12125     *
12126     * @param dbc the current database context
12127     * @param ouFqn the organizational unit to get the users from
12128     * @param groupname the name of the group to list users from
12129     * @param includeOtherOuUsers include users of other organizational units
12130     * @param directUsersOnly if set only the direct assigned users will be returned,
12131     *                        if not also indirect users, ie. members of parent roles,
12132     *                        this parameter only works with roles
12133     * @param readRoles if to read roles or groups
12134     *
12135     * @return all <code>{@link CmsUser}</code> objects in the group
12136     *
12137     * @throws CmsException if operation was not successful
12138     */
12139    private List<CmsUser> internalUsersOfGroup(
12140        CmsDbContext dbc,
12141        String ouFqn,
12142        String groupname,
12143        boolean includeOtherOuUsers,
12144        boolean directUsersOnly,
12145        boolean readRoles)
12146    throws CmsException {
12147
12148        CmsGroup group = readGroup(dbc, groupname); // check that the group really exists
12149        if ((group == null) || (!((!readRoles && !group.isRole()) || (readRoles && group.isRole())))) {
12150            throw new CmsDbEntryNotFoundException(Messages.get().container(Messages.ERR_UNKNOWN_GROUP_1, groupname));
12151        }
12152
12153        String prefix = "_" + includeOtherOuUsers + "_" + directUsersOnly + "_" + ouFqn;
12154        String cacheKey = m_keyGenerator.getCacheKeyForGroupUsers(prefix, dbc, group);
12155        List<CmsUser> allUsers = m_monitor.getCachedUserList(cacheKey);
12156        if (allUsers == null) {
12157            Set<CmsUser> users = new HashSet<CmsUser>(
12158                getUserDriver(dbc).readUsersOfGroup(dbc, groupname, includeOtherOuUsers));
12159            if (readRoles && !directUsersOnly) {
12160                CmsRole role = CmsRole.valueOf(group);
12161                if (role.getParentRole() != null) {
12162                    try {
12163                        String parentGroup = role.getParentRole().getGroupName();
12164                        readGroup(dbc, parentGroup);
12165                        // iterate the parent roles
12166                        users.addAll(
12167                            internalUsersOfGroup(
12168                                dbc,
12169                                ouFqn,
12170                                parentGroup,
12171                                includeOtherOuUsers,
12172                                directUsersOnly,
12173                                readRoles));
12174                    } catch (CmsDbEntryNotFoundException e) {
12175                        // ignore, this may happen while deleting an orgunit
12176                        if (LOG.isDebugEnabled()) {
12177                            LOG.debug(e.getLocalizedMessage(), e);
12178                        }
12179                    }
12180                }
12181                String parentOu = CmsOrganizationalUnit.getParentFqn(group.getOuFqn());
12182                if (parentOu != null) {
12183                    // iterate the parent ou's
12184                    users.addAll(
12185                        internalUsersOfGroup(
12186                            dbc,
12187                            ouFqn,
12188                            parentOu + group.getSimpleName(),
12189                            includeOtherOuUsers,
12190                            directUsersOnly,
12191                            readRoles));
12192                }
12193            } else if (!readRoles && !directUsersOnly) {
12194                List<CmsGroup> groups = getChildren(dbc, group, false);
12195                for (CmsGroup parentGroup : groups) {
12196                    try {
12197                        // iterate the parent groups
12198                        users.addAll(
12199                            internalUsersOfGroup(
12200                                dbc,
12201                                ouFqn,
12202                                parentGroup.getName(),
12203                                includeOtherOuUsers,
12204                                directUsersOnly,
12205                                readRoles));
12206                    } catch (CmsDbEntryNotFoundException e) {
12207                        // ignore, this may happen while deleting an orgunit
12208                        if (LOG.isDebugEnabled()) {
12209                            LOG.debug(e.getLocalizedMessage(), e);
12210                        }
12211                    }
12212                }
12213            }
12214            // filter users from other ous
12215            if (!includeOtherOuUsers) {
12216                Iterator<CmsUser> itUsers = users.iterator();
12217                while (itUsers.hasNext()) {
12218                    CmsUser user = itUsers.next();
12219                    if (!user.getOuFqn().equals(ouFqn)) {
12220                        itUsers.remove();
12221                    }
12222                }
12223            }
12224
12225            // make user list unmodifiable for caching
12226            allUsers = Collections.unmodifiableList(new ArrayList<CmsUser>(users));
12227            if (dbc.getProjectId().isNullUUID()) {
12228                m_monitor.cacheUserList(cacheKey, allUsers);
12229            }
12230        }
12231        return allUsers;
12232    }
12233
12234    /**
12235     * Reads all resources that are inside and changed in a specified project.<p>
12236     *
12237     * @param dbc the current database context
12238     * @param projectId the ID of the project
12239     * @param mode one of the {@link CmsReadChangedProjectResourceMode} constants
12240     *
12241     * @return a List with all resources inside the specified project
12242     *
12243     * @throws CmsException if something goes wrong
12244     */
12245    private List<CmsResource> readChangedResourcesInsideProject(
12246        CmsDbContext dbc,
12247        CmsUUID projectId,
12248        CmsReadChangedProjectResourceMode mode)
12249    throws CmsException {
12250
12251        String cacheKey = projectId + "_" + mode.toString();
12252        List<CmsResource> result = m_monitor.getCachedProjectResources(cacheKey);
12253        if (result != null) {
12254            return result;
12255        }
12256        List<String> projectResources = readProjectResources(dbc, readProject(dbc, projectId));
12257        result = new ArrayList<CmsResource>();
12258        String currentProjectResource = null;
12259        List<CmsResource> resources = new ArrayList<CmsResource>();
12260        CmsResource currentResource = null;
12261        CmsLock currentLock = null;
12262
12263        for (int i = 0; i < projectResources.size(); i++) {
12264            // read all resources that are inside the project by visiting each project resource
12265            currentProjectResource = projectResources.get(i);
12266
12267            try {
12268                currentResource = readResource(dbc, currentProjectResource, CmsResourceFilter.ALL);
12269
12270                if (currentResource.isFolder()) {
12271                    resources.addAll(readResources(dbc, currentResource, CmsResourceFilter.ALL, true));
12272                } else {
12273                    resources.add(currentResource);
12274                }
12275            } catch (CmsException e) {
12276                // the project resource probably doesn't exist (anymore)...
12277                if (!(e instanceof CmsVfsResourceNotFoundException)) {
12278                    throw e;
12279                }
12280            }
12281        }
12282
12283        for (int j = 0; j < resources.size(); j++) {
12284            currentResource = resources.get(j);
12285            currentLock = getLock(dbc, currentResource).getEditionLock();
12286
12287            if (!currentResource.getState().isUnchanged()) {
12288                if ((currentLock.isNullLock() && (currentResource.getProjectLastModified().equals(projectId)))
12289                    || (currentLock.isOwnedBy(dbc.currentUser()) && (currentLock.getProjectId().equals(projectId)))) {
12290                    // add only resources that are
12291                    // - inside the project,
12292                    // - changed in the project,
12293                    // - either unlocked, or locked for the current user in the project
12294                    if ((mode == RCPRM_FILES_AND_FOLDERS_MODE)
12295                        || (currentResource.isFolder() && (mode == RCPRM_FOLDERS_ONLY_MODE))
12296                        || (currentResource.isFile() && (mode == RCPRM_FILES_ONLY_MODE))) {
12297                        result.add(currentResource);
12298                    }
12299                }
12300            }
12301        }
12302
12303        resources.clear();
12304        resources = null;
12305
12306        m_monitor.cacheProjectResources(cacheKey, result);
12307        return result;
12308    }
12309
12310    /**
12311     * Reads the content for a history resource.<p>
12312     *
12313     * @param dbc the current database context
12314     * @param resource the history resource
12315     * @return the history content
12316     * @throws CmsDataAccessException if the content can not be read
12317     */
12318    private byte[] readHistoryContent(CmsDbContext dbc, CmsResource resource) throws CmsDataAccessException {
12319
12320        return getHistoryDriver(dbc).readContent(
12321            dbc,
12322            resource.getResourceId(),
12323            ((I_CmsHistoryResource)resource).getPublishTag());
12324    }
12325
12326    /**
12327     * Sorts the given list of {@link CmsAccessControlEntry} objects.<p>
12328     *
12329     * The the 'all others' ace in first place, the 'overwrite all' ace in second.<p>
12330     *
12331     * @param aces the list of ACEs to sort
12332     *
12333     * @return <code>true</code> if the list contains the 'overwrite all' ace
12334     */
12335    private boolean sortAceList(List<CmsAccessControlEntry> aces) {
12336
12337        // sort the list of entries
12338        Collections.sort(aces, CmsAccessControlEntry.COMPARATOR_ACE);
12339        // after sorting just the first 2 positions come in question
12340        for (int i = 0; i < Math.min(aces.size(), 2); i++) {
12341            CmsAccessControlEntry acEntry = aces.get(i);
12342            if (acEntry.getPrincipal().equals(CmsAccessControlEntry.PRINCIPAL_OVERWRITE_ALL_ID)) {
12343                return true;
12344            }
12345        }
12346        return false;
12347    }
12348
12349    /**
12350     * All permissions and resources attributes of the principal
12351     * are transfered to a replacement principal.<p>
12352     *
12353     * @param dbc the current database context
12354     * @param project the current project
12355     * @param principalId the id of the principal to be replaced
12356     * @param replacementId the user to be transfered
12357     * @param withACEs flag to signal if the ACEs should also be transfered or just deleted
12358     *
12359     * @throws CmsException if operation was not successful
12360     */
12361    private void transferPrincipalResources(
12362        CmsDbContext dbc,
12363        CmsProject project,
12364        CmsUUID principalId,
12365        CmsUUID replacementId,
12366        boolean withACEs)
12367    throws CmsException {
12368
12369        // get all resources for the given user including resources associated by ACEs or attributes
12370        I_CmsUserDriver userDriver = getUserDriver(dbc);
12371        I_CmsVfsDriver vfsDriver = getVfsDriver(dbc);
12372        Set<CmsResource> resources = getResourcesForPrincipal(dbc, project, principalId, null, true);
12373        Iterator<CmsResource> it = resources.iterator();
12374        while (it.hasNext()) {
12375            CmsResource resource = it.next();
12376            // check resource attributes
12377            boolean attrModified = false;
12378            CmsUUID createdUser = null;
12379            if (resource.getUserCreated().equals(principalId)) {
12380                createdUser = replacementId;
12381                attrModified = true;
12382            }
12383            CmsUUID lastModUser = null;
12384            if (resource.getUserLastModified().equals(principalId)) {
12385                lastModUser = replacementId;
12386                attrModified = true;
12387            }
12388            if (attrModified) {
12389                vfsDriver.transferResource(dbc, project, resource, createdUser, lastModUser);
12390                // clear the cache
12391                m_monitor.clearResourceCache();
12392            }
12393            boolean aceModified = false;
12394            // check aces
12395            if (withACEs) {
12396                Iterator<CmsAccessControlEntry> itAces = userDriver.readAccessControlEntries(
12397                    dbc,
12398                    project,
12399                    resource.getResourceId(),
12400                    false).iterator();
12401                while (itAces.hasNext()) {
12402                    CmsAccessControlEntry ace = itAces.next();
12403                    if (ace.getPrincipal().equals(principalId)) {
12404                        CmsAccessControlEntry newAce = new CmsAccessControlEntry(
12405                            ace.getResource(),
12406                            replacementId,
12407                            ace.getAllowedPermissions(),
12408                            ace.getDeniedPermissions(),
12409                            ace.getFlags());
12410                        // write the new ace
12411                        userDriver.writeAccessControlEntry(dbc, project, newAce);
12412                        aceModified = true;
12413                    }
12414                }
12415                if (aceModified) {
12416                    // clear the cache
12417                    m_monitor.clearAccessControlListCache();
12418                }
12419            }
12420            if (attrModified || aceModified) {
12421                // fire the event
12422                Map<String, Object> data = new HashMap<String, Object>(2);
12423                data.put(I_CmsEventListener.KEY_RESOURCE, resource);
12424                data.put(
12425                    I_CmsEventListener.KEY_CHANGE,
12426                    Integer.valueOf(
12427                        ((attrModified) ? CHANGED_RESOURCE : 0) | ((aceModified) ? CHANGED_ACCESSCONTROL : 0)));
12428                OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_MODIFIED, data));
12429            }
12430        }
12431    }
12432
12433    /**
12434     * Undoes all content changes of a resource.<p>
12435     *
12436     * @param dbc the database context
12437     * @param onlineProject the online project
12438     * @param offlineResource the offline resource, or <code>null</code> if deleted
12439     * @param onlineResource the online resource
12440     * @param newState the new resource state
12441     * @param moveUndone is a move operation on the same resource has been made
12442     *
12443     * @throws CmsException if something goes wrong
12444     */
12445    private void undoContentChanges(
12446        CmsDbContext dbc,
12447        CmsProject onlineProject,
12448        CmsResource offlineResource,
12449        CmsResource onlineResource,
12450        CmsResourceState newState,
12451        boolean moveUndone)
12452    throws CmsException {
12453
12454        String path = ((moveUndone || (offlineResource == null))
12455        ? onlineResource.getRootPath()
12456        : offlineResource.getRootPath());
12457
12458        // change folder or file?
12459        I_CmsUserDriver userDriver = getUserDriver(dbc);
12460        I_CmsVfsDriver vfsDriver = getVfsDriver(dbc);
12461        if (onlineResource.isFolder()) {
12462            CmsFolder restoredFolder = new CmsFolder(
12463                onlineResource.getStructureId(),
12464                onlineResource.getResourceId(),
12465                path,
12466                onlineResource.getTypeId(),
12467                onlineResource.getFlags(),
12468                dbc.currentProject().getUuid(),
12469                newState,
12470                onlineResource.getDateCreated(),
12471                onlineResource.getUserCreated(),
12472                onlineResource.getDateLastModified(),
12473                onlineResource.getUserLastModified(),
12474                onlineResource.getDateReleased(),
12475                onlineResource.getDateExpired(),
12476                onlineResource.getVersion()); // version number does not matter since it will be computed later
12477
12478            // write the folder in the offline project
12479            // this sets a flag so that the folder date is not set to the current time
12480            restoredFolder.setDateLastModified(onlineResource.getDateLastModified());
12481
12482            // write the folder
12483            vfsDriver.writeResource(dbc, dbc.currentProject().getUuid(), restoredFolder, NOTHING_CHANGED);
12484
12485            // restore the properties from the online project
12486            vfsDriver.deletePropertyObjects(
12487                dbc,
12488                dbc.currentProject().getUuid(),
12489                restoredFolder,
12490                CmsProperty.DELETE_OPTION_DELETE_STRUCTURE_AND_RESOURCE_VALUES);
12491
12492            List<CmsProperty> propertyInfos = vfsDriver.readPropertyObjects(dbc, onlineProject, onlineResource);
12493            vfsDriver.writePropertyObjects(dbc, dbc.currentProject(), restoredFolder, propertyInfos);
12494
12495            // restore the access control entries from the online project
12496            userDriver.removeAccessControlEntries(dbc, dbc.currentProject(), onlineResource.getResourceId());
12497            ListIterator<CmsAccessControlEntry> aceList = userDriver.readAccessControlEntries(
12498                dbc,
12499                onlineProject,
12500                onlineResource.getResourceId(),
12501                false).listIterator();
12502
12503            while (aceList.hasNext()) {
12504                CmsAccessControlEntry ace = aceList.next();
12505                userDriver.createAccessControlEntry(
12506                    dbc,
12507                    dbc.currentProject(),
12508                    onlineResource.getResourceId(),
12509                    ace.getPrincipal(),
12510                    ace.getPermissions().getAllowedPermissions(),
12511                    ace.getPermissions().getDeniedPermissions(),
12512                    ace.getFlags());
12513            }
12514        } else {
12515            byte[] onlineContent = vfsDriver.readContent(
12516                dbc,
12517                CmsProject.ONLINE_PROJECT_ID,
12518                onlineResource.getResourceId());
12519
12520            CmsFile restoredFile = new CmsFile(
12521                onlineResource.getStructureId(),
12522                onlineResource.getResourceId(),
12523                path,
12524                onlineResource.getTypeId(),
12525                onlineResource.getFlags(),
12526                dbc.currentProject().getUuid(),
12527                newState,
12528                onlineResource.getDateCreated(),
12529                onlineResource.getUserCreated(),
12530                onlineResource.getDateLastModified(),
12531                onlineResource.getUserLastModified(),
12532                onlineResource.getDateReleased(),
12533                onlineResource.getDateExpired(),
12534                0,
12535                onlineResource.getLength(),
12536                onlineResource.getDateContent(),
12537                onlineResource.getVersion(), // version number does not matter since it will be computed later
12538                onlineContent);
12539
12540            // write the file in the offline project
12541            // this sets a flag so that the file date is not set to the current time
12542            restoredFile.setDateLastModified(onlineResource.getDateLastModified());
12543
12544            // collect the old properties
12545            List<CmsProperty> properties = vfsDriver.readPropertyObjects(dbc, onlineProject, onlineResource);
12546
12547            if (offlineResource != null) {
12548                // bug fix 1020: delete all properties (inclum_rejectStructureIdded shared),
12549                // shared properties will be recreated by the next call of #createResource(...)
12550                vfsDriver.deletePropertyObjects(
12551                    dbc,
12552                    dbc.currentProject().getUuid(),
12553                    onlineResource,
12554                    CmsProperty.DELETE_OPTION_DELETE_STRUCTURE_AND_RESOURCE_VALUES);
12555
12556                // implementation notes:
12557                // undo changes can become complex e.g. if a resource was deleted, and then
12558                // another resource was copied over the deleted file as a sibling
12559                // therefore we must "clean" delete the offline resource, and then create
12560                // an new resource with the create method
12561                // note that this does NOT apply to folders, since a folder cannot be replaced
12562                // like a resource anyway
12563                deleteResource(dbc, offlineResource, CmsResource.DELETE_PRESERVE_SIBLINGS);
12564            }
12565            CmsResource res = createResource(
12566                dbc,
12567                restoredFile.getRootPath(),
12568                restoredFile,
12569                restoredFile.getContents(),
12570                properties,
12571                false);
12572
12573            // copy the access control entries from the online project
12574            if (offlineResource != null) {
12575                userDriver.removeAccessControlEntries(dbc, dbc.currentProject(), onlineResource.getResourceId());
12576            }
12577            ListIterator<CmsAccessControlEntry> aceList = userDriver.readAccessControlEntries(
12578                dbc,
12579                onlineProject,
12580                onlineResource.getResourceId(),
12581                false).listIterator();
12582
12583            while (aceList.hasNext()) {
12584                CmsAccessControlEntry ace = aceList.next();
12585                userDriver.createAccessControlEntry(
12586                    dbc,
12587                    dbc.currentProject(),
12588                    res.getResourceId(),
12589                    ace.getPrincipal(),
12590                    ace.getPermissions().getAllowedPermissions(),
12591                    ace.getPermissions().getDeniedPermissions(),
12592                    ace.getFlags());
12593            }
12594
12595            vfsDriver.deleteUrlNameMappingEntries(
12596                dbc,
12597                false,
12598                CmsUrlNameMappingFilter.ALL.filterStructureId(res.getStructureId()).filterStates(
12599                    CmsUrlNameMappingEntry.MAPPING_STATUS_NEW,
12600                    CmsUrlNameMappingEntry.MAPPING_STATUS_REPLACE_ON_PUBLISH));
12601            // restore the state to unchanged
12602            res.setState(newState);
12603            m_vfsDriver.writeResourceState(dbc, dbc.currentProject(), res, UPDATE_ALL, false);
12604        }
12605
12606        // delete all offline relations
12607        if (offlineResource != null) {
12608            vfsDriver.deleteRelations(dbc, dbc.currentProject().getUuid(), offlineResource, CmsRelationFilter.TARGETS);
12609        }
12610        // get online relations
12611        List<CmsRelation> relations = vfsDriver.readRelations(
12612            dbc,
12613            CmsProject.ONLINE_PROJECT_ID,
12614            onlineResource,
12615            CmsRelationFilter.TARGETS);
12616        // write offline relations
12617        Iterator<CmsRelation> itRelations = relations.iterator();
12618        while (itRelations.hasNext()) {
12619            CmsRelation relation = itRelations.next();
12620            vfsDriver.createRelation(dbc, dbc.currentProject().getUuid(), relation);
12621        }
12622
12623        // update the cache
12624        m_monitor.clearResourceCache();
12625        m_monitor.flushCache(CmsMemoryMonitor.CacheType.PROPERTY, CmsMemoryMonitor.CacheType.PROPERTY_LIST);
12626
12627        if ((offlineResource == null) || offlineResource.getRootPath().equals(onlineResource.getRootPath())) {
12628            log(
12629                dbc,
12630                new CmsLogEntry(
12631                    dbc,
12632                    onlineResource.getStructureId(),
12633                    CmsLogEntryType.RESOURCE_RESTORED,
12634                    new String[] {onlineResource.getRootPath()}),
12635                false);
12636        } else {
12637            log(
12638                dbc,
12639                new CmsLogEntry(
12640                    dbc,
12641                    offlineResource.getStructureId(),
12642                    CmsLogEntryType.RESOURCE_MOVE_RESTORED,
12643                    new String[] {offlineResource.getRootPath(), onlineResource.getRootPath()}),
12644                false);
12645        }
12646        if (offlineResource != null) {
12647            OpenCms.fireCmsEvent(
12648                new CmsEvent(
12649                    I_CmsEventListener.EVENT_RESOURCE_AND_PROPERTIES_MODIFIED,
12650                    Collections.<String, Object> singletonMap(I_CmsEventListener.KEY_RESOURCE, offlineResource)));
12651        } else {
12652            OpenCms.fireCmsEvent(
12653                new CmsEvent(
12654                    I_CmsEventListener.EVENT_RESOURCE_AND_PROPERTIES_MODIFIED,
12655                    Collections.<String, Object> singletonMap(I_CmsEventListener.KEY_RESOURCE, onlineResource)));
12656        }
12657    }
12658
12659    /**
12660     * Updates the current users context dates with the given resource.<p>
12661     *
12662     * This checks the date information of the resource based on
12663     * {@link CmsResource#getDateLastModified()} as well as
12664     * {@link CmsResource#getDateReleased()} and {@link CmsResource#getDateExpired()}.
12665     * The current users request context is updated with the the "latest" dates found.<p>
12666     *
12667     * This is required in order to ensure proper setting of <code>"last-modified"</code> http headers
12668     * and also for expiration of cached elements in the Flex cache.
12669     * Consider the following use case: Page A is generated from resources x, y and z.
12670     * If either x, y or z has an expiration / release date set, then page A must expire at a certain point
12671     * in time. This is ensured by the context date check here.<p>
12672     *
12673     * @param dbc the current database context
12674     * @param resource the resource to get the date information from
12675     */
12676    private void updateContextDates(CmsDbContext dbc, CmsResource resource) {
12677
12678        CmsFlexRequestContextInfo info = dbc.getFlexRequestContextInfo();
12679        if (info != null) {
12680            info.updateFromResource(resource);
12681        }
12682    }
12683
12684    /**
12685     * Updates the current users context dates with each {@link CmsResource} object in the given list.<p>
12686     *
12687     * The given input list is returned unmodified.<p>
12688     *
12689     * Please see {@link #updateContextDates(CmsDbContext, CmsResource)} for an explanation of what this method does.<p>
12690     *
12691     * @param dbc the current database context
12692     * @param resourceList a list of {@link CmsResource} objects
12693     *
12694     * @return the original list of CmsResources with the full resource name set
12695     */
12696    private List<CmsResource> updateContextDates(CmsDbContext dbc, List<CmsResource> resourceList) {
12697
12698        CmsFlexRequestContextInfo info = dbc.getFlexRequestContextInfo();
12699        if (info != null) {
12700            for (int i = 0; i < resourceList.size(); i++) {
12701                CmsResource resource = resourceList.get(i);
12702                info.updateFromResource(resource);
12703            }
12704        }
12705        return resourceList;
12706    }
12707
12708    /**
12709     * Returns a List of {@link CmsResource} objects generated when applying the given filter to the given list,
12710     * also updates the current users context dates with each {@link CmsResource} object in the given list,
12711     * also applies the selected resource filter to all resources in the list and returns the remaining resources.<p>
12712     *
12713     * Please see {@link #updateContextDates(CmsDbContext, CmsResource)} for an explanation of what this method does.<p>
12714     *
12715     * @param dbc the current database context
12716     * @param resourceList a list of {@link CmsResource} objects
12717     * @param filter the resource filter to use
12718     *
12719     * @return a List of {@link CmsResource} objects generated when applying the given filter to the given list
12720     */
12721    private List<CmsResource> updateContextDates(
12722        CmsDbContext dbc,
12723        List<CmsResource> resourceList,
12724        CmsResourceFilter filter) {
12725
12726        if (CmsResourceFilter.ALL == filter) {
12727            // if there is no filter required, then use the simpler method that does not apply the filter
12728            return new ArrayList<CmsResource>(updateContextDates(dbc, resourceList));
12729        }
12730
12731        CmsFlexRequestContextInfo info = dbc.getFlexRequestContextInfo();
12732        List<CmsResource> result = new ArrayList<CmsResource>(resourceList.size());
12733        for (int i = 0; i < resourceList.size(); i++) {
12734            CmsResource resource = resourceList.get(i);
12735            if (filter.isValid(dbc.getRequestContext(), resource)) {
12736                result.add(resource);
12737            }
12738            // must also include "invalid" resources for the update of context dates
12739            // since a resource may be invalid because of release / expiration date
12740            if (info != null) {
12741                info.updateFromResource(resource);
12742            }
12743        }
12744        return result;
12745    }
12746
12747    /**
12748     * Updates the state of a resource, depending on the <code>resourceState</code> parameter.<p>
12749     *
12750     * @param dbc the db context
12751     * @param resource the resource
12752     * @param resourceState if <code>true</code> the resource state will be updated, if not just the structure state.
12753     *
12754     * @throws CmsDataAccessException if something goes wrong
12755     */
12756    private void updateState(CmsDbContext dbc, CmsResource resource, boolean resourceState)
12757    throws CmsDataAccessException {
12758
12759        CmsUUID projectId = ((dbc.getProjectId() == null) || dbc.getProjectId().isNullUUID())
12760        ? dbc.currentProject().getUuid()
12761        : dbc.getProjectId();
12762        resource.setUserLastModified(dbc.currentUser().getId());
12763        if (resourceState) {
12764            // update the whole resource state
12765            getVfsDriver(dbc).writeResource(dbc, projectId, resource, UPDATE_RESOURCE_STATE);
12766        } else {
12767            // update the structure state
12768            getVfsDriver(dbc).writeResource(dbc, projectId, resource, UPDATE_STRUCTURE_STATE);
12769        }
12770    }
12771
12772    /**
12773     * Wraps a driver object with a dynamic proxy that counts method calls and their durations.<p>
12774     *
12775     * @param newDriverInstance the driver instance to wrap
12776     * @return the proxy
12777     */
12778    private Object wrapDriverInProfilingProxy(Object newDriverInstance) {
12779
12780        Class<?> cls = getDriverInterfaceForProxy(newDriverInstance);
12781        if (cls == null) {
12782            return newDriverInstance;
12783        }
12784        return Proxy.newProxyInstance(
12785            Thread.currentThread().getContextClassLoader(),
12786            new Class[] {cls},
12787            new CmsProfilingInvocationHandler(newDriverInstance, CmsDefaultProfilingHandler.INSTANCE));
12788    }
12789
12790}