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.timing.CmsDefaultProfilingHandler;
040import org.opencms.db.timing.CmsProfilingInvocationHandler;
041import org.opencms.db.urlname.CmsUrlNameMappingEntry;
042import org.opencms.db.urlname.CmsUrlNameMappingFilter;
043import org.opencms.db.userpublishlist.A_CmsLogPublishListConverter;
044import org.opencms.db.userpublishlist.CmsLogPublishListConverterAllUsers;
045import org.opencms.db.userpublishlist.CmsLogPublishListConverterCurrentUser;
046import org.opencms.file.CmsDataAccessException;
047import org.opencms.file.CmsFile;
048import org.opencms.file.CmsFolder;
049import org.opencms.file.CmsGroup;
050import org.opencms.file.CmsObject;
051import org.opencms.file.CmsProject;
052import org.opencms.file.CmsProperty;
053import org.opencms.file.CmsPropertyDefinition;
054import org.opencms.file.CmsRequestContext;
055import org.opencms.file.CmsResource;
056import org.opencms.file.CmsResourceFilter;
057import org.opencms.file.CmsUser;
058import org.opencms.file.CmsUserSearchParameters;
059import org.opencms.file.CmsVfsException;
060import org.opencms.file.CmsVfsResourceAlreadyExistsException;
061import org.opencms.file.CmsVfsResourceNotFoundException;
062import org.opencms.file.I_CmsResource;
063import org.opencms.file.history.CmsHistoryFile;
064import org.opencms.file.history.CmsHistoryFolder;
065import org.opencms.file.history.CmsHistoryPrincipal;
066import org.opencms.file.history.CmsHistoryProject;
067import org.opencms.file.history.I_CmsHistoryResource;
068import org.opencms.file.quota.CmsFolderSizeEntry;
069import org.opencms.file.quota.CmsFolderSizeOptions;
070import org.opencms.file.types.CmsResourceTypeFolder;
071import org.opencms.file.types.CmsResourceTypeJsp;
072import org.opencms.file.types.I_CmsResourceType;
073import org.opencms.flex.CmsFlexRequestContextInfo;
074import org.opencms.gwt.shared.alias.CmsAliasImportResult;
075import org.opencms.gwt.shared.alias.CmsAliasImportStatus;
076import org.opencms.gwt.shared.alias.CmsAliasMode;
077import org.opencms.i18n.CmsLocaleManager;
078import org.opencms.i18n.CmsMessageContainer;
079import org.opencms.jsp.CmsJspNavBuilder;
080import org.opencms.lock.CmsLock;
081import org.opencms.lock.CmsLockException;
082import org.opencms.lock.CmsLockFilter;
083import org.opencms.lock.CmsLockManager;
084import org.opencms.lock.CmsLockType;
085import org.opencms.main.CmsEvent;
086import org.opencms.main.CmsException;
087import org.opencms.main.CmsIllegalArgumentException;
088import org.opencms.main.CmsIllegalStateException;
089import org.opencms.main.CmsInitException;
090import org.opencms.main.CmsLog;
091import org.opencms.main.CmsMultiException;
092import org.opencms.main.I_CmsEventListener;
093import org.opencms.main.OpenCms;
094import org.opencms.module.CmsModule;
095import org.opencms.monitor.CmsMemoryMonitor;
096import org.opencms.monitor.CmsMemoryMonitor.CacheType;
097import org.opencms.publish.CmsPublishEngine;
098import org.opencms.publish.CmsPublishJobInfoBean;
099import org.opencms.publish.CmsPublishReport;
100import org.opencms.relations.CmsCategoryService;
101import org.opencms.relations.CmsLink;
102import org.opencms.relations.CmsRelation;
103import org.opencms.relations.CmsRelationFilter;
104import org.opencms.relations.CmsRelationSystemValidator;
105import org.opencms.relations.CmsRelationType;
106import org.opencms.relations.CmsRelationType.CopyBehavior;
107import org.opencms.relations.I_CmsLinkParseable;
108import org.opencms.report.CmsLogReport;
109import org.opencms.report.I_CmsReport;
110import org.opencms.security.CmsAccessControlEntry;
111import org.opencms.security.CmsAccessControlList;
112import org.opencms.security.CmsAuthentificationException;
113import org.opencms.security.CmsOrganizationalUnit;
114import org.opencms.security.CmsPasswordEncryptionException;
115import org.opencms.security.CmsPermissionSet;
116import org.opencms.security.CmsPermissionSetCustom;
117import org.opencms.security.CmsPrincipal;
118import org.opencms.security.CmsRole;
119import org.opencms.security.CmsSecurityException;
120import org.opencms.security.I_CmsPermissionHandler;
121import org.opencms.security.I_CmsPermissionHandler.LockCheck;
122import org.opencms.security.I_CmsPrincipal;
123import org.opencms.security.twofactor.CmsSecondFactorInfo;
124import org.opencms.security.twofactor.CmsSecondFactorSetupException;
125import org.opencms.security.twofactor.CmsTwoFactorAuthenticationHandler;
126import org.opencms.site.CmsSiteMatcher;
127import org.opencms.util.CmsFileUtil;
128import org.opencms.util.CmsPath;
129import org.opencms.util.CmsStringUtil;
130import org.opencms.util.CmsUUID;
131import org.opencms.util.PrintfFormat;
132import org.opencms.workflow.CmsDefaultWorkflowManager;
133import org.opencms.workplace.threads.A_CmsProgressThread;
134
135import java.lang.reflect.Proxy;
136import java.util.ArrayList;
137import java.util.Arrays;
138import java.util.Collection;
139import java.util.Collections;
140import java.util.Comparator;
141import java.util.Date;
142import java.util.HashMap;
143import java.util.HashSet;
144import java.util.Iterator;
145import java.util.List;
146import java.util.ListIterator;
147import java.util.Locale;
148import java.util.Map;
149import java.util.Map.Entry;
150import java.util.Set;
151import java.util.TreeSet;
152import java.util.concurrent.ConcurrentMap;
153import java.util.concurrent.ExecutionException;
154import java.util.concurrent.TimeUnit;
155import java.util.function.Predicate;
156import java.util.function.Supplier;
157import java.util.regex.Pattern;
158import java.util.regex.PatternSyntaxException;
159import java.util.stream.Collectors;
160
161import org.apache.commons.logging.Log;
162
163import com.google.common.cache.Cache;
164import com.google.common.cache.CacheBuilder;
165import com.google.common.collect.ArrayListMultimap;
166import com.google.common.collect.Maps;
167import com.google.common.collect.Multimap;
168
169/**
170 * The OpenCms driver manager.<p>
171 *
172 * @since 6.0.0
173 */
174public final class CmsDriverManager implements I_CmsEventListener {
175
176    /**
177     * Enum for distinguishing between login modes.
178     */
179    public static enum LoginUserMode {
180        /** 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). */
181        checkOnly,
182
183        /** Normal login process. */
184        standard
185    }
186
187    /**
188     * Resource list which additionally knows whether it should be cacheable in the resource list cache or not.
189     */
190    public static class ResourceListWithCacheability extends ArrayList<CmsResource> {
191
192        /** Serial version id. */
193        private static final long serialVersionUID = 1L;
194
195        /** True if the list should be cacheable. */
196        private boolean m_cacheable = true;
197
198        /**
199         * Creates a new instance.
200         */
201        public ResourceListWithCacheability() {
202
203            super();
204        }
205
206        /**
207         * Creates a new instance.
208         * @param initialCapacity the initial capacity
209         */
210        public ResourceListWithCacheability(int initialCapacity) {
211
212            super(initialCapacity);
213        }
214
215        /**
216         * Returns true if the resource list is cacheable.
217         *
218         * @return true if the list is cacheable
219         */
220        public boolean isCacheable() {
221
222            return m_cacheable;
223        }
224
225        /**
226         * Enables/disables cacheability for the resource list.
227         * @param cacheable true if the list should be cacheable
228         */
229        public void setCacheable(boolean cacheable) {
230
231            m_cacheable = cacheable;
232        }
233
234    }
235
236    /**
237     * Special key class for caching the resource OU data with a Guava LoadingCache.<p>
238     *
239     * In principle, the actual cache key is just the current project, but because of how cache loaders work,
240     * the key must contain everything that varies between calls and is required to load the value. So we also store the DB context
241     * for use by the cache loader. The project (offline/online) must still be stored, because the DB context gets invalidated
242     * eventually, i.e. its project id gets nulled.
243     */
244    public static class ResourceOUCacheKey {
245
246        /** The actual cache key. */
247        private String m_actualKey;
248
249        /** The DB context. */
250        private CmsDbContext m_dbc;
251
252        /** The driver manager to use. */
253        private CmsDriverManager m_driverManager;
254
255        /**
256         * Creates a new instance.
257         *
258         * @param driverManager the driver manager to use
259         * @param dbc the current DB context
260         */
261        public ResourceOUCacheKey(CmsDriverManager driverManager, CmsDbContext dbc) {
262
263            m_dbc = dbc;
264            m_driverManager = driverManager;
265            m_actualKey = CmsProject.ONLINE_PROJECT_ID.equals(dbc.currentProject().getId()) ? "ONLINE" : "OFFLINE";
266        }
267
268        /**
269         * @see java.lang.Object#equals(java.lang.Object)
270         */
271        @Override
272        public boolean equals(Object obj) {
273
274            return (obj instanceof ResourceOUCacheKey)
275                && ((ResourceOUCacheKey)obj).getActualKey().equals(getActualKey());
276        }
277
278        /**
279         * Gets the stored DB context.<p>
280         *
281         * Note that the DB contex returned by this may have been invalidated!
282         *
283         * @return the stored DB context
284         */
285        public CmsDbContext getDbContext() {
286
287            return m_dbc;
288        }
289
290        /**
291         * Gets the current driver manager.
292         *
293         * @return the driver manager to use
294         **/
295        public CmsDriverManager getDriverManager() {
296
297            return m_driverManager;
298        }
299
300        /**
301         * @see java.lang.Object#hashCode()
302         */
303        @Override
304        public int hashCode() {
305
306            return getActualKey().hashCode();
307        }
308
309        /**
310         * Gets the actual key data.
311         *
312         * @return the actual key data
313         */
314        private String getActualKey() {
315
316            return m_actualKey;
317        }
318
319    }
320
321    /**
322     * Helper class used to store information about resources assigned to OUs in a cache.
323     */
324    public static class ResourceOUMap {
325
326        /** Multimap from the paths of resources to the OUs to which they are assigned as OU resources. */
327        private Multimap<CmsPath, CmsOrganizationalUnit> m_ousByAssignedResourcePaths = ArrayListMultimap.create();
328
329        /** The organizational units, with their UUIDs as keys. */
330        private Map<CmsUUID, CmsOrganizationalUnit> m_ousById = new HashMap<>();
331
332        /**
333         * Gets the list of organizational units to which a given root path belongs, according to the cached
334         * OU resource assignments.
335         *
336         * @param rootPath the root path
337         * @return the organizational units to which the path belongs
338         */
339        public List<CmsOrganizationalUnit> getResourceOrgUnits(String rootPath) {
340
341            Set<CmsOrganizationalUnit> result = new HashSet<>();
342            String currentPath = rootPath;
343            while (currentPath != null) {
344                result.addAll(m_ousByAssignedResourcePaths.get(new CmsPath(currentPath)));
345                currentPath = CmsResource.getParentFolder(currentPath);
346            }
347            return new ArrayList<>(result);
348        }
349
350        /**
351         * Reads the OU resource data from the VFS and initializes this instance with it.
352         *
353         * @param driverManager the driver manager to use
354         * @param dbc the current DB context
355         * @throws CmsException if something goes wrong
356         */
357        public void init(CmsDriverManager driverManager, CmsDbContext dbc) throws CmsException {
358
359            List<CmsRelation> relations = driverManager.getRelationsForResource(
360                dbc,
361                null,
362                CmsRelationFilter.ALL.filterType(CmsRelationType.OU_RESOURCE));
363            CmsOrganizationalUnit root = driverManager.readOrganizationalUnit(dbc, "");
364            List<CmsOrganizationalUnit> children = driverManager.getOrganizationalUnits(dbc, root, true);
365
366            Set<CmsOrganizationalUnit> ous = new HashSet<>();
367            ous.add(root);
368            ous.addAll(children);
369            init(relations, ous);
370
371        }
372
373        /**
374         * Initializes the OU resource data.
375         *
376         * @param ouRelations the current list of OU relations
377         * @param ous the current list of OUs
378         */
379        public void init(Collection<CmsRelation> ouRelations, Collection<CmsOrganizationalUnit> ous) {
380
381            m_ousById.clear();
382            m_ousByAssignedResourcePaths.clear();
383            for (CmsOrganizationalUnit ou : ous) {
384                m_ousById.put(ou.getId(), ou);
385            }
386            for (CmsRelation rel : ouRelations) {
387                CmsOrganizationalUnit ou = m_ousById.get(rel.getSourceId());
388                if (ou != null) {
389                    m_ousByAssignedResourcePaths.put(new CmsPath(rel.getTargetPath()), ou);
390                }
391            }
392        }
393    }
394
395    /**
396     * The comparator used for comparing url name mapping entries by date.<p>
397     */
398    class UrlNameMappingComparator implements Comparator<CmsUrlNameMappingEntry> {
399
400        /**
401         * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
402         */
403        public int compare(CmsUrlNameMappingEntry o1, CmsUrlNameMappingEntry o2) {
404
405            long date1 = o1.getDateChanged();
406            long date2 = o2.getDateChanged();
407            if (date1 < date2) {
408                return -1;
409            }
410            if (date1 > date2) {
411                return +1;
412            }
413            return 0;
414        }
415    }
416
417    /**
418     * Enumeration class for the mode parameter in the
419     * {@link CmsDriverManager#readChangedResourcesInsideProject(CmsDbContext, CmsUUID, CmsReadChangedProjectResourceMode)}
420     * method.<p>
421     */
422    private static class CmsReadChangedProjectResourceMode {
423
424        /**
425         * Default constructor.<p>
426         */
427        protected CmsReadChangedProjectResourceMode() {
428
429            // noop
430        }
431    }
432
433    /** Request context attribute used to override the time used for time-based exclusive access checks. */
434    public static final String ATTR_EXCLUSIVE_ACCESS_CLOCK = "ATTR_EXCLUSIVE_ACCESS_CLOCK";
435
436    /** Attribute for signaling to the user driver that a specific OU should be initialized by fillDefaults. */
437    public static final String ATTR_INIT_OU = "INIT_OU";
438
439    /** DB context attribute used to communicate information about resource cacheability between various methods. */
440    public static final String ATTR_PERMISSION_NOCACHE = "ATTR_PERMISSION_NOCACHE";
441
442    /** Attribute login. */
443    public static final String ATTRIBUTE_LOGIN = "A_LOGIN";
444
445    /** Cache key for all properties. */
446    public static final String CACHE_ALL_PROPERTIES = "_CAP_";
447
448    /**
449     * Values indicating changes of a resource,
450     * ordered according to the scope of the change.
451     */
452    /** Value to indicate a change in access control entries of a resource. */
453    public static final int CHANGED_ACCESSCONTROL = 1;
454
455    /** Value to indicate a content change. */
456    public static final int CHANGED_CONTENT = 16;
457
458    /** Value to indicate a change in the lastmodified settings of a resource. */
459    public static final int CHANGED_LASTMODIFIED = 4;
460
461    /** Value to indicate a project change. */
462    public static final int CHANGED_PROJECT = 32;
463
464    /** Value to indicate a change in the resource data. */
465    public static final int CHANGED_RESOURCE = 8;
466
467    /** Value to indicate a change in the availability timeframe. */
468    public static final int CHANGED_TIMEFRAME = 2;
469
470    /** "cache" string in the configuration-file. */
471    public static final String CONFIGURATION_CACHE = "cache";
472
473    /** "db" string in the configuration-file. */
474    public static final String CONFIGURATION_DB = "db";
475
476    /** "driver.history" string in the configuration-file. */
477    public static final String CONFIGURATION_HISTORY = "driver.history";
478
479    /** "driver.project" string in the configuration-file. */
480    public static final String CONFIGURATION_PROJECT = "driver.project";
481
482    /** "subscription.vfs" string in the configuration file. */
483    public static final String CONFIGURATION_SUBSCRIPTION = "driver.subscription";
484
485    /** "driver.user" string in the configuration-file. */
486    public static final String CONFIGURATION_USER = "driver.user";
487
488    /** "driver.vfs" string in the configuration-file. */
489    public static final String CONFIGURATION_VFS = "driver.vfs";
490
491    /** DBC attribute key needed to fix publishing behavior involving siblings. */
492    public static final String KEY_CHANGED_AND_DELETED = "changedAndDeleted";
493
494    /** The vfs path of the loast and found folder. */
495    public static final String LOST_AND_FOUND_FOLDER = "/system/lost-found";
496
497    /** The maximum length of a VFS resource path. */
498    public static final int MAX_VFS_RESOURCE_PATH_LENGTH = 512;
499
500    /** Key for indicating no changes. */
501    public static final int NOTHING_CHANGED = 0;
502
503    /** Name of the configuration parameter to enable/disable logging to the CMS_LOG table. */
504    public static final String PARAM_LOG_TABLE_ENABLED = "log.table.enabled";
505
506    /** Indicates to ignore the resource path when matching resources. */
507    public static final String READ_IGNORE_PARENT = null;
508
509    /** Indicates to ignore the time value. */
510    public static final long READ_IGNORE_TIME = 0L;
511
512    /** Indicates to ignore the resource type when matching resources. */
513    public static final int READ_IGNORE_TYPE = -1;
514
515    /** Indicates to match resources NOT having the given state. */
516    public static final int READMODE_EXCLUDE_STATE = 8;
517
518    /** Indicates to match immediate children only. */
519    public static final int READMODE_EXCLUDE_TREE = 1;
520
521    /** Indicates to match resources NOT having the given type. */
522    public static final int READMODE_EXCLUDE_TYPE = 4;
523
524    /** Mode for reading project resources from the db. */
525    public static final int READMODE_IGNORESTATE = 0;
526
527    /** Indicates to match resources in given project only. */
528    public static final int READMODE_INCLUDE_PROJECT = 2;
529
530    /** Indicates to match all successors. */
531    public static final int READMODE_INCLUDE_TREE = 0;
532
533    /** Mode for reading project resources from the db. */
534    public static final int READMODE_MATCHSTATE = 1;
535
536    /** Indicates if only file resources should be read. */
537    public static final int READMODE_ONLY_FILES = 128;
538
539    /** Indicates if only folder resources should be read. */
540    public static final int READMODE_ONLY_FOLDERS = 64;
541
542    /** Mode for reading project resources from the db. */
543    public static final int READMODE_UNMATCHSTATE = 2;
544
545    /** Flag that can be used to disable the resource OU caching if necessary. */
546    public static boolean resourceOrgUnitCachingEnabled = true;
547
548    /** Prefix char for temporary files in the VFS. */
549    public static final String TEMP_FILE_PREFIX = "~";
550
551    /** Key to indicate complete update. */
552    public static final int UPDATE_ALL = 3;
553
554    /** Key to indicate update of resource record. */
555    public static final int UPDATE_RESOURCE = 4;
556
557    /** Key to indicate update of last modified project reference. */
558    public static final int UPDATE_RESOURCE_PROJECT = 6;
559
560    /** Key to indicate update of resource state. */
561    public static final int UPDATE_RESOURCE_STATE = 1;
562
563    /** Key to indicate update of resource state including the content date. */
564    public static final int UPDATE_RESOURCE_STATE_CONTENT = 7;
565
566    /** Key to indicate update of structure record. */
567    public static final int UPDATE_STRUCTURE = 5;
568
569    /** Key to indicate update of structure state. */
570    public static final int UPDATE_STRUCTURE_STATE = 2;
571
572    /** Map of pools defined in opencms.properties. */
573    protected static ConcurrentMap<String, CmsDbPoolV11> m_pools = Maps.newConcurrentMap();
574
575    /** The log object for this class. */
576    private static final Log LOG = CmsLog.getLog(CmsDriverManager.class);
577
578    /** Constant mode parameter to read all files and folders in the {@link #readChangedResourcesInsideProject(CmsDbContext, CmsUUID, CmsReadChangedProjectResourceMode)}} method. */
579    private static final CmsReadChangedProjectResourceMode RCPRM_FILES_AND_FOLDERS_MODE = new CmsReadChangedProjectResourceMode();
580
581    /** Constant mode parameter to read all files and folders in the {@link #readChangedResourcesInsideProject(CmsDbContext, CmsUUID, CmsReadChangedProjectResourceMode)}} method. */
582    private static final CmsReadChangedProjectResourceMode RCPRM_FILES_ONLY_MODE = new CmsReadChangedProjectResourceMode();
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_FOLDERS_ONLY_MODE = new CmsReadChangedProjectResourceMode();
586
587    private final Object m_publishTagLock = new Object();
588
589    /** The history driver. */
590    private I_CmsHistoryDriver m_historyDriver;
591
592    /** The HTML link validator. */
593    private CmsRelationSystemValidator m_htmlLinkValidator;
594
595    /** The class used for cache key generation. */
596    private I_CmsCacheKey m_keyGenerator;
597
598    /** The lock manager. */
599    private CmsLockManager m_lockManager;
600
601    /** The log entry cache. */
602    private List<CmsLogEntry> m_log = new ArrayList<CmsLogEntry>();
603
604    /** Local reference to the memory monitor to avoid multiple lookups through the OpenCms singleton. */
605    private CmsMemoryMonitor m_monitor;
606
607    /** The project driver. */
608    private I_CmsProjectDriver m_projectDriver;
609
610    /** The the configuration read from the <code>opencms.properties</code> file. */
611    private CmsParameterConfiguration m_propertyConfiguration;
612
613    /** the publish engine. */
614    private CmsPublishEngine m_publishEngine;
615
616    /** Object used for synchronizing updates to the user publish list. */
617    private Object m_publishListUpdateLock = new Object();
618
619    /** The security manager (for access checks). */
620    private CmsSecurityManager m_securityManager;
621
622    /** The sql manager. */
623    private CmsSqlManager m_sqlManager;
624
625    /** The subscription driver. */
626    private I_CmsSubscriptionDriver m_subscriptionDriver;
627
628    /** The user driver. */
629    private I_CmsUserDriver m_userDriver;
630
631    /** The VFS driver. */
632    private I_CmsVfsDriver m_vfsDriver;
633
634    private volatile Integer m_lastPublishTag = null;
635
636    /** Cache for which OUs can be skipped for principal transfer when deleting a user. */
637    private Cache<String, Boolean> m_skipTransferPrincipalResourceCache;
638
639    /**
640     * Private constructor, initializes some required member variables.<p>
641     */
642    private CmsDriverManager() {
643
644        // intentionally left blank
645    }
646
647    /**
648     * Reads the required configurations from the opencms.properties file and creates
649     * the various drivers to access the cms resources.<p>
650     *
651     * The initialization process of the driver manager and its drivers is split into
652     * the following phases:
653     * <ul>
654     * <li>the database pool configuration is read</li>
655     * <li>a plain and empty driver manager instance is created</li>
656     * <li>an instance of each driver is created</li>
657     * <li>the driver manager is passed to each driver during initialization</li>
658     * <li>finally, the driver instances are passed to the driver manager during initialization</li>
659     * </ul>
660     *
661     * @param configurationManager the configuration manager
662     * @param securityManager the security manager
663     * @param runtimeInfoFactory the initialized OpenCms runtime info factory
664     * @param publishEngine the publish engine
665     *
666     * @return CmsDriverManager the instantiated driver manager
667     * @throws CmsInitException if the driver manager couldn't be instantiated
668     */
669    public static CmsDriverManager newInstance(
670        CmsConfigurationManager configurationManager,
671        CmsSecurityManager securityManager,
672        I_CmsDbContextFactory runtimeInfoFactory,
673        CmsPublishEngine publishEngine)
674    throws CmsInitException {
675
676        // read the opencms.properties from the configuration
677        CmsParameterConfiguration config = configurationManager.getConfiguration();
678
679        CmsDriverManager driverManager = null;
680        try {
681            // create a driver manager instance
682            driverManager = new CmsDriverManager();
683            if (CmsLog.INIT.isInfoEnabled()) {
684                CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_DRIVER_MANAGER_START_PHASE1_0));
685            }
686            if (runtimeInfoFactory == null) {
687                throw new CmsInitException(
688                    org.opencms.main.Messages.get().container(org.opencms.main.Messages.ERR_CRITICAL_NO_DB_CONTEXT_0));
689            }
690        } catch (Exception exc) {
691            CmsMessageContainer message = Messages.get().container(Messages.LOG_ERR_DRIVER_MANAGER_START_0);
692            if (LOG.isFatalEnabled()) {
693                LOG.fatal(message.key(), exc);
694            }
695            throw new CmsInitException(message, exc);
696        }
697
698        // store the configuration
699        driverManager.m_propertyConfiguration = config;
700
701        // set the security manager
702        driverManager.m_securityManager = securityManager;
703
704        // set the lock manager
705        driverManager.m_lockManager = new CmsLockManager(driverManager);
706
707        // create and set the sql manager
708        driverManager.m_sqlManager = new CmsSqlManager(driverManager);
709
710        // set the publish engine
711        driverManager.m_publishEngine = publishEngine;
712
713        if (CmsLog.INIT.isInfoEnabled()) {
714            CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_DRIVER_MANAGER_START_PHASE2_0));
715        }
716
717        // read the pool names to initialize
718        List<String> driverPoolNames = config.getList(CmsDriverManager.CONFIGURATION_DB + ".pools");
719        if (CmsLog.INIT.isInfoEnabled()) {
720            String names = "";
721            for (String name : driverPoolNames) {
722                names += name + " ";
723            }
724            CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_DRIVER_MANAGER_START_POOLS_1, names));
725        }
726
727        // initialize each pool
728        for (String name : driverPoolNames) {
729            driverManager.newPoolInstance(config, name);
730        }
731
732        // initialize the runtime info factory with the generated driver manager
733        runtimeInfoFactory.initialize(driverManager);
734
735        if (CmsLog.INIT.isInfoEnabled()) {
736            CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_DRIVER_MANAGER_START_PHASE3_0));
737        }
738
739        // store the access objects
740        CmsDbContext dbc = runtimeInfoFactory.getDbContext();
741        driverManager.m_vfsDriver = (I_CmsVfsDriver)driverManager.createDriver(
742            dbc,
743            configurationManager,
744            config,
745            CONFIGURATION_VFS,
746            ".vfs.driver");
747        dbc.clear();
748
749        dbc = runtimeInfoFactory.getDbContext();
750        driverManager.m_userDriver = (I_CmsUserDriver)driverManager.createDriver(
751            dbc,
752            configurationManager,
753            config,
754            CONFIGURATION_USER,
755            ".user.driver");
756        dbc.clear();
757
758        dbc = runtimeInfoFactory.getDbContext();
759        driverManager.m_projectDriver = (I_CmsProjectDriver)driverManager.createDriver(
760            dbc,
761            configurationManager,
762            config,
763            CONFIGURATION_PROJECT,
764            ".project.driver");
765        dbc.clear();
766
767        dbc = runtimeInfoFactory.getDbContext();
768        driverManager.m_historyDriver = (I_CmsHistoryDriver)driverManager.createDriver(
769            dbc,
770            configurationManager,
771            config,
772            CONFIGURATION_HISTORY,
773            ".history.driver");
774        dbc.clear();
775
776        dbc = runtimeInfoFactory.getDbContext();
777        try {
778            // we wrap this in a try-catch because otherwise it would fail during the update
779            // process, since the subscription driver configuration does not exist at that point.
780            driverManager.m_subscriptionDriver = (I_CmsSubscriptionDriver)driverManager.createDriver(
781                dbc,
782                configurationManager,
783                config,
784                CONFIGURATION_SUBSCRIPTION,
785                ".subscription.driver");
786        } catch (IndexOutOfBoundsException npe) {
787            LOG.warn("Could not instantiate subscription driver!");
788            LOG.warn(npe.getLocalizedMessage(), npe);
789        }
790        dbc.clear();
791
792        // register the driver manager for required events
793        org.opencms.main.OpenCms.addCmsEventListener(
794            driverManager,
795            new int[] {
796                I_CmsEventListener.EVENT_UPDATE_EXPORTS,
797                I_CmsEventListener.EVENT_CLEAR_CACHES,
798                I_CmsEventListener.EVENT_CLEAR_PRINCIPAL_CACHES,
799                I_CmsEventListener.EVENT_USER_MODIFIED,
800                I_CmsEventListener.EVENT_RESOURCE_MODIFIED,
801                I_CmsEventListener.EVENT_OU_MODIFIED,
802                I_CmsEventListener.EVENT_PUBLISH_PROJECT});
803
804        // not sure the manual cache flushing based on events is sufficient in all cases for the 'is principal transfer skippable?' cache,
805        // so we limit it to a few minutes
806        Cache<String, Boolean> skipTransferPrincipalResourceCache = CacheBuilder.newBuilder().concurrencyLevel(
807            4).expireAfterWrite(5, TimeUnit.MINUTES).build();
808        driverManager.m_skipTransferPrincipalResourceCache = skipTransferPrincipalResourceCache;
809
810        // return the configured driver manager
811        return driverManager;
812    }
813
814    /**
815     * Adds an alias entry.<p>
816     *
817     * @param dbc the database context
818     * @param project the current project
819     * @param alias the alias to add
820     *
821     * @throws CmsException if something goes wrong
822     */
823    public void addAlias(CmsDbContext dbc, CmsProject project, CmsAlias alias) throws CmsException {
824
825        I_CmsVfsDriver vfsDriver = getVfsDriver(dbc);
826        vfsDriver.insertAlias(dbc, project, alias);
827    }
828
829    /**
830     * Adds a new relation to the given resource.<p>
831     *
832     * @param dbc the database context
833     * @param resource the resource to add the relation to
834     * @param target the target of the relation
835     * @param type the type of the relation
836     * @param importCase if importing relations
837     *
838     * @throws CmsException if something goes wrong
839     */
840    public void addRelationToResource(
841        CmsDbContext dbc,
842        CmsResource resource,
843        CmsResource target,
844        CmsRelationType type,
845        boolean importCase)
846    throws CmsException {
847
848        if (type.isDefinedInContent()) {
849            throw new CmsIllegalArgumentException(
850                Messages.get().container(
851                    Messages.ERR_ADD_RELATION_IN_CONTENT_3,
852                    dbc.removeSiteRoot(resource.getRootPath()),
853                    dbc.removeSiteRoot(target.getRootPath()),
854                    type.getLocalizedName(dbc.getRequestContext().getLocale())));
855        }
856        CmsRelation relation = new CmsRelation(resource, target, type);
857        getVfsDriver(dbc).createRelation(dbc, dbc.currentProject().getUuid(), relation);
858        // IMPORTANT: In the import case, the importer has to ensure that the
859        // resource is reindexed offline after the relation has been added.
860        // We do not trigger reindexing here for each added relation due to performance issues.
861        // in the the non-import case reindexing is triggered by the update of the last modification date.
862        if (!importCase) {
863            // log it
864            log(
865                dbc,
866                new CmsLogEntry(
867                    dbc,
868                    resource.getStructureId(),
869                    CmsLogEntryType.RESOURCE_ADD_RELATION,
870                    new String[] {relation.getSourcePath(), relation.getTargetPath()}),
871                false);
872            // touch the resource
873            setDateLastModified(dbc, resource, System.currentTimeMillis());
874        }
875    }
876
877    /**
878     * Adds a resource to the given organizational unit.<p>
879     *
880     * @param dbc the current db context
881     * @param orgUnit the organizational unit to add the resource to
882     * @param resource the resource that is to be added to the organizational unit
883     *
884     * @throws CmsException if something goes wrong
885     *
886     * @see org.opencms.security.CmsOrgUnitManager#addResourceToOrgUnit(CmsObject, String, String)
887     * @see org.opencms.security.CmsOrgUnitManager#addResourceToOrgUnit(CmsObject, String, String)
888     */
889    public void addResourceToOrgUnit(CmsDbContext dbc, CmsOrganizationalUnit orgUnit, CmsResource resource)
890    throws CmsException {
891
892        m_monitor.flushCache(CmsMemoryMonitor.CacheType.HAS_ROLE, CmsMemoryMonitor.CacheType.ROLE_LIST);
893        getUserDriver(dbc).addResourceToOrganizationalUnit(dbc, orgUnit, resource);
894    }
895
896    /**
897     * Adds a user to a group.<p>
898     *
899     * @param dbc the current database context
900     * @param username the name of the user that is to be added to the group
901     * @param groupname the name of the group
902     * @param readRoles if reading roles or groups
903     *
904     * @throws CmsException if operation was not successful
905     * @throws CmsDbEntryNotFoundException if the given user or the given group was not found
906     *
907     * @see #removeUserFromGroup(CmsDbContext, String, String, boolean)
908     */
909    public void addUserToGroup(CmsDbContext dbc, String username, String groupname, boolean readRoles)
910    throws CmsException, CmsDbEntryNotFoundException {
911
912        //check if group exists
913        CmsGroup group = readGroup(dbc, groupname);
914        if (group == null) {
915            // the group does not exists
916            throw new CmsDbEntryNotFoundException(Messages.get().container(Messages.ERR_UNKNOWN_GROUP_1, groupname));
917        }
918        if (group.isVirtual() && !readRoles) {
919            String roleName = CmsRole.valueOf(group).getGroupName();
920            if (!userInGroup(dbc, username, roleName, true)) {
921                addUserToGroup(dbc, username, roleName, true);
922                return;
923            }
924        }
925        if (group.isVirtual()) {
926            // this is an hack to prevent unlimited recursive calls
927            readRoles = false;
928        }
929        if ((readRoles && !group.isRole()) || (!readRoles && group.isRole())) {
930            // we want a role but we got a group, or the other way
931            throw new CmsDbEntryNotFoundException(Messages.get().container(Messages.ERR_UNKNOWN_GROUP_1, groupname));
932        }
933        if (userInGroup(dbc, username, groupname, readRoles)) {
934            // the user is already member of the group
935            return;
936        }
937        //check if the user exists
938        CmsUser user = readUser(dbc, username);
939        if (user == null) {
940            // the user does not exists
941            throw new CmsDbEntryNotFoundException(Messages.get().container(Messages.ERR_UNKNOWN_USER_1, username));
942        }
943
944        // if adding an user to a role
945        if (readRoles) {
946            CmsRole role = CmsRole.valueOf(group);
947            // a role can only be set if the user has the given role
948            m_securityManager.checkRole(dbc, role);
949            // now we check if we already have the role
950            if (m_securityManager.hasRole(dbc, user, role)) {
951                // do nothing
952                return;
953            }
954            // and now we need to remove all possible child-roles
955            List<CmsRole> children = role.getChildren(true);
956            Iterator<CmsGroup> itUserGroups = getGroupsOfUser(
957                dbc,
958                username,
959                group.getOuFqn(),
960                true,
961                true,
962                true,
963                dbc.getRequestContext().getRemoteAddress()).iterator();
964            while (itUserGroups.hasNext()) {
965                CmsGroup roleGroup = itUserGroups.next();
966                if (children.contains(CmsRole.valueOf(roleGroup))) {
967                    // remove only child roles
968                    removeUserFromGroup(dbc, username, roleGroup.getName(), true);
969                }
970            }
971            // update virtual groups
972            Iterator<CmsGroup> it = getVirtualGroupsForRole(dbc, role).iterator();
973            while (it.hasNext()) {
974                CmsGroup virtualGroup = it.next();
975                // here we say readroles = true, to prevent an unlimited recursive calls
976                addUserToGroup(dbc, username, virtualGroup.getName(), true);
977            }
978        }
979
980        //add this user to the group
981        getUserDriver(dbc).createUserInGroup(dbc, user.getId(), group.getId());
982
983        // flush the cache
984        if (readRoles) {
985            m_monitor.flushCache(CmsMemoryMonitor.CacheType.HAS_ROLE, CmsMemoryMonitor.CacheType.ROLE_LIST);
986        }
987        m_monitor.flushUserGroups(user.getId());
988        m_monitor.flushCache(CmsMemoryMonitor.CacheType.USER_LIST);
989
990        if (!dbc.getProjectId().isNullUUID() && !CmsProject.ONLINE_PROJECT_ID.equals(dbc.getProjectId())) {
991            // user modified event is not needed
992            return;
993        }
994        // fire user modified event
995        Map<String, Object> eventData = new HashMap<String, Object>();
996        eventData.put(I_CmsEventListener.KEY_USER_ID, user.getId().toString());
997        eventData.put(I_CmsEventListener.KEY_USER_NAME, user.getName());
998        eventData.put(I_CmsEventListener.KEY_USER_ID, user.getId().toString());
999        eventData.put(I_CmsEventListener.KEY_GROUP_NAME, group.getName());
1000        eventData.put(
1001            I_CmsEventListener.KEY_USER_ACTION,
1002            I_CmsEventListener.VALUE_USER_MODIFIED_ACTION_ADD_USER_TO_GROUP);
1003        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_USER_MODIFIED, eventData));
1004    }
1005
1006    /**
1007     * Changes the lock of a resource to the current user,
1008     * that is "steals" the lock from another user.<p>
1009     *
1010     * @param dbc the current database context
1011     * @param resource the resource to change the lock for
1012     * @param lockType the new lock type to set
1013     *
1014     * @throws CmsException if something goes wrong
1015     * @throws CmsSecurityException if something goes wrong
1016     *
1017     *
1018     * @see CmsObject#changeLock(String)
1019     * @see I_CmsResourceType#changeLock(CmsObject, CmsSecurityManager, CmsResource)
1020     *
1021     * @see CmsSecurityManager#hasPermissions(CmsRequestContext, CmsResource, CmsPermissionSet, boolean, CmsResourceFilter)
1022     */
1023    public void changeLock(CmsDbContext dbc, CmsResource resource, CmsLockType lockType)
1024    throws CmsException, CmsSecurityException {
1025
1026        // get the current lock
1027        CmsLock currentLock = getLock(dbc, resource);
1028        // check if the resource is locked at all
1029        if (currentLock.getEditionLock().isUnlocked() && currentLock.getSystemLock().isUnlocked()) {
1030            throw new CmsLockException(
1031                Messages.get().container(
1032                    Messages.ERR_CHANGE_LOCK_UNLOCKED_RESOURCE_1,
1033                    dbc.getRequestContext().getSitePath(resource)));
1034        } else if ((lockType == CmsLockType.EXCLUSIVE)
1035            && currentLock.isExclusiveOwnedInProjectBy(dbc.currentUser(), dbc.currentProject())) {
1036            // the current lock requires no change
1037            return;
1038        }
1039
1040        // duplicate logic from CmsSecurityManager#hasPermissions() because lock state can't be ignored
1041        // if another user has locked the file, the current user can never get WRITE permissions with the default check
1042        int denied = 0;
1043
1044        // check if the current user is vfs manager
1045        boolean canIgnorePermissions = m_securityManager.hasRoleForResource(
1046            dbc,
1047            dbc.currentUser(),
1048            CmsRole.VFS_MANAGER,
1049            resource);
1050        // if the resource type is jsp
1051        // write is only allowed for developers
1052        if (!canIgnorePermissions && (CmsResourceTypeJsp.isJsp(resource))) {
1053            if (!m_securityManager.hasRoleForResource(dbc, dbc.currentUser(), CmsRole.VFS_MANAGER, resource)) {
1054                denied |= CmsPermissionSet.PERMISSION_WRITE;
1055            }
1056        }
1057        CmsPermissionSetCustom permissions;
1058        if (canIgnorePermissions) {
1059            // if the current user is administrator, anything is allowed
1060            permissions = new CmsPermissionSetCustom(~0);
1061        } else {
1062            // otherwise, get the permissions from the access control list
1063            permissions = getPermissions(dbc, resource, dbc.currentUser());
1064        }
1065        // revoke the denied permissions
1066        permissions.denyPermissions(denied);
1067        // now check if write permission is granted
1068        if ((CmsPermissionSet.ACCESS_WRITE.getPermissions()
1069            & permissions.getPermissions()) != CmsPermissionSet.ACCESS_WRITE.getPermissions()) {
1070            // check failed, throw exception
1071            m_securityManager.checkPermissions(
1072                dbc.getRequestContext(),
1073                resource,
1074                CmsPermissionSet.ACCESS_WRITE,
1075                I_CmsPermissionHandler.PERM_DENIED);
1076        }
1077        // if we got here write permission is granted on the target
1078
1079        // remove the old lock
1080        m_lockManager.removeResource(dbc, resource, true, lockType.isSystem());
1081        // apply the new lock
1082        lockResource(dbc, resource, lockType);
1083    }
1084
1085    /**
1086     * Returns a list with all sub resources of a given folder that have set the given property,
1087     * matching the current property's value with the given old value and replacing it by a given new value.<p>
1088     *
1089     * @param dbc the current database context
1090     * @param resource the resource on which property definition values are changed
1091     * @param propertyDefinition the name of the propertydefinition to change the value
1092     * @param oldValue the old value of the propertydefinition
1093     * @param newValue the new value of the propertydefinition
1094     * @param recursive if true, change the property value on the resource and recursively all property values on
1095     *                     sub-resources (only for folders)
1096     * @return a list with the <code>{@link CmsResource}</code>'s where the property value has been changed
1097     *
1098     * @throws CmsVfsException for now only when the search for the oldvalue failed.
1099     * @throws CmsException if operation was not successful
1100     */
1101    public List<CmsResource> changeResourcesInFolderWithProperty(
1102        CmsDbContext dbc,
1103        CmsResource resource,
1104        String propertyDefinition,
1105        String oldValue,
1106        String newValue,
1107        boolean recursive)
1108    throws CmsVfsException, CmsException {
1109
1110        CmsResourceFilter filter = CmsResourceFilter.IGNORE_EXPIRATION;
1111        // collect the resources to look up
1112        List<CmsResource> resources = new ArrayList<CmsResource>();
1113        if (recursive) {
1114            // read the files in the folder
1115            resources = readResourcesWithProperty(dbc, resource, propertyDefinition, null, filter);
1116            // add the folder itself
1117            resources.add(resource);
1118        } else {
1119            resources.add(resource);
1120        }
1121
1122        Pattern oldPattern;
1123        try {
1124            // remove the place holder if available
1125            String tmpOldValue = oldValue;
1126            if (tmpOldValue.contains(CmsStringUtil.PLACEHOLDER_START)
1127                && tmpOldValue.contains(CmsStringUtil.PLACEHOLDER_END)) {
1128                tmpOldValue = tmpOldValue.replace(CmsStringUtil.PLACEHOLDER_START, "");
1129                tmpOldValue = tmpOldValue.replace(CmsStringUtil.PLACEHOLDER_END, "");
1130            }
1131            // compile regular expression pattern
1132            oldPattern = Pattern.compile(tmpOldValue);
1133        } catch (PatternSyntaxException e) {
1134            throw new CmsVfsException(
1135                Messages.get().container(
1136                    Messages.ERR_CHANGE_RESOURCES_IN_FOLDER_WITH_PROP_4,
1137                    new Object[] {propertyDefinition, oldValue, newValue, resource.getRootPath()}),
1138                e);
1139        }
1140
1141        List<CmsResource> changedResources = new ArrayList<CmsResource>(resources.size());
1142        // create permission set and filter to check each resource
1143        CmsPermissionSet perm = CmsPermissionSet.ACCESS_WRITE;
1144        for (int i = 0; i < resources.size(); i++) {
1145            // loop through found resources and check property values
1146            CmsResource res = resources.get(i);
1147            // check resource state and permissions
1148            try {
1149                m_securityManager.checkPermissions(dbc, res, perm, true, filter);
1150            } catch (Exception e) {
1151                // resource is deleted or not writable for current user
1152                continue;
1153            }
1154            CmsProperty property = readPropertyObject(dbc, res, propertyDefinition, false);
1155            String propertyValue = property.getValue();
1156            boolean changed = false;
1157            if ((propertyValue != null) && oldPattern.matcher(propertyValue).matches()) {
1158                // apply the place holder content
1159                String tmpNewValue = CmsStringUtil.transformValues(oldValue, newValue, propertyValue);
1160                // change structure value
1161                property.setStructureValue(tmpNewValue);
1162                changed = true;
1163            }
1164            if (changed) {
1165                // write property object if something has changed
1166                writePropertyObject(dbc, res, property);
1167                changedResources.add(res);
1168            }
1169        }
1170        return changedResources;
1171    }
1172
1173    /**
1174     * Changes the resource flags of a resource.<p>
1175     *
1176     * The resource flags are used to indicate various "special" conditions
1177     * for a resource. Most notably, the "internal only" setting which signals
1178     * that a resource can not be directly requested with it's URL.<p>
1179     *
1180     * @param dbc the current database context
1181     * @param resource the resource to change the flags for
1182     * @param flags the new resource flags for this resource
1183     *
1184     * @throws CmsException if something goes wrong
1185     *
1186     * @see CmsObject#chflags(String, int)
1187     * @see I_CmsResourceType#chflags(CmsObject, CmsSecurityManager, CmsResource, int)
1188     */
1189    public void chflags(CmsDbContext dbc, CmsResource resource, int flags) throws CmsException {
1190
1191        // must operate on a clone to ensure resource is not modified in case permissions are not granted
1192        CmsResource clone = (CmsResource)resource.clone();
1193        clone.setFlags(flags);
1194        // log it
1195        log(
1196            dbc,
1197            new CmsLogEntry(
1198                dbc,
1199                resource.getStructureId(),
1200                CmsLogEntryType.RESOURCE_FLAGS,
1201                new String[] {resource.getRootPath()}),
1202            false);
1203        // write it
1204        writeResource(dbc, clone);
1205    }
1206
1207    /**
1208     * Changes the resource type of a resource.<p>
1209     *
1210     * OpenCms handles resources according to the resource type,
1211     * not the file suffix. This is e.g. why a JSP in OpenCms can have the
1212     * suffix ".html" instead of ".jsp" only. Changing the resource type
1213     * makes sense e.g. if you want to make a plain text file a JSP resource,
1214     * or a binary file an image, etc.<p>
1215     *
1216     * @param dbc the current database context
1217     * @param resource the resource to change the type for
1218     * @param type the new resource type for this resource
1219     *
1220     * @throws CmsException if something goes wrong
1221     *
1222     * @see CmsObject#chtype(String, int)
1223     * @see I_CmsResourceType#chtype(CmsObject, CmsSecurityManager, CmsResource, int)
1224     */
1225    @SuppressWarnings({"javadoc", "deprecation"})
1226    public void chtype(CmsDbContext dbc, CmsResource resource, int type) throws CmsException {
1227
1228        // must operate on a clone to ensure resource is not modified in case permissions are not granted
1229        CmsResource clone = (CmsResource)resource.clone();
1230        I_CmsResourceType newType = OpenCms.getResourceManager().getResourceType(type);
1231        clone.setType(newType.getTypeId());
1232        // log it
1233        log(
1234            dbc,
1235            new CmsLogEntry(
1236                dbc,
1237                resource.getStructureId(),
1238                CmsLogEntryType.RESOURCE_TYPE,
1239                new String[] {resource.getRootPath()}),
1240            false);
1241        // write it
1242        writeResource(dbc, clone);
1243    }
1244
1245    /**
1246     * Cleans up the publish history entries according to the given filter.
1247     *
1248     * @param dbc the database context
1249     * @param filter the filter
1250     * @return the number of cleaned up rows
1251     * @throws CmsDataAccessException if something goes wrong
1252     */
1253    public int cleanupPublishHistory(CmsDbContext dbc, CmsPublishHistoryCleanupFilter filter)
1254    throws CmsDataAccessException {
1255
1256        int result = m_projectDriver.cleanupPublishHistory(dbc, filter);
1257        if (filter.getMode() == CmsPublishHistoryCleanupFilter.Mode.single) {
1258            OpenCms.getMemoryMonitor().cachePublishedResources(filter.getHistoryId().toString(), null);
1259        } else {
1260            OpenCms.getMemoryMonitor().flushCache(CmsMemoryMonitor.CacheType.PUBLISHED_RESOURCES);
1261        }
1262        return result;
1263    }
1264
1265    /**
1266     * @see org.opencms.main.I_CmsEventListener#cmsEvent(org.opencms.main.CmsEvent)
1267     */
1268    public void cmsEvent(CmsEvent event) {
1269
1270        if (LOG.isDebugEnabled()) {
1271            LOG.debug(Messages.get().getBundle().key(Messages.LOG_CMS_EVENT_1, Integer.valueOf(event.getType())));
1272        }
1273
1274        I_CmsReport report;
1275        CmsDbContext dbc;
1276
1277        switch (event.getType()) {
1278
1279            case I_CmsEventListener.EVENT_UPDATE_EXPORTS:
1280                dbc = (CmsDbContext)event.getData().get(I_CmsEventListener.KEY_DBCONTEXT);
1281                updateExportPoints(dbc);
1282                break;
1283
1284            case I_CmsEventListener.EVENT_PUBLISH_PROJECT:
1285                CmsUUID publishHistoryId = new CmsUUID((String)event.getData().get(I_CmsEventListener.KEY_PUBLISHID));
1286                report = (I_CmsReport)event.getData().get(I_CmsEventListener.KEY_REPORT);
1287                dbc = (CmsDbContext)event.getData().get(I_CmsEventListener.KEY_DBCONTEXT);
1288                m_monitor.clearCacheForPublishing();
1289                writeExportPoints(dbc, report, publishHistoryId);
1290                break;
1291
1292            case I_CmsEventListener.EVENT_CLEAR_CACHES:
1293                m_monitor.clearCache();
1294                m_skipTransferPrincipalResourceCache.invalidateAll();
1295                break;
1296            case I_CmsEventListener.EVENT_CLEAR_PRINCIPAL_CACHES:
1297                m_monitor.clearPrincipalsCache();
1298                break;
1299            case I_CmsEventListener.EVENT_USER_MODIFIED:
1300                String action = (String)event.getData().get(I_CmsEventListener.KEY_USER_ACTION);
1301                m_monitor.flushCache(
1302                    CacheType.USER,
1303                    CacheType.GROUP,
1304                    CacheType.ORG_UNIT,
1305                    CacheType.ACL,
1306                    CacheType.PERMISSION,
1307                    CacheType.USER_LIST);
1308                if (I_CmsEventListener.VALUE_USER_MODIFIED_ACTION_ADD_USER_TO_GROUP.equals(action)
1309                    || I_CmsEventListener.VALUE_USER_MODIFIED_ACTION_REMOVE_USER_FROM_GROUP.equals(action)
1310                    || I_CmsEventListener.VALUE_USER_MODIFIED_ACTION_SET_OU.equals(action)) {
1311
1312                    Object userIdObj = event.getData().get(I_CmsEventListener.KEY_USER_ID);
1313                    if (userIdObj != null) {
1314                        CmsUUID userId = null;
1315                        if (userIdObj instanceof CmsUUID) {
1316                            userId = (CmsUUID)userIdObj;
1317                        } else if (userIdObj instanceof String) {
1318                            try {
1319                                userId = new CmsUUID(userIdObj.toString());
1320                            } catch (Exception e) {
1321                                LOG.error(e.getLocalizedMessage(), e);
1322                            }
1323                        }
1324                        if (userId != null) {
1325                            m_monitor.flushUserGroups(userId);
1326                        }
1327                    } else {
1328                        m_monitor.flushCache(CacheType.USERGROUPS);
1329                    }
1330                    m_monitor.flushCache(CacheType.HAS_ROLE, CacheType.ROLE_LIST);
1331                }
1332                break;
1333            case I_CmsEventListener.EVENT_RESOURCE_MODIFIED:
1334
1335                Object resObj = event.getData().get(I_CmsEventListener.KEY_RESOURCE);
1336                if ((resObj != null) && (resObj instanceof CmsResource)) {
1337                    CmsResource resource = (CmsResource)resObj;
1338                    if (resource.getRootPath().startsWith(CmsUserDriver.ORGUNIT_BASE_FOLDER)) {
1339                        m_skipTransferPrincipalResourceCache.invalidateAll();
1340                    }
1341                }
1342                break;
1343            case I_CmsEventListener.EVENT_OU_MODIFIED:
1344                m_skipTransferPrincipalResourceCache.invalidateAll();
1345                break;
1346            default:
1347                // noop
1348        }
1349    }
1350
1351    /**
1352     * Copies the access control entries of a given resource to a destination resource.<p>
1353     *
1354     * Already existing access control entries of the destination resource are removed.<p>
1355     *
1356     * @param dbc the current database context
1357     * @param source the resource to copy the access control entries from
1358     * @param destination the resource to which the access control entries are copied
1359     * @param updateLastModifiedInfo if true, user and date "last modified" information on the target resource will be updated
1360     *
1361     * @throws CmsException if something goes wrong
1362     */
1363    public void copyAccessControlEntries(
1364        CmsDbContext dbc,
1365        CmsResource source,
1366        CmsResource destination,
1367        boolean updateLastModifiedInfo)
1368    throws CmsException {
1369
1370        // get the entries to copy
1371        ListIterator<CmsAccessControlEntry> aceList = getUserDriver(
1372            dbc).readAccessControlEntries(dbc, dbc.currentProject(), source.getResourceId(), false).listIterator();
1373
1374        // remove the current entries from the destination
1375        getUserDriver(dbc).removeAccessControlEntries(dbc, dbc.currentProject(), destination.getResourceId());
1376
1377        // now write the new entries
1378        while (aceList.hasNext()) {
1379            CmsAccessControlEntry ace = aceList.next();
1380            getUserDriver(dbc).createAccessControlEntry(
1381                dbc,
1382                dbc.currentProject(),
1383                destination.getResourceId(),
1384                ace.getPrincipal(),
1385                ace.getPermissions().getAllowedPermissions(),
1386                ace.getPermissions().getDeniedPermissions(),
1387                ace.getFlags());
1388        }
1389
1390        // log it
1391        log(
1392            dbc,
1393            new CmsLogEntry(
1394                dbc,
1395                destination.getStructureId(),
1396                CmsLogEntryType.RESOURCE_PERMISSIONS,
1397                new String[] {destination.getRootPath()}),
1398            false);
1399
1400        // update the "last modified" information
1401        if (updateLastModifiedInfo) {
1402            setDateLastModified(dbc, destination, destination.getDateLastModified());
1403        }
1404
1405        // clear the cache
1406        m_monitor.clearAccessControlListCache();
1407
1408        // fire a resource modification event
1409        Map<String, Object> data = new HashMap<String, Object>(2);
1410        data.put(I_CmsEventListener.KEY_RESOURCE, destination);
1411        data.put(I_CmsEventListener.KEY_CHANGE, Integer.valueOf(CHANGED_ACCESSCONTROL));
1412        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_MODIFIED, data));
1413    }
1414
1415    /**
1416     * Copies a resource.<p>
1417     *
1418     * You must ensure that the destination path is an absolute, valid and
1419     * existing VFS path. Relative paths from the source are currently not supported.<p>
1420     *
1421     * In case the target resource already exists, it is overwritten with the
1422     * source resource.<p>
1423     *
1424     * The <code>siblingMode</code> parameter controls how to handle siblings
1425     * during the copy operation.
1426     * Possible values for this parameter are:
1427     * <ul>
1428     * <li><code>{@link org.opencms.file.CmsResource#COPY_AS_NEW}</code></li>
1429     * <li><code>{@link org.opencms.file.CmsResource#COPY_AS_SIBLING}</code></li>
1430     * <li><code>{@link org.opencms.file.CmsResource#COPY_PRESERVE_SIBLING}</code></li>
1431     * </ul><p>
1432     *
1433     * @param dbc the current database context
1434     * @param source the resource to copy
1435     * @param destination the name of the copy destination with complete path
1436     * @param siblingMode indicates how to handle siblings during copy
1437     *
1438     * @throws CmsException if something goes wrong
1439     * @throws CmsIllegalArgumentException if the <code>source</code> argument is <code>null</code>
1440     *
1441     * @see CmsObject#copyResource(String, String, CmsResource.CmsResourceCopyMode)
1442     * @see I_CmsResourceType#copyResource(CmsObject, CmsSecurityManager, CmsResource, String, CmsResource.CmsResourceCopyMode)
1443     */
1444    public CmsResource copyResource(
1445        CmsDbContext dbc,
1446        CmsResource source,
1447        String destination,
1448        CmsResource.CmsResourceCopyMode siblingMode)
1449    throws CmsException, CmsIllegalArgumentException {
1450
1451        // check the sibling mode to see if this resource has to be copied as a sibling
1452        boolean copyAsSibling = false;
1453
1454        // siblings of folders are not supported
1455        if (!source.isFolder()) {
1456            // if the "copy as sibling" mode is used, set the flag to true
1457            if (siblingMode == CmsResource.COPY_AS_SIBLING) {
1458                copyAsSibling = true;
1459            }
1460            // if the mode is "preserve siblings", we have to check the sibling counter
1461            if (siblingMode == CmsResource.COPY_PRESERVE_SIBLING) {
1462                if (source.getSiblingCount() > 1) {
1463                    copyAsSibling = true;
1464                }
1465            }
1466        }
1467
1468        // read the source properties
1469        List<CmsProperty> properties = readPropertyObjects(dbc, source, false);
1470
1471        if (copyAsSibling) {
1472            // create a sibling of the source file at the destination
1473            return createSibling(dbc, source, destination, properties);
1474        }
1475
1476        // prepare the content if required
1477        byte[] content = null;
1478        if (source.isFile()) {
1479            if (source instanceof CmsFile) {
1480                // resource already is a file
1481                content = ((CmsFile)source).getContents();
1482            }
1483            if ((content == null) || (content.length < 1)) {
1484                // no known content yet - read from database
1485                content = getVfsDriver(dbc).readContent(dbc, dbc.currentProject().getUuid(), source.getResourceId());
1486            }
1487        }
1488
1489        // determine destination folder
1490        String destinationFoldername = CmsResource.getParentFolder(destination);
1491
1492        // read the destination folder (will also check read permissions)
1493        CmsFolder destinationFolder = m_securityManager.readFolder(
1494            dbc,
1495            destinationFoldername,
1496            CmsResourceFilter.IGNORE_EXPIRATION);
1497
1498        // no further permission check required here, will be done in createResource()
1499
1500        // set user and creation time stamps
1501        long currentTime = System.currentTimeMillis();
1502        long dateLastModified;
1503        CmsUUID userLastModified;
1504        if (source.isFolder()) {
1505            // folders always get a new date and user when they are copied
1506            dateLastModified = currentTime;
1507            userLastModified = dbc.currentUser().getId();
1508        } else {
1509            // files keep the date and user last modified from the source
1510            dateLastModified = source.getDateLastModified();
1511            userLastModified = source.getUserLastModified();
1512        }
1513
1514        // check the resource flags
1515        int flags = source.getFlags();
1516        if (source.isLabeled()) {
1517            // reset "labeled" link flag for new resource
1518            flags &= ~CmsResource.FLAG_LABELED;
1519        }
1520
1521        // create the new resource
1522        CmsResource newResource = new CmsResource(
1523            new CmsUUID(),
1524            new CmsUUID(),
1525            destination,
1526            source.getTypeId(),
1527            source.isFolder(),
1528            flags,
1529            dbc.currentProject().getUuid(),
1530            CmsResource.STATE_NEW,
1531            currentTime,
1532            dbc.currentUser().getId(),
1533            dateLastModified,
1534            userLastModified,
1535            source.getDateReleased(),
1536            source.getDateExpired(),
1537            1,
1538            source.getLength(),
1539            source.getDateContent(),
1540            source.getVersion()); // version number does not matter since it will be computed later
1541
1542        // trigger "is touched" state on resource (will ensure modification date is kept unchanged)
1543        newResource.setDateLastModified(dateLastModified);
1544
1545        // log it
1546        log(
1547            dbc,
1548            new CmsLogEntry(
1549                dbc,
1550                newResource.getStructureId(),
1551                CmsLogEntryType.RESOURCE_COPIED,
1552                new String[] {newResource.getRootPath()}),
1553            false);
1554
1555        // create the resource
1556        newResource = createResource(dbc, destination, newResource, content, properties, false);
1557        // copy relations
1558        copyRelations(dbc, source, newResource);
1559
1560        // copy the access control entries to the created resource
1561        copyAccessControlEntries(dbc, source, newResource, false);
1562
1563        // clear the cache
1564        m_monitor.clearAccessControlListCache();
1565
1566        List<CmsResource> modifiedResources = new ArrayList<CmsResource>();
1567        modifiedResources.add(source);
1568        modifiedResources.add(newResource);
1569        modifiedResources.add(destinationFolder);
1570        OpenCms.fireCmsEvent(
1571            new CmsEvent(
1572                I_CmsEventListener.EVENT_RESOURCE_COPIED,
1573                Collections.<String, Object> singletonMap(I_CmsEventListener.KEY_RESOURCES, modifiedResources)));
1574        return newResource;
1575    }
1576
1577    /**
1578     * Copies a resource to the current project of the user.<p>
1579     *
1580     * @param dbc the current database context
1581     * @param resource the resource to apply this operation to
1582     *
1583     * @throws CmsException if something goes wrong
1584     *
1585     * @see CmsObject#copyResourceToProject(String)
1586     * @see I_CmsResourceType#copyResourceToProject(CmsObject, CmsSecurityManager, CmsResource)
1587     */
1588    public void copyResourceToProject(CmsDbContext dbc, CmsResource resource) throws CmsException {
1589
1590        // copy the resource to the project only if the resource is not already in the project
1591        if (!isInsideCurrentProject(dbc, resource.getRootPath())) {
1592            // check if there are already any subfolders of this resource
1593            I_CmsProjectDriver projectDriver = getProjectDriver(dbc);
1594            if (resource.isFolder()) {
1595                List<String> projectResources = projectDriver.readProjectResources(dbc, dbc.currentProject());
1596                for (int i = 0; i < projectResources.size(); i++) {
1597                    String resname = projectResources.get(i);
1598                    if (resname.startsWith(resource.getRootPath())) {
1599                        // delete the existing project resource first
1600                        projectDriver.deleteProjectResource(dbc, dbc.currentProject().getUuid(), resname);
1601                    }
1602                }
1603            }
1604            try {
1605                projectDriver.createProjectResource(dbc, dbc.currentProject().getUuid(), resource.getRootPath());
1606            } catch (CmsException exc) {
1607                // if the subfolder exists already - all is ok
1608            } finally {
1609                m_monitor.flushCache(CmsMemoryMonitor.CacheType.PROJECT_RESOURCES);
1610
1611                OpenCms.fireCmsEvent(
1612                    new CmsEvent(
1613                        I_CmsEventListener.EVENT_PROJECT_MODIFIED,
1614                        Collections.<String, Object> singletonMap("project", dbc.currentProject())));
1615            }
1616        }
1617    }
1618
1619    /**
1620     * Counts the locked resources in this project.<p>
1621     *
1622     * @param project the project to count the locked resources in
1623     *
1624     * @return the amount of locked resources in this project
1625     */
1626    public int countLockedResources(CmsProject project) {
1627
1628        // count locks
1629        return m_lockManager.countExclusiveLocksInProject(project);
1630    }
1631
1632    /**
1633     * Add a new group to the Cms.<p>
1634     *
1635     * Only the admin can do this.
1636     * Only users, which are in the group "administrators" are granted.<p>
1637     *
1638     * @param dbc the current database context
1639     * @param id the id of the new group
1640     * @param name the name of the new group
1641     * @param description the description for the new group
1642     * @param flags the flags for the new group
1643     * @param parent the name of the parent group (or <code>null</code>)
1644     *
1645     * @return new created group
1646     *
1647     * @throws CmsException if the creation of the group failed
1648     * @throws CmsIllegalArgumentException if the length of the given name was below 1
1649     */
1650    public CmsGroup createGroup(CmsDbContext dbc, CmsUUID id, String name, String description, int flags, String parent)
1651    throws CmsIllegalArgumentException, CmsException {
1652
1653        // check the group name
1654        OpenCms.getValidationHandler().checkGroupName(CmsOrganizationalUnit.getSimpleName(name));
1655        // trim the name
1656        name = name.trim();
1657
1658        // check the OU
1659        readOrganizationalUnit(dbc, CmsOrganizationalUnit.getParentFqn(name));
1660
1661        // get the id of the parent group if necessary
1662        if (CmsStringUtil.isNotEmpty(parent)) {
1663            CmsGroup parentGroup = readGroup(dbc, parent);
1664            if (!parentGroup.isRole()
1665                && !CmsOrganizationalUnit.getParentFqn(parent).equals(CmsOrganizationalUnit.getParentFqn(name))) {
1666                throw new CmsDataAccessException(
1667                    Messages.get().container(
1668                        Messages.ERR_PARENT_GROUP_MUST_BE_IN_SAME_OU_3,
1669                        CmsOrganizationalUnit.getSimpleName(name),
1670                        CmsOrganizationalUnit.getParentFqn(name),
1671                        parent));
1672            }
1673        }
1674
1675        // create the group
1676        CmsGroup group = getUserDriver(dbc).createGroup(dbc, id, name, description, flags, parent);
1677
1678        // if the group is in fact a role, initialize it
1679        if (group.isVirtual()) {
1680            // get all users that have the given role
1681            String groupname = CmsRole.valueOf(group).getGroupName();
1682            Iterator<CmsUser> it = getUsersOfGroup(dbc, groupname, true, false, true).iterator();
1683            while (it.hasNext()) {
1684                CmsUser user = it.next();
1685                // put them in the new group
1686                addUserToGroup(dbc, user.getName(), group.getName(), true);
1687            }
1688        }
1689
1690        // put it into the cache
1691        m_monitor.cacheGroup(group);
1692
1693        if (!dbc.getProjectId().isNullUUID()) {
1694            // group modified event is not needed
1695            return group;
1696        }
1697        // fire group modified event
1698        Map<String, Object> eventData = new HashMap<String, Object>();
1699        eventData.put(I_CmsEventListener.KEY_GROUP_NAME, group.getName());
1700        eventData.put(I_CmsEventListener.KEY_GROUP_ID, group.getId().toString());
1701        eventData.put(I_CmsEventListener.KEY_USER_ACTION, I_CmsEventListener.VALUE_GROUP_MODIFIED_ACTION_CREATE);
1702        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_GROUP_MODIFIED, eventData));
1703
1704        // return it
1705        return group;
1706    }
1707
1708    /**
1709     * Creates a new organizational unit.<p>
1710     *
1711     * @param dbc the current db context
1712     * @param ouFqn the fully qualified name of the new organizational unit
1713     * @param description the description of the new organizational unit
1714     * @param flags the flags for the new organizational unit
1715     * @param resource the first associated resource
1716     *
1717     * @return a <code>{@link CmsOrganizationalUnit}</code> object representing
1718     *          the newly created organizational unit
1719     *
1720     * @throws CmsException if operation was not successful
1721     *
1722     * @see org.opencms.security.CmsOrgUnitManager#createOrganizationalUnit(CmsObject, String, String, int, String)
1723     */
1724    public CmsOrganizationalUnit createOrganizationalUnit(
1725        CmsDbContext dbc,
1726        String ouFqn,
1727        String description,
1728        int flags,
1729        CmsResource resource)
1730    throws CmsException {
1731
1732        // normal case
1733        CmsOrganizationalUnit parent = readOrganizationalUnit(dbc, CmsOrganizationalUnit.getParentFqn(ouFqn));
1734        String name = CmsOrganizationalUnit.getSimpleName(ouFqn);
1735        if (name.endsWith(CmsOrganizationalUnit.SEPARATOR)) {
1736            name = name.substring(0, name.length() - 1);
1737        }
1738
1739        // check the name
1740        CmsResource.checkResourceName(name);
1741
1742        // trim the name
1743        name = name.trim();
1744
1745        // check the description
1746        if (CmsStringUtil.isEmptyOrWhitespaceOnly(description)) {
1747            throw new CmsIllegalArgumentException(Messages.get().container(Messages.ERR_BAD_OU_DESCRIPTION_EMPTY_0));
1748        }
1749
1750        // create the organizational unit
1751        CmsOrganizationalUnit orgUnit = getUserDriver(dbc).createOrganizationalUnit(
1752            dbc,
1753            name,
1754            description,
1755            flags,
1756            parent,
1757            resource != null ? resource.getRootPath() : null);
1758        // put the new created org unit into the cache
1759        m_monitor.cacheOrgUnit(orgUnit);
1760
1761        // flush relevant caches
1762        m_monitor.clearPrincipalsCache();
1763        m_monitor.flushCache(CmsMemoryMonitor.CacheType.PROPERTY, CmsMemoryMonitor.CacheType.PROPERTY_LIST);
1764
1765        // create a publish list for the 'virtual' publish event
1766        CmsResource ouRes = readResource(
1767            dbc,
1768            CmsUserDriver.ORGUNIT_BASE_FOLDER + orgUnit.getName(),
1769            CmsResourceFilter.DEFAULT);
1770        CmsPublishList pl = new CmsPublishList(ouRes, false);
1771        pl.add(ouRes, false);
1772
1773        getProjectDriver(dbc).writePublishHistory(
1774            dbc,
1775            pl.getPublishHistoryId(),
1776            new CmsPublishedResource(ouRes, -1, CmsResourceState.STATE_NEW));
1777
1778        // fire the 'virtual' publish event
1779        Map<String, Object> eventData = new HashMap<String, Object>();
1780        eventData.put(I_CmsEventListener.KEY_PUBLISHID, pl.getPublishHistoryId().toString());
1781        eventData.put(I_CmsEventListener.KEY_PROJECTID, dbc.currentProject().getUuid());
1782        eventData.put(I_CmsEventListener.KEY_DBCONTEXT, dbc);
1783        CmsEvent afterPublishEvent = new CmsEvent(I_CmsEventListener.EVENT_PUBLISH_PROJECT, eventData);
1784        OpenCms.fireCmsEvent(afterPublishEvent);
1785
1786        if (!dbc.getProjectId().isNullUUID()) {
1787            // OU modified event is not needed
1788            return orgUnit;
1789        }
1790
1791        // fire OU modified event
1792        Map<String, Object> event2Data = new HashMap<String, Object>();
1793        event2Data.put(I_CmsEventListener.KEY_OU_NAME, orgUnit.getName());
1794        event2Data.put(I_CmsEventListener.KEY_OU_ID, orgUnit.getId().toString());
1795        event2Data.put(I_CmsEventListener.KEY_USER_ACTION, I_CmsEventListener.VALUE_OU_MODIFIED_ACTION_CREATE);
1796        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_OU_MODIFIED, event2Data));
1797
1798        // return it
1799        return orgUnit;
1800    }
1801
1802    /**
1803     * Creates a project.<p>
1804     *
1805     * @param dbc the current database context
1806     * @param name the name of the project to create
1807     * @param description the description of the project
1808     * @param groupname the project user group to be set
1809     * @param managergroupname the project manager group to be set
1810     * @param projecttype the type of the project
1811     *
1812     * @return the created project
1813     *
1814     * @throws CmsIllegalArgumentException if the chosen <code>name</code> is already used
1815     *         by the online project, or if the name is not valid
1816     * @throws CmsException if something goes wrong
1817     */
1818    public CmsProject createProject(
1819        CmsDbContext dbc,
1820        String name,
1821        String description,
1822        String groupname,
1823        String managergroupname,
1824        CmsProject.CmsProjectType projecttype)
1825    throws CmsIllegalArgumentException, CmsException {
1826
1827        if (CmsProject.ONLINE_PROJECT_NAME.equals(name)) {
1828            throw new CmsIllegalArgumentException(
1829                Messages.get().container(
1830                    Messages.ERR_CREATE_PROJECT_ONLINE_PROJECT_NAME_1,
1831                    CmsProject.ONLINE_PROJECT_NAME));
1832        }
1833        // check the name
1834        CmsProject.checkProjectName(CmsOrganizationalUnit.getSimpleName(name));
1835        // check the ou
1836        readOrganizationalUnit(dbc, CmsOrganizationalUnit.getParentFqn(name));
1837        // read the needed groups from the cms
1838        CmsGroup group = readGroup(dbc, groupname);
1839        CmsGroup managergroup = readGroup(dbc, managergroupname);
1840
1841        return getProjectDriver(dbc).createProject(
1842            dbc,
1843            new CmsUUID(),
1844            dbc.currentUser(),
1845            group,
1846            managergroup,
1847            name,
1848            description,
1849            projecttype.getDefaultFlags(),
1850            projecttype);
1851    }
1852
1853    /**
1854     * Creates a property definition.<p>
1855     *
1856     * Property definitions are valid for all resource types.<p>
1857     *
1858     * @param dbc the current database context
1859     * @param name the name of the property definition to create
1860     *
1861     * @return the created property definition
1862     *
1863     * @throws CmsException if something goes wrong
1864     */
1865    public CmsPropertyDefinition createPropertyDefinition(CmsDbContext dbc, String name) throws CmsException {
1866
1867        CmsPropertyDefinition propertyDefinition = null;
1868
1869        name = name.trim();
1870        // validate the property name
1871        CmsPropertyDefinition.checkPropertyName(name);
1872        // TODO: make the type a parameter
1873        try {
1874            try {
1875                propertyDefinition = getVfsDriver(dbc).readPropertyDefinition(
1876                    dbc,
1877                    name,
1878                    dbc.currentProject().getUuid());
1879            } catch (CmsException e) {
1880                propertyDefinition = getVfsDriver(dbc).createPropertyDefinition(
1881                    dbc,
1882                    dbc.currentProject().getUuid(),
1883                    name,
1884                    CmsPropertyDefinition.TYPE_NORMAL);
1885            }
1886
1887            try {
1888                getVfsDriver(dbc).readPropertyDefinition(dbc, name, CmsProject.ONLINE_PROJECT_ID);
1889            } catch (CmsException e) {
1890                getVfsDriver(dbc).createPropertyDefinition(
1891                    dbc,
1892                    CmsProject.ONLINE_PROJECT_ID,
1893                    name,
1894                    CmsPropertyDefinition.TYPE_NORMAL);
1895            }
1896
1897            try {
1898                getHistoryDriver(dbc).readPropertyDefinition(dbc, name);
1899            } catch (CmsException e) {
1900                getHistoryDriver(dbc).createPropertyDefinition(dbc, name, CmsPropertyDefinition.TYPE_NORMAL);
1901            }
1902        } finally {
1903
1904            // fire an event that a property of a resource has been deleted
1905            OpenCms.fireCmsEvent(
1906                new CmsEvent(
1907                    I_CmsEventListener.EVENT_PROPERTY_DEFINITION_CREATED,
1908                    Collections.<String, Object> singletonMap("propertyDefinition", propertyDefinition)));
1909
1910        }
1911
1912        return propertyDefinition;
1913    }
1914
1915    /**
1916     * Creates a new publish job.<p>
1917     *
1918     * @param dbc the current database context
1919     * @param publishJob the publish job to create
1920     *
1921     * @throws CmsException if something goes wrong
1922     */
1923    public void createPublishJob(CmsDbContext dbc, CmsPublishJobInfoBean publishJob) throws CmsException {
1924
1925        getProjectDriver(dbc).createPublishJob(dbc, publishJob);
1926    }
1927
1928    /**
1929     * Creates a new resource with the provided content and properties.<p>
1930     *
1931     * The <code>content</code> parameter may be <code>null</code> if the resource id
1932     * already exists. If so, the created resource will be a sibling of the existing
1933     * resource, the existing content will remain unchanged.<p>
1934     *
1935     * This is used during file import for import of siblings as the
1936     * <code>manifest.xml</code> only contains one binary copy per file.<p>
1937     *
1938     * If the resource id exists but the <code>content</code> is not <code>null</code>,
1939     * the created resource will be made a sibling of the existing resource,
1940     * and both will share the new content.<p>
1941     *
1942     * @param dbc the current database context
1943     * @param resourcePath the name of the resource to create (full path)
1944     * @param resource the new resource to create
1945     * @param content the content for the new resource
1946     * @param properties the properties for the new resource
1947     * @param importCase if <code>true</code>, signals that this operation is done while
1948     *                      importing resource, causing different lock behavior and
1949     *                      potential "lost and found" usage
1950     *
1951     * @return the created resource
1952     *
1953     * @throws CmsException if something goes wrong
1954     */
1955    public CmsResource createResource(
1956        CmsDbContext dbc,
1957        String resourcePath,
1958        CmsResource resource,
1959        byte[] content,
1960        List<CmsProperty> properties,
1961        boolean importCase)
1962    throws CmsException {
1963
1964        CmsResource newResource = null;
1965        if (resource.isFolder()) {
1966            resourcePath = CmsFileUtil.addTrailingSeparator(resourcePath);
1967        }
1968
1969        try {
1970            synchronized (this) {
1971                // need to provide the parent folder id for resource creation
1972                String parentFolderName = CmsResource.getParentFolder(resourcePath);
1973                CmsResource parentFolder = readFolder(dbc, parentFolderName, CmsResourceFilter.IGNORE_EXPIRATION);
1974
1975                CmsLock parentLock = getLock(dbc, parentFolder);
1976                // it is not allowed to create a resource in a folder locked by other user
1977                if (!parentLock.isUnlocked() && !parentLock.isOwnedBy(dbc.currentUser())) {
1978                    // one exception is if the admin user tries to create a temporary resource
1979                    if (!CmsResource.getName(resourcePath).startsWith(TEMP_FILE_PREFIX)
1980                        || !m_securityManager.hasRole(dbc, dbc.currentUser(), CmsRole.ROOT_ADMIN)) {
1981                        throw new CmsLockException(
1982                            Messages.get().container(
1983                                Messages.ERR_CREATE_RESOURCE_PARENT_LOCK_1,
1984                                dbc.removeSiteRoot(resourcePath)));
1985                    }
1986                }
1987                if (CmsResourceTypeJsp.isJsp(resource)) {
1988                    // security check when trying to create a new jsp file
1989                    m_securityManager.checkRoleForResource(dbc, CmsRole.VFS_MANAGER, parentFolder);
1990                }
1991
1992                // check import configuration of "lost and found" folder
1993                boolean useLostAndFound = importCase && !OpenCms.getImportExportManager().overwriteCollidingResources();
1994
1995                // check if the resource already exists by name
1996                CmsResource currentResourceByName = null;
1997                try {
1998                    currentResourceByName = readResource(dbc, resourcePath, CmsResourceFilter.ALL);
1999                } catch (CmsVfsResourceNotFoundException e) {
2000                    // if the resource does exist, we have to check the id later to decide what to do
2001                }
2002
2003                // check if the resource already exists by id
2004                try {
2005                    CmsResource currentResourceById = readResource(
2006                        dbc,
2007                        resource.getStructureId(),
2008                        CmsResourceFilter.ALL);
2009                    // it is not allowed to import resources when there is already a resource with the same id but different path
2010                    if (!currentResourceById.getRootPath().equals(resourcePath)) {
2011                        throw new CmsVfsResourceAlreadyExistsException(
2012                            Messages.get().container(
2013                                Messages.ERR_RESOURCE_WITH_ID_ALREADY_EXISTS_3,
2014                                dbc.removeSiteRoot(resourcePath),
2015                                dbc.removeSiteRoot(currentResourceById.getRootPath()),
2016                                currentResourceById.getStructureId()));
2017                    }
2018                } catch (CmsVfsResourceNotFoundException e) {
2019                    // if the resource does exist, we have to check the id later to decide what to do
2020                }
2021
2022                // check the permissions
2023                if (currentResourceByName == null) {
2024                    // resource does not exist - check parent folder
2025                    m_securityManager.checkPermissions(
2026                        dbc,
2027                        parentFolder,
2028                        CmsPermissionSet.ACCESS_WRITE,
2029                        false,
2030                        CmsResourceFilter.IGNORE_EXPIRATION);
2031                } else {
2032                    // resource already exists - check existing resource
2033                    m_securityManager.checkPermissions(
2034                        dbc,
2035                        currentResourceByName,
2036                        CmsPermissionSet.ACCESS_WRITE,
2037                        !importCase,
2038                        CmsResourceFilter.ALL);
2039                }
2040
2041                // now look for the resource by name
2042                if (currentResourceByName != null) {
2043                    boolean overwrite = true;
2044                    if (currentResourceByName.getState().isDeleted()) {
2045                        if (!currentResourceByName.isFolder()) {
2046                            // if a non-folder resource was deleted it's treated like a new resource
2047                            overwrite = false;
2048                        }
2049                    } else {
2050                        if (!importCase) {
2051                            // direct "overwrite" of a resource is possible only during import,
2052                            // or if the resource has been deleted
2053                            throw new CmsVfsResourceAlreadyExistsException(
2054                                org.opencms.db.generic.Messages.get().container(
2055                                    org.opencms.db.generic.Messages.ERR_RESOURCE_WITH_NAME_ALREADY_EXISTS_1,
2056                                    dbc.removeSiteRoot(resource.getRootPath())));
2057                        }
2058                        // the resource already exists
2059                        if (!resource.isFolder()
2060                            && useLostAndFound
2061                            && (!currentResourceByName.getResourceId().equals(resource.getResourceId()))) {
2062                            // semantic change: the current resource is moved to L&F and the imported resource will overwrite the old one
2063                            // will leave the resource with state deleted,
2064                            // but it does not matter, since the state will be set later again
2065                            moveToLostAndFound(dbc, currentResourceByName, false);
2066                        }
2067                    }
2068                    if (!overwrite) {
2069                        // lock the resource, will throw an exception if not lockable
2070                        lockResource(dbc, currentResourceByName, CmsLockType.EXCLUSIVE);
2071
2072                        // trigger createResource instead of writeResource
2073                        currentResourceByName = null;
2074                    }
2075                }
2076                // if null, create new resource, if not null write resource
2077                CmsResource overwrittenResource = currentResourceByName;
2078
2079                // extract the name (without path)
2080                String targetName = CmsResource.getName(resourcePath);
2081
2082                int contentLength;
2083
2084                // modify target name and content length in case of folder creation
2085                if (resource.isFolder()) {
2086                    // folders never have any content
2087                    contentLength = -1;
2088                    // must cut of trailing '/' for folder creation (or name check fails)
2089                    if (CmsResource.isFolder(targetName)) {
2090                        targetName = targetName.substring(0, targetName.length() - 1);
2091                    }
2092                } else {
2093                    // otherwise ensure content and content length are set correctly
2094                    if (content != null) {
2095                        // if a content is provided, in each case the length is the length of this content
2096                        contentLength = content.length;
2097                    } else if (overwrittenResource != null) {
2098                        // we have no content, but an already existing resource - length remains unchanged
2099                        contentLength = overwrittenResource.getLength();
2100                    } else {
2101                        // we have no content - length is used as set in the resource
2102                        contentLength = resource.getLength();
2103                    }
2104                }
2105
2106                // check if the target name is valid (forbidden chars etc.),
2107                // if not throw an exception
2108                // must do this here since targetName is modified in folder case (see above)
2109                CmsResource.checkResourceName(targetName);
2110
2111                // set structure and resource ids as given
2112                CmsUUID structureId = resource.getStructureId();
2113                CmsUUID resourceId = resource.getResourceId();
2114
2115                // decide which structure id to use
2116                if (overwrittenResource != null) {
2117                    // resource exists, re-use existing ids
2118                    structureId = overwrittenResource.getStructureId();
2119                }
2120                if (structureId.isNullUUID()) {
2121                    // need a new structure id
2122                    structureId = new CmsUUID();
2123                }
2124
2125                // decide which resource id to use
2126                if (overwrittenResource != null) {
2127                    // if we are overwriting we have to assure the resource id is the same
2128                    resourceId = overwrittenResource.getResourceId();
2129                }
2130                if (resourceId.isNullUUID()) {
2131                    // need a new resource id
2132                    resourceId = new CmsUUID();
2133                }
2134
2135                try {
2136                    // check online resource
2137                    CmsResource onlineResource = getVfsDriver(
2138                        dbc).readResource(dbc, CmsProject.ONLINE_PROJECT_ID, resourcePath, true);
2139                    // only allow to overwrite with different id if importing (createResource will set the right id)
2140                    try {
2141                        CmsResource offlineResource = getVfsDriver(dbc).readResource(
2142                            dbc,
2143                            dbc.currentProject().getUuid(),
2144                            onlineResource.getStructureId(),
2145                            true);
2146                        if (!offlineResource.getRootPath().equals(onlineResource.getRootPath())) {
2147                            throw new CmsVfsOnlineResourceAlreadyExistsException(
2148                                Messages.get().container(
2149                                    Messages.ERR_ONLINE_RESOURCE_EXISTS_2,
2150                                    dbc.removeSiteRoot(resourcePath),
2151                                    dbc.removeSiteRoot(offlineResource.getRootPath())));
2152                        }
2153                    } catch (CmsVfsResourceNotFoundException e) {
2154                        // there is no problem for now
2155                        // but should never happen
2156                        if (LOG.isErrorEnabled()) {
2157                            LOG.error(e.getLocalizedMessage(), e);
2158                        }
2159                    }
2160                } catch (CmsVfsResourceNotFoundException e) {
2161                    // ok, there is no online entry to worry about
2162                }
2163
2164                // now create a resource object with all informations
2165                newResource = new CmsResource(
2166                    structureId,
2167                    resourceId,
2168                    resourcePath,
2169                    resource.getTypeId(),
2170                    resource.isFolder(),
2171                    resource.getFlags(),
2172                    dbc.currentProject().getUuid(),
2173                    resource.getState(),
2174                    resource.getDateCreated(),
2175                    resource.getUserCreated(),
2176                    resource.getDateLastModified(),
2177                    resource.getUserLastModified(),
2178                    resource.getDateReleased(),
2179                    resource.getDateExpired(),
2180                    1,
2181                    contentLength,
2182                    resource.getDateContent(),
2183                    resource.getVersion()); // version number does not matter since it will be computed later
2184
2185                // ensure date is updated only if required
2186                if (resource.isTouched()) {
2187                    // this will trigger the internal "is touched" state on the new resource
2188                    newResource.setDateLastModified(resource.getDateLastModified());
2189                }
2190
2191                if (resource.isFile()) {
2192                    // check if a sibling to the imported resource lies in a marked site
2193                    if (labelResource(dbc, resource, resourcePath, 2)) {
2194                        int flags = resource.getFlags();
2195                        flags |= CmsResource.FLAG_LABELED;
2196                        resource.setFlags(flags);
2197                    }
2198                    // ensure siblings don't overwrite existing resource records
2199                    if (content == null) {
2200                        newResource.setState(CmsResource.STATE_KEEP);
2201                    }
2202                }
2203
2204                // delete all relations for the resource, before writing the content
2205                getVfsDriver(
2206                    dbc).deleteRelations(dbc, dbc.currentProject().getUuid(), newResource, CmsRelationFilter.TARGETS);
2207                if (overwrittenResource == null) {
2208                    CmsLock lock = getLock(dbc, newResource);
2209                    if (lock.getEditionLock().isExclusive()) {
2210                        unlockResource(dbc, newResource, true, false);
2211                    }
2212                    // resource does not exist.
2213                    newResource = getVfsDriver(
2214                        dbc).createResource(dbc, dbc.currentProject().getUuid(), newResource, content);
2215                } else {
2216                    // resource already exists.
2217                    // probably the resource is a merged page file that gets overwritten during import, or it gets
2218                    // overwritten by a copy operation. if so, the structure & resource state are not modified to changed.
2219                    int updateStates = (overwrittenResource.getState().isNew()
2220                    ? CmsDriverManager.NOTHING_CHANGED
2221                    : CmsDriverManager.UPDATE_ALL);
2222                    getVfsDriver(dbc).writeResource(dbc, dbc.currentProject().getUuid(), newResource, updateStates);
2223
2224                    if ((content != null) && resource.isFile()) {
2225                        // also update file content if required
2226                        getVfsDriver(dbc).writeContent(dbc, newResource.getResourceId(), content);
2227                    }
2228                }
2229
2230                // write the properties (internal operation, no events or duplicate permission checks)
2231                writePropertyObjects(dbc, newResource, properties, false);
2232
2233                // lock the created resource
2234                try {
2235                    // if it is locked by another user (copied or moved resource) this lock should be preserved and
2236                    // the exception is OK: locks on created resources are a slave feature to original locks
2237                    lockResource(dbc, newResource, CmsLockType.EXCLUSIVE);
2238                } catch (CmsLockException cle) {
2239                    if (LOG.isDebugEnabled()) {
2240                        LOG.debug(
2241                            Messages.get().getBundle().key(
2242                                Messages.ERR_CREATE_RESOURCE_LOCK_1,
2243                                new Object[] {dbc.removeSiteRoot(newResource.getRootPath())}));
2244                    }
2245                }
2246
2247                if (!importCase) {
2248                    log(
2249                        dbc,
2250                        new CmsLogEntry(
2251                            dbc,
2252                            newResource.getStructureId(),
2253                            CmsLogEntryType.RESOURCE_CREATED,
2254                            new String[] {resource.getRootPath()}),
2255                        false);
2256                } else {
2257                    log(
2258                        dbc,
2259                        new CmsLogEntry(
2260                            dbc,
2261                            newResource.getStructureId(),
2262                            CmsLogEntryType.RESOURCE_IMPORTED,
2263                            new String[] {resource.getRootPath()}),
2264                        false);
2265                }
2266            }
2267        } finally {
2268            // clear the internal caches
2269            m_monitor.clearAccessControlListCache();
2270            m_monitor.flushCache(CmsMemoryMonitor.CacheType.PROPERTY, CmsMemoryMonitor.CacheType.PROPERTY_LIST);
2271
2272            if (newResource != null) {
2273                // fire an event that a new resource has been created
2274                OpenCms.fireCmsEvent(
2275                    new CmsEvent(
2276                        I_CmsEventListener.EVENT_RESOURCE_CREATED,
2277                        Collections.<String, Object> singletonMap(I_CmsEventListener.KEY_RESOURCE, newResource)));
2278            }
2279        }
2280        return newResource;
2281    }
2282
2283    /**
2284     * Creates a new resource of the given resource type
2285     * with the provided content and properties.<p>
2286     *
2287     * If the provided content is null and the resource is not a folder,
2288     * the content will be set to an empty byte array.<p>
2289     *
2290     * @param dbc the current database context
2291     * @param resourcename the name of the resource to create (full path)
2292     * @param type the type of the resource to create
2293     * @param content the content for the new resource
2294     * @param properties the properties for the new resource
2295     *
2296     * @return the created resource
2297     *
2298     * @throws CmsException if something goes wrong
2299     * @throws CmsIllegalArgumentException if the <code>resourcename</code> argument is null or of length 0
2300     *
2301     * @see CmsObject#createResource(String, int, byte[], List)
2302     * @see CmsObject#createResource(String, int)
2303     * @see I_CmsResourceType#createResource(CmsObject, CmsSecurityManager, String, byte[], List)
2304     */
2305    @SuppressWarnings("javadoc")
2306    public CmsResource createResource(
2307        CmsDbContext dbc,
2308        String resourcename,
2309        int type,
2310        byte[] content,
2311        List<CmsProperty> properties)
2312    throws CmsException, CmsIllegalArgumentException {
2313
2314        String targetName = resourcename;
2315
2316        if (content == null) {
2317            // name based resource creation MUST have a content
2318            content = new byte[0];
2319        }
2320        int size;
2321
2322        if (CmsFolder.isFolderType(type)) {
2323            // must cut of trailing '/' for folder creation
2324            if (CmsResource.isFolder(targetName)) {
2325                targetName = targetName.substring(0, targetName.length() - 1);
2326            }
2327            size = -1;
2328        } else {
2329            size = content.length;
2330        }
2331
2332        // create a new resource
2333        CmsResource newResource = new CmsResource(
2334            CmsUUID.getNullUUID(), // uuids will be "corrected" later
2335            CmsUUID.getNullUUID(),
2336            targetName,
2337            type,
2338            CmsFolder.isFolderType(type),
2339            0,
2340            dbc.currentProject().getUuid(),
2341            CmsResource.STATE_NEW,
2342            0,
2343            dbc.currentUser().getId(),
2344            0,
2345            dbc.currentUser().getId(),
2346            CmsResource.DATE_RELEASED_DEFAULT,
2347            CmsResource.DATE_EXPIRED_DEFAULT,
2348            1,
2349            size,
2350            0, // version number does not matter since it will be computed later
2351            0); // content time will be corrected later
2352
2353        return createResource(dbc, targetName, newResource, content, properties, false);
2354    }
2355
2356    /**
2357     * Creates a new sibling of the source resource.<p>
2358     *
2359     * @param dbc the current database context
2360     * @param source the resource to create a sibling for
2361     * @param destination the name of the sibling to create with complete path
2362     * @param properties the individual properties for the new sibling
2363     *
2364     * @return the new created sibling
2365     *
2366     * @throws CmsException if something goes wrong
2367     *
2368     * @see CmsObject#createSibling(String, String, List)
2369     * @see I_CmsResourceType#createSibling(CmsObject, CmsSecurityManager, CmsResource, String, List)
2370     */
2371    public CmsResource createSibling(
2372        CmsDbContext dbc,
2373        CmsResource source,
2374        String destination,
2375        List<CmsProperty> properties)
2376    throws CmsException {
2377
2378        if (source.isFolder()) {
2379            throw new CmsVfsException(Messages.get().container(Messages.ERR_VFS_FOLDERS_DONT_SUPPORT_SIBLINGS_0));
2380        }
2381
2382        // determine destination folder and resource name
2383        String destinationFoldername = CmsResource.getParentFolder(destination);
2384
2385        // read the destination folder (will also check read permissions)
2386        CmsFolder destinationFolder = readFolder(dbc, destinationFoldername, CmsResourceFilter.IGNORE_EXPIRATION);
2387
2388        // no further permission check required here, will be done in createResource()
2389
2390        // check the resource flags
2391        int flags = source.getFlags();
2392        if (labelResource(dbc, source, destination, 1)) {
2393            // set "labeled" link flag for new resource
2394            flags |= CmsResource.FLAG_LABELED;
2395        }
2396
2397        // create the new resource
2398        CmsResource newResource = new CmsResource(
2399            new CmsUUID(),
2400            source.getResourceId(),
2401            destination,
2402            source.getTypeId(),
2403            source.isFolder(),
2404            flags,
2405            dbc.currentProject().getUuid(),
2406            CmsResource.STATE_KEEP,
2407            source.getDateCreated(), // ensures current resource record remains untouched
2408            source.getUserCreated(),
2409            source.getDateLastModified(),
2410            source.getUserLastModified(),
2411            source.getDateReleased(),
2412            source.getDateExpired(),
2413            source.getSiblingCount() + 1,
2414            source.getLength(),
2415            source.getDateContent(),
2416            source.getVersion()); // version number does not matter since it will be computed later
2417
2418        // trigger "is touched" state on resource (will ensure modification date is kept unchanged)
2419        newResource.setDateLastModified(newResource.getDateLastModified());
2420
2421        log(
2422            dbc,
2423            new CmsLogEntry(
2424                dbc,
2425                newResource.getStructureId(),
2426                CmsLogEntryType.RESOURCE_CLONED,
2427                new String[] {newResource.getRootPath()}),
2428            false);
2429        // create the resource (null content signals creation of sibling)
2430        newResource = createResource(dbc, destination, newResource, null, properties, false);
2431
2432        // copy relations
2433        copyRelations(dbc, source, newResource);
2434
2435        // clear the caches
2436        m_monitor.clearAccessControlListCache();
2437
2438        List<CmsResource> modifiedResources = new ArrayList<CmsResource>();
2439        modifiedResources.add(source);
2440        modifiedResources.add(newResource);
2441        modifiedResources.add(destinationFolder);
2442        Map<String, Object> eventData = new HashMap<>();
2443        eventData.put(I_CmsEventListener.KEY_RESOURCES, modifiedResources);
2444        eventData.put(I_CmsEventListener.KEY_CHANGE, I_CmsEventListener.VALUE_CREATE_SIBLING);
2445        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCES_AND_PROPERTIES_MODIFIED, eventData));
2446
2447        return newResource;
2448    }
2449
2450    /**
2451     * Creates the project for the temporary workplace files.<p>
2452     *
2453     * @param dbc the current database context
2454     *
2455     * @return the created project for the temporary workplace files
2456     *
2457     * @throws CmsException if something goes wrong
2458     */
2459    public CmsProject createTempfileProject(CmsDbContext dbc) throws CmsException {
2460
2461        // read the needed groups from the cms
2462        CmsGroup projectUserGroup = readGroup(dbc, dbc.currentProject().getGroupId());
2463        CmsGroup projectManagerGroup = readGroup(dbc, dbc.currentProject().getManagerGroupId());
2464
2465        CmsProject tempProject = getProjectDriver(dbc).createProject(
2466            dbc,
2467            new CmsUUID(),
2468            dbc.currentUser(),
2469            projectUserGroup,
2470            projectManagerGroup,
2471            I_CmsProjectDriver.TEMP_FILE_PROJECT_NAME,
2472            Messages.get().getBundle(dbc.getRequestContext().getLocale()).key(
2473                Messages.GUI_WORKPLACE_TEMPFILE_PROJECT_DESC_0),
2474            CmsProject.PROJECT_FLAG_HIDDEN,
2475            CmsProject.PROJECT_TYPE_NORMAL);
2476        getProjectDriver(dbc).createProjectResource(dbc, tempProject.getUuid(), "/");
2477
2478        OpenCms.fireCmsEvent(
2479            new CmsEvent(
2480                I_CmsEventListener.EVENT_PROJECT_MODIFIED,
2481                Collections.<String, Object> singletonMap("project", tempProject)));
2482
2483        return tempProject;
2484    }
2485
2486    /**
2487     * Creates a new user.<p>
2488     *
2489     * @param dbc the current database context
2490     * @param name the name for the new user
2491     * @param password the password for the new user
2492     * @param description the description for the new user
2493     * @param additionalInfos the additional infos for the user
2494     *
2495     * @return the created user
2496     *
2497     * @see CmsObject#createUser(String, String, String, Map)
2498     *
2499     * @throws CmsException if something goes wrong
2500     * @throws CmsIllegalArgumentException if the name for the user is not valid
2501     */
2502    public CmsUser createUser(
2503        CmsDbContext dbc,
2504        String name,
2505        String password,
2506        String description,
2507        Map<String, Object> additionalInfos)
2508    throws CmsException, CmsIllegalArgumentException {
2509
2510        // no space before or after the name
2511        name = name.trim();
2512        // check the user name
2513        String userName = CmsOrganizationalUnit.getSimpleName(name);
2514        OpenCms.getValidationHandler().checkUserName(userName);
2515        if (CmsStringUtil.isEmptyOrWhitespaceOnly(userName)) {
2516            throw new CmsIllegalArgumentException(Messages.get().container(Messages.ERR_BAD_USER_1, userName));
2517        }
2518        // check the ou
2519        CmsOrganizationalUnit ou = readOrganizationalUnit(dbc, CmsOrganizationalUnit.getParentFqn(name));
2520        // check the password
2521        validatePassword(password);
2522
2523        Map<String, Object> info = new HashMap<String, Object>();
2524        if (additionalInfos != null) {
2525            info.putAll(additionalInfos);
2526        }
2527        if (description != null) {
2528            info.put(CmsUserSettings.ADDITIONAL_INFO_DESCRIPTION, description);
2529        }
2530        int flags = 0;
2531        if (ou.hasFlagWebuser()) {
2532            flags += I_CmsPrincipal.FLAG_USER_WEBUSER;
2533        }
2534        CmsUser user = getUserDriver(dbc).createUser(
2535            dbc,
2536            new CmsUUID(),
2537            name,
2538            OpenCms.getPasswordHandler().digest(password),
2539            " ",
2540            " ",
2541            " ",
2542            0,
2543            I_CmsPrincipal.FLAG_ENABLED + flags,
2544            0,
2545            info);
2546
2547        if (!dbc.getProjectId().isNullUUID()) {
2548            // user modified event is not needed
2549            return user;
2550        }
2551        // fire user modified event
2552        Map<String, Object> eventData = new HashMap<String, Object>();
2553        eventData.put(I_CmsEventListener.KEY_USER_ID, user.getId().toString());
2554        eventData.put(I_CmsEventListener.KEY_USER_ACTION, I_CmsEventListener.VALUE_USER_MODIFIED_ACTION_CREATE_USER);
2555        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_USER_MODIFIED, eventData));
2556        return user;
2557    }
2558
2559    /**
2560     * Deletes aliases indicated by a filter.<p>
2561     *
2562     * @param dbc the current database context
2563     * @param project the current project
2564     * @param filter the filter which describes which aliases to delete
2565     *
2566     * @throws CmsException if something goes wrong
2567     */
2568    public void deleteAliases(CmsDbContext dbc, CmsProject project, CmsAliasFilter filter) throws CmsException {
2569
2570        I_CmsVfsDriver vfsDriver = getVfsDriver(dbc);
2571        vfsDriver.deleteAliases(dbc, project, filter);
2572    }
2573
2574    /**
2575     * Deletes all property values of a file or folder.<p>
2576     *
2577     * If there are no other siblings than the specified resource,
2578     * both the structure and resource property values get deleted.
2579     * If the specified resource has siblings, only the structure
2580     * property values get deleted.<p>
2581     *
2582     * @param dbc the current database context
2583     * @param resourcename the name of the resource for which all properties should be deleted
2584     *
2585     * @throws CmsException if operation was not successful
2586     */
2587    public void deleteAllProperties(CmsDbContext dbc, String resourcename) throws CmsException {
2588
2589        CmsResource resource = null;
2590        List<CmsResource> resources = new ArrayList<CmsResource>();
2591
2592        try {
2593            // read the resource
2594            resource = readResource(dbc, resourcename, CmsResourceFilter.IGNORE_EXPIRATION);
2595
2596            // check the security
2597            m_securityManager.checkPermissions(
2598                dbc,
2599                resource,
2600                CmsPermissionSet.ACCESS_WRITE,
2601                false,
2602                CmsResourceFilter.ALL);
2603
2604            // delete the property values
2605            if (resource.getSiblingCount() > 1) {
2606                // the resource has siblings- delete only the (structure) properties of this sibling
2607                getVfsDriver(dbc).deletePropertyObjects(
2608                    dbc,
2609                    dbc.currentProject().getUuid(),
2610                    resource,
2611                    CmsProperty.DELETE_OPTION_DELETE_STRUCTURE_VALUES);
2612                resources.addAll(readSiblings(dbc, resource, CmsResourceFilter.ALL));
2613
2614            } else {
2615                // the resource has no other siblings- delete all (structure+resource) properties
2616                getVfsDriver(dbc).deletePropertyObjects(
2617                    dbc,
2618                    dbc.currentProject().getUuid(),
2619                    resource,
2620                    CmsProperty.DELETE_OPTION_DELETE_STRUCTURE_AND_RESOURCE_VALUES);
2621                resources.add(resource);
2622            }
2623        } finally {
2624            // clear the driver manager cache
2625            m_monitor.flushCache(CmsMemoryMonitor.CacheType.PROPERTY, CmsMemoryMonitor.CacheType.PROPERTY_LIST);
2626
2627            // fire an event that all properties of a resource have been deleted
2628            OpenCms.fireCmsEvent(
2629                new CmsEvent(
2630                    I_CmsEventListener.EVENT_RESOURCES_AND_PROPERTIES_MODIFIED,
2631                    Collections.<String, Object> singletonMap(I_CmsEventListener.KEY_RESOURCES, resources)));
2632        }
2633    }
2634
2635    /**
2636     * Deletes all entries in the published resource table.<p>
2637     *
2638     * @param dbc the current database context
2639     * @param linkType the type of resource deleted (0= non-paramter, 1=parameter)
2640     *
2641     * @throws CmsException if something goes wrong
2642     */
2643    public void deleteAllStaticExportPublishedResources(CmsDbContext dbc, int linkType) throws CmsException {
2644
2645        getProjectDriver(dbc).deleteAllStaticExportPublishedResources(dbc, linkType);
2646    }
2647
2648    /**
2649     * Deletes a group, where all permissions, users and children of the group
2650     * are transfered to a replacement group.<p>
2651     *
2652     * @param dbc the current request context
2653     * @param group the id of the group to be deleted
2654     * @param replacementId the id of the group to be transfered, can be <code>null</code>
2655     *
2656     * @throws CmsException if operation was not successful
2657     * @throws CmsDataAccessException if group to be deleted contains user
2658     */
2659    public void deleteGroup(CmsDbContext dbc, CmsGroup group, CmsUUID replacementId)
2660    throws CmsDataAccessException, CmsException {
2661
2662        CmsGroup replacementGroup = null;
2663        if (replacementId != null) {
2664            replacementGroup = readGroup(dbc, replacementId);
2665        }
2666        // get all child groups of the group
2667        List<CmsGroup> children = getChildren(dbc, group, false);
2668        // get all users in this group
2669        List<CmsUser> users = getUsersOfGroup(dbc, group.getName(), true, true, group.isRole());
2670        // get online project
2671        CmsProject onlineProject = readProject(dbc, CmsProject.ONLINE_PROJECT_ID);
2672        if (replacementGroup == null) {
2673            // remove users
2674            Iterator<CmsUser> itUsers = users.iterator();
2675            while (itUsers.hasNext()) {
2676                CmsUser user = itUsers.next();
2677                if (userInGroup(dbc, user.getName(), group.getName(), group.isRole())) {
2678                    removeUserFromGroup(dbc, user.getName(), group.getName(), group.isRole());
2679                }
2680            }
2681            // transfer children to grandfather if possible
2682            CmsUUID parentId = group.getParentId();
2683            if (parentId == null) {
2684                parentId = CmsUUID.getNullUUID();
2685            }
2686            Iterator<CmsGroup> itChildren = children.iterator();
2687            while (itChildren.hasNext()) {
2688                CmsGroup child = itChildren.next();
2689                child.setParentId(parentId);
2690                writeGroup(dbc, child);
2691            }
2692        } else {
2693            // move children
2694            Iterator<CmsGroup> itChildren = children.iterator();
2695            while (itChildren.hasNext()) {
2696                CmsGroup child = itChildren.next();
2697                child.setParentId(replacementId);
2698                writeGroup(dbc, child);
2699            }
2700            // move users
2701            Iterator<CmsUser> itUsers = users.iterator();
2702            while (itUsers.hasNext()) {
2703                CmsUser user = itUsers.next();
2704                addUserToGroup(dbc, user.getName(), replacementGroup.getName(), group.isRole());
2705                removeUserFromGroup(dbc, user.getName(), group.getName(), group.isRole());
2706            }
2707            // transfer for offline
2708            transferPrincipalResources(dbc, dbc.currentProject(), group.getId(), replacementId, true);
2709            // transfer for online
2710            transferPrincipalResources(dbc, onlineProject, group.getId(), replacementId, true);
2711        }
2712        // remove the group
2713        getUserDriver(
2714            dbc).removeAccessControlEntriesForPrincipal(dbc, dbc.currentProject(), onlineProject, group.getId());
2715        getUserDriver(dbc).deleteGroup(dbc, group.getName());
2716        // backup the group
2717        getHistoryDriver(dbc).writePrincipal(dbc, group);
2718        if (OpenCms.getSubscriptionManager().isEnabled()) {
2719            // delete all subscribed resources for group
2720            unsubscribeAllResourcesFor(dbc, OpenCms.getSubscriptionManager().getPoolName(), group);
2721        }
2722
2723        // clear the relevant caches
2724        m_monitor.uncacheGroup(group);
2725        m_monitor.flushCache(
2726            CmsMemoryMonitor.CacheType.USERGROUPS,
2727            CmsMemoryMonitor.CacheType.USER_LIST,
2728            CmsMemoryMonitor.CacheType.ACL);
2729
2730        if (!dbc.getProjectId().isNullUUID()) {
2731            // group modified event is not needed
2732            return;
2733        }
2734        // fire group modified event
2735        Map<String, Object> eventData = new HashMap<String, Object>();
2736        eventData.put(I_CmsEventListener.KEY_GROUP_ID, group.getId().toString());
2737        eventData.put(I_CmsEventListener.KEY_GROUP_NAME, group.getName());
2738        eventData.put(I_CmsEventListener.KEY_USER_ACTION, I_CmsEventListener.VALUE_GROUP_MODIFIED_ACTION_DELETE);
2739        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_GROUP_MODIFIED, eventData));
2740    }
2741
2742    /**
2743     * Deletes the versions from the history tables, keeping the given number of versions per resource.<p>
2744     *
2745     * if the <code>cleanUp</code> option is set, additionally versions of deleted resources will be removed.<p>
2746     *
2747     * @param dbc the current database context
2748     * @param versionsToKeep number of versions to keep, is ignored if negative
2749     * @param versionsDeleted number of versions to keep for deleted resources, is ignored if negative
2750     * @param timeDeleted deleted resources older than this will also be deleted, is ignored if negative
2751     * @param clearDeletedFilter filter to evaluate whether the deleted resources should be cleared
2752     * @param report the report for output logging
2753     *
2754     * @throws CmsException if operation was not successful
2755     */
2756    public void deleteHistoricalVersions(
2757        CmsDbContext dbc,
2758        int versionsToKeep,
2759        int versionsDeleted,
2760        long timeDeleted,
2761        Predicate<I_CmsHistoryResource> clearDeletedFilter,
2762        I_CmsReport report)
2763    throws CmsException {
2764
2765        if (clearDeletedFilter == null) {
2766            clearDeletedFilter = res -> true;
2767        }
2768
2769        report.println(Messages.get().container(Messages.RPT_START_DELETE_VERSIONS_0), I_CmsReport.FORMAT_HEADLINE);
2770        if (versionsToKeep >= 0) {
2771            report.println(
2772                Messages.get().container(Messages.RPT_START_DELETE_ACT_VERSIONS_1, Integer.valueOf(versionsToKeep)),
2773                I_CmsReport.FORMAT_HEADLINE);
2774
2775            List<I_CmsHistoryResource> resources = getHistoryDriver(dbc).getAllNotDeletedEntries(dbc);
2776            if (resources.isEmpty()) {
2777                report.println(Messages.get().container(Messages.RPT_DELETE_NOTHING_0), I_CmsReport.FORMAT_OK);
2778            }
2779            int n = resources.size();
2780            int m = 1;
2781            Iterator<I_CmsHistoryResource> itResources = resources.iterator();
2782            while (itResources.hasNext()) {
2783                I_CmsHistoryResource histResource = itResources.next();
2784
2785                report.print(
2786                    org.opencms.report.Messages.get().container(
2787                        org.opencms.report.Messages.RPT_SUCCESSION_2,
2788                        String.valueOf(m),
2789                        String.valueOf(n)),
2790                    I_CmsReport.FORMAT_NOTE);
2791                report.print(
2792                    org.opencms.report.Messages.get().container(
2793                        org.opencms.report.Messages.RPT_ARGUMENT_1,
2794                        dbc.removeSiteRoot(histResource.getRootPath())));
2795                report.print(org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_DOTS_0));
2796
2797                try {
2798                    int deleted = getHistoryDriver(dbc).deleteEntries(dbc, histResource, versionsToKeep, -1);
2799
2800                    report.print(
2801                        Messages.get().container(Messages.RPT_VERSION_DELETING_1, Integer.valueOf(deleted)),
2802                        I_CmsReport.FORMAT_NOTE);
2803                    report.print(org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_DOTS_0));
2804                    report.println(
2805                        org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_OK_0),
2806                        I_CmsReport.FORMAT_OK);
2807                } catch (CmsDataAccessException e) {
2808                    report.println(
2809                        org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_ERROR_0),
2810                        I_CmsReport.FORMAT_ERROR);
2811
2812                    if (LOG.isDebugEnabled()) {
2813                        LOG.debug(e.getLocalizedMessage(), e);
2814                    }
2815                }
2816
2817                m++;
2818            }
2819
2820            report.println(
2821                Messages.get().container(Messages.RPT_END_DELETE_ACT_VERSIONS_0),
2822                I_CmsReport.FORMAT_HEADLINE);
2823        }
2824        if ((versionsDeleted >= 0) || (timeDeleted >= 0)) {
2825            if (timeDeleted >= 0) {
2826                report.println(
2827                    Messages.get().container(
2828                        Messages.RPT_START_DELETE_DEL_VERSIONS_2,
2829                        Integer.valueOf(versionsDeleted),
2830                        new Date(timeDeleted)),
2831                    I_CmsReport.FORMAT_HEADLINE);
2832            } else {
2833                report.println(
2834                    Messages.get().container(
2835                        Messages.RPT_START_DELETE_DEL_VERSIONS_1,
2836                        Integer.valueOf(versionsDeleted)),
2837                    I_CmsReport.FORMAT_HEADLINE);
2838            }
2839            List<I_CmsHistoryResource> resources = getHistoryDriver(dbc).getAllDeletedEntries(dbc);
2840            resources = resources.stream().filter(clearDeletedFilter).collect(Collectors.toList());
2841            if (resources.isEmpty()) {
2842                report.println(Messages.get().container(Messages.RPT_DELETE_NOTHING_0), I_CmsReport.FORMAT_OK);
2843            }
2844            int n = resources.size();
2845            int m = 1;
2846            Iterator<I_CmsHistoryResource> itResources = resources.iterator();
2847            while (itResources.hasNext()) {
2848                I_CmsHistoryResource histResource = itResources.next();
2849
2850                report.print(
2851                    org.opencms.report.Messages.get().container(
2852                        org.opencms.report.Messages.RPT_SUCCESSION_2,
2853                        String.valueOf(m),
2854                        String.valueOf(n)),
2855                    I_CmsReport.FORMAT_NOTE);
2856                report.print(
2857                    org.opencms.report.Messages.get().container(
2858                        org.opencms.report.Messages.RPT_ARGUMENT_1,
2859                        dbc.removeSiteRoot(histResource.getRootPath())));
2860                report.print(org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_DOTS_0));
2861
2862                try {
2863                    int deleted = getHistoryDriver(dbc).deleteEntries(dbc, histResource, versionsDeleted, timeDeleted);
2864
2865                    report.print(
2866                        Messages.get().container(Messages.RPT_VERSION_DELETING_1, Integer.valueOf(deleted)),
2867                        I_CmsReport.FORMAT_NOTE);
2868                    report.print(org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_DOTS_0));
2869                    report.println(
2870                        org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_OK_0),
2871                        I_CmsReport.FORMAT_OK);
2872                } catch (CmsDataAccessException e) {
2873                    report.println(
2874                        org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_ERROR_0),
2875                        I_CmsReport.FORMAT_ERROR);
2876
2877                    if (LOG.isDebugEnabled()) {
2878                        LOG.debug(e.getLocalizedMessage(), e);
2879                    }
2880                }
2881
2882                m++;
2883            }
2884            report.println(
2885                Messages.get().container(Messages.RPT_END_DELETE_DEL_VERSIONS_0),
2886                I_CmsReport.FORMAT_HEADLINE);
2887        }
2888        report.println(Messages.get().container(Messages.RPT_END_DELETE_VERSIONS_0), I_CmsReport.FORMAT_HEADLINE);
2889    }
2890
2891    /**
2892     * Deletes all log entries matching the given filter.<p>
2893     *
2894     * @param dbc the current db context
2895     * @param filter the filter to use for deletion
2896     *
2897     * @throws CmsException if something goes wrong
2898     *
2899     * @see CmsSecurityManager#deleteLogEntries(CmsRequestContext, CmsLogFilter)
2900     */
2901    public void deleteLogEntries(CmsDbContext dbc, CmsLogFilter filter) throws CmsException {
2902
2903        updateLog(dbc);
2904        m_projectDriver.deleteLog(dbc, filter);
2905    }
2906
2907    /**
2908     * Deletes an organizational unit.<p>
2909     *
2910     * Only organizational units that contain no suborganizational unit can be deleted.<p>
2911     *
2912     * The organizational unit can not be delete if it is used in the request context,
2913     * or if the current user belongs to it.<p>
2914     *
2915     * All users and groups in the given organizational unit will be deleted.<p>
2916     *
2917     * @param dbc the current db context
2918     * @param organizationalUnit the organizational unit to delete
2919     *
2920     * @throws CmsException if operation was not successful
2921     *
2922     * @see org.opencms.security.CmsOrgUnitManager#deleteOrganizationalUnit(CmsObject, String)
2923     */
2924    public void deleteOrganizationalUnit(CmsDbContext dbc, CmsOrganizationalUnit organizationalUnit)
2925    throws CmsException {
2926
2927        // check organizational unit in context
2928        if (dbc.getRequestContext().getOuFqn().equals(organizationalUnit.getName())) {
2929            throw new CmsDbConsistencyException(
2930                Messages.get().container(Messages.ERR_ORGUNIT_DELETE_IN_CONTEXT_1, organizationalUnit.getName()));
2931        }
2932        // check organizational unit for user
2933        if (dbc.currentUser().getOuFqn().equals(organizationalUnit.getName())) {
2934            throw new CmsDbConsistencyException(
2935                Messages.get().container(Messages.ERR_ORGUNIT_DELETE_CURRENT_USER_1, organizationalUnit.getName()));
2936        }
2937        // check sub organizational units
2938        if (!getOrganizationalUnits(dbc, organizationalUnit, true).isEmpty()) {
2939            throw new CmsDbConsistencyException(
2940                Messages.get().container(Messages.ERR_ORGUNIT_DELETE_SUB_ORGUNITS_1, organizationalUnit.getName()));
2941        }
2942        // check groups
2943        List<CmsGroup> groups = getGroups(dbc, organizationalUnit, true, false);
2944        Iterator<CmsGroup> itGroups = groups.iterator();
2945        while (itGroups.hasNext()) {
2946            CmsGroup group = itGroups.next();
2947            if (!OpenCms.getDefaultUsers().isDefaultGroup(group.getName())) {
2948                throw new CmsDbConsistencyException(
2949                    Messages.get().container(Messages.ERR_ORGUNIT_DELETE_GROUPS_1, organizationalUnit.getName()));
2950            }
2951        }
2952        // check users
2953        if (!getUsers(dbc, organizationalUnit, true).isEmpty()) {
2954            throw new CmsDbConsistencyException(
2955                Messages.get().container(Messages.ERR_ORGUNIT_DELETE_USERS_1, organizationalUnit.getName()));
2956        }
2957
2958        // delete default groups if needed
2959        itGroups = groups.iterator();
2960        while (itGroups.hasNext()) {
2961            CmsGroup group = itGroups.next();
2962            deleteGroup(dbc, group, null);
2963        }
2964
2965        // delete projects
2966        Iterator<CmsProject> itProjects = getProjectDriver(dbc).readProjects(
2967            dbc,
2968            organizationalUnit.getName()).iterator();
2969        while (itProjects.hasNext()) {
2970            CmsProject project = itProjects.next();
2971            deleteProject(dbc, project, false);
2972        }
2973
2974        // delete roles
2975        Iterator<CmsGroup> itRoles = getGroups(dbc, organizationalUnit, true, true).iterator();
2976        while (itRoles.hasNext()) {
2977            CmsGroup role = itRoles.next();
2978            deleteGroup(dbc, role, null);
2979        }
2980
2981        // create a publish list for the 'virtual' publish event
2982        CmsResource resource = readResource(dbc, organizationalUnit.getId(), CmsResourceFilter.DEFAULT);
2983        CmsPublishList pl = new CmsPublishList(resource, false);
2984        pl.add(resource, false);
2985
2986        // remove the organizational unit itself
2987        getUserDriver(dbc).deleteOrganizationalUnit(dbc, organizationalUnit);
2988
2989        // write the publish history entry
2990        getProjectDriver(dbc).writePublishHistory(
2991            dbc,
2992            pl.getPublishHistoryId(),
2993            new CmsPublishedResource(resource, -1, CmsResourceState.STATE_DELETED));
2994
2995        // flush relevant caches
2996        m_monitor.clearPrincipalsCache();
2997        m_monitor.flushCache(CmsMemoryMonitor.CacheType.PROPERTY, CmsMemoryMonitor.CacheType.PROPERTY_LIST);
2998
2999        // fire the 'virtual' publish event
3000        Map<String, Object> eventData = new HashMap<String, Object>();
3001        eventData.put(I_CmsEventListener.KEY_PUBLISHID, pl.getPublishHistoryId().toString());
3002        eventData.put(I_CmsEventListener.KEY_PROJECTID, dbc.currentProject().getUuid());
3003        eventData.put(I_CmsEventListener.KEY_DBCONTEXT, dbc);
3004        CmsEvent afterPublishEvent = new CmsEvent(I_CmsEventListener.EVENT_PUBLISH_PROJECT, eventData);
3005        OpenCms.fireCmsEvent(afterPublishEvent);
3006
3007        m_lockManager.removeDeletedResource(dbc, resource.getRootPath());
3008
3009        if (!dbc.getProjectId().isNullUUID()) {
3010            // OU modified event is not needed
3011            return;
3012        }
3013        // fire OU modified event
3014        Map<String, Object> event2Data = new HashMap<String, Object>();
3015        event2Data.put(I_CmsEventListener.KEY_OU_NAME, organizationalUnit.getName());
3016        event2Data.put(I_CmsEventListener.KEY_USER_ACTION, I_CmsEventListener.VALUE_OU_MODIFIED_ACTION_DELETE);
3017        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_OU_MODIFIED, event2Data));
3018
3019    }
3020
3021    /**
3022     * Deletes a project.<p>
3023     *
3024     * Only the admin or the owner of the project can do this.
3025     *
3026     * @param dbc the current database context
3027     * @param deleteProject the project to be deleted
3028     *
3029     * @throws CmsException if something goes wrong
3030     */
3031    public void deleteProject(CmsDbContext dbc, CmsProject deleteProject) throws CmsException {
3032
3033        deleteProject(dbc, deleteProject, true);
3034    }
3035
3036    /**
3037     * Deletes a project.<p>
3038     *
3039     * Only the admin or the owner of the project can do this.
3040     *
3041     * @param dbc the current database context
3042     * @param deleteProject the project to be deleted
3043     * @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
3044     *
3045     * @throws CmsException if something goes wrong
3046     */
3047    public void deleteProject(CmsDbContext dbc, CmsProject deleteProject, boolean resetResources) throws CmsException {
3048
3049        CmsUUID projectId = deleteProject.getUuid();
3050
3051        if (resetResources) {
3052            // changed/new/deleted files in the specified project
3053            List<CmsResource> modifiedFiles = readChangedResourcesInsideProject(dbc, projectId, RCPRM_FILES_ONLY_MODE);
3054            // changed/new/deleted folders in the specified project
3055            List<CmsResource> modifiedFolders = readChangedResourcesInsideProject(
3056                dbc,
3057                projectId,
3058                RCPRM_FOLDERS_ONLY_MODE);
3059            resetResourcesInProject(dbc, projectId, modifiedFiles, modifiedFolders);
3060        }
3061
3062        // unlock all resources in the project
3063        m_lockManager.removeResourcesInProject(deleteProject.getUuid(), true);
3064        m_monitor.clearAccessControlListCache();
3065        m_monitor.clearResourceCache();
3066
3067        // set project to online project if current project is the one which will be deleted
3068        if (projectId.equals(dbc.currentProject().getUuid())) {
3069            dbc.getRequestContext().setCurrentProject(readProject(dbc, CmsProject.ONLINE_PROJECT_ID));
3070        }
3071
3072        // delete the project itself
3073        getProjectDriver(dbc).deleteProject(dbc, deleteProject);
3074        m_monitor.uncacheProject(deleteProject);
3075
3076        // fire the corresponding event
3077        OpenCms.fireCmsEvent(
3078            new CmsEvent(
3079                I_CmsEventListener.EVENT_PROJECT_MODIFIED,
3080                Collections.<String, Object> singletonMap("project", deleteProject)));
3081
3082    }
3083
3084    /**
3085     * Deletes a property definition.<p>
3086     *
3087     * @param dbc the current database context
3088     * @param name the name of the property definition to delete
3089     *
3090     * @throws CmsException if something goes wrong
3091     */
3092    public void deletePropertyDefinition(CmsDbContext dbc, String name) throws CmsException {
3093
3094        CmsPropertyDefinition propertyDefinition = null;
3095
3096        try {
3097            // first read and then delete the metadefinition.
3098            propertyDefinition = readPropertyDefinition(dbc, name);
3099            getVfsDriver(dbc).deletePropertyDefinition(dbc, propertyDefinition);
3100            getHistoryDriver(dbc).deletePropertyDefinition(dbc, propertyDefinition);
3101        } finally {
3102
3103            // fire an event that a property of a resource has been deleted
3104            OpenCms.fireCmsEvent(
3105                new CmsEvent(
3106                    I_CmsEventListener.EVENT_PROPERTY_DEFINITION_MODIFIED,
3107                    Collections.<String, Object> singletonMap("propertyDefinition", propertyDefinition)));
3108        }
3109    }
3110
3111    /**
3112     * Deletes a publish job identified by its history id.<p>
3113     *
3114     * @param dbc the current database context
3115     * @param publishHistoryId the history id identifying the publish job
3116     *
3117     * @throws CmsException if something goes wrong
3118     */
3119    public void deletePublishJob(CmsDbContext dbc, CmsUUID publishHistoryId) throws CmsException {
3120
3121        getProjectDriver(dbc).deletePublishJob(dbc, publishHistoryId);
3122    }
3123
3124    /**
3125     * Deletes the publish list assigned to a publish job.<p>
3126     *
3127     * @param dbc the current database context
3128     * @param publishHistoryId the history id identifying the publish job
3129     * @throws CmsException if something goes wrong
3130     */
3131    public void deletePublishList(CmsDbContext dbc, CmsUUID publishHistoryId) throws CmsException {
3132
3133        getProjectDriver(dbc).deletePublishList(dbc, publishHistoryId);
3134    }
3135
3136    /**
3137     * Deletes all relations for the given resource matching the given filter.<p>
3138     *
3139     * @param dbc the current db context
3140     * @param resource the resource to delete the relations for
3141     * @param filter the filter to use for deletion
3142     *
3143     * @throws CmsException if something goes wrong
3144     *
3145     * @see CmsSecurityManager#deleteRelationsForResource(CmsRequestContext, CmsResource, CmsRelationFilter)
3146     */
3147    public void deleteRelationsForResource(CmsDbContext dbc, CmsResource resource, CmsRelationFilter filter)
3148    throws CmsException {
3149
3150        if (filter.includesDefinedInContent()) {
3151            throw new CmsIllegalArgumentException(
3152                Messages.get().container(
3153                    Messages.ERR_DELETE_RELATION_IN_CONTENT_2,
3154                    dbc.removeSiteRoot(resource.getRootPath()),
3155                    filter.getTypes()));
3156        }
3157        getVfsDriver(dbc).deleteRelations(dbc, dbc.currentProject().getUuid(), resource, filter);
3158        setDateLastModified(dbc, resource, System.currentTimeMillis());
3159        log(
3160            dbc,
3161            new CmsLogEntry(
3162                dbc,
3163                resource.getStructureId(),
3164                CmsLogEntryType.RESOURCE_REMOVE_RELATION,
3165                new String[] {resource.getRootPath(), filter.toString()}),
3166            false);
3167    }
3168
3169    /**
3170     * Deletes a resource.<p>
3171     *
3172     * The <code>siblingMode</code> parameter controls how to handle siblings
3173     * during the delete operation.
3174     * Possible values for this parameter are:
3175     * <ul>
3176     * <li><code>{@link CmsResource#DELETE_REMOVE_SIBLINGS}</code></li>
3177     * <li><code>{@link CmsResource#DELETE_PRESERVE_SIBLINGS}</code></li>
3178     * </ul><p>
3179     *
3180     * @param dbc the current database context
3181     * @param resource the name of the resource to delete (full path)
3182     * @param siblingMode indicates how to handle siblings of the deleted resource
3183     *
3184     * @throws CmsException if something goes wrong
3185     *
3186     * @see CmsObject#deleteResource(String, CmsResource.CmsResourceDeleteMode)
3187     * @see I_CmsResourceType#deleteResource(CmsObject, CmsSecurityManager, CmsResource, CmsResource.CmsResourceDeleteMode)
3188     */
3189    public void deleteResource(CmsDbContext dbc, CmsResource resource, CmsResource.CmsResourceDeleteMode siblingMode)
3190    throws CmsException {
3191
3192        // upgrade a potential inherited, non-shared lock into a common lock
3193        CmsLock currentLock = getLock(dbc, resource);
3194        if (currentLock.getEditionLock().isDirectlyInherited()) {
3195            // upgrade the lock status if required
3196            lockResource(dbc, resource, CmsLockType.EXCLUSIVE);
3197        }
3198
3199        // check if siblings of the resource exist and must be deleted as well
3200        if (resource.isFolder()) {
3201            // folder can have no siblings
3202            siblingMode = CmsResource.DELETE_PRESERVE_SIBLINGS;
3203        }
3204
3205        // if selected, add all siblings of this resource to the list of resources to be deleted
3206        boolean allSiblingsRemoved;
3207        List<CmsResource> resources;
3208        if (siblingMode == CmsResource.DELETE_REMOVE_SIBLINGS) {
3209            resources = new ArrayList<CmsResource>(readSiblings(dbc, resource, CmsResourceFilter.ALL));
3210            allSiblingsRemoved = true;
3211
3212            // ensure that the resource requested to be deleted is the last resource that gets actually deleted
3213            // to keep the shared locks of the siblings while those get deleted.
3214            resources.remove(resource);
3215            resources.add(resource);
3216        } else {
3217            // only delete the resource, no siblings
3218            resources = Collections.singletonList(resource);
3219            allSiblingsRemoved = false;
3220        }
3221
3222        int size = resources.size();
3223        // if we have only one resource no further check is required
3224        if (size > 1) {
3225            CmsMultiException me = new CmsMultiException();
3226            // ensure that each sibling is unlocked or locked by the current user
3227            for (int i = 0; i < size; i++) {
3228                CmsResource currentResource = resources.get(i);
3229                currentLock = getLock(dbc, currentResource);
3230                if (!currentLock.getEditionLock().isUnlocked() && !currentLock.isOwnedBy(dbc.currentUser())) {
3231                    // the resource is locked by a user different from the current user
3232                    CmsRequestContext context = dbc.getRequestContext();
3233                    me.addException(
3234                        new CmsLockException(
3235                            org.opencms.lock.Messages.get().container(
3236                                org.opencms.lock.Messages.ERR_SIBLING_LOCKED_2,
3237                                context.getSitePath(currentResource),
3238                                context.getSitePath(resource))));
3239                }
3240            }
3241            if (!me.getExceptions().isEmpty()) {
3242                throw me;
3243            }
3244        }
3245
3246        boolean removeAce = true;
3247
3248        if (resource.isFolder()) {
3249            // check if the folder has any resources in it
3250            Iterator<CmsResource> childResources = getVfsDriver(
3251                dbc).readChildResources(dbc, dbc.currentProject(), resource, true, true).iterator();
3252
3253            CmsUUID projectId = CmsProject.ONLINE_PROJECT_ID;
3254            if (dbc.currentProject().isOnlineProject()) {
3255                projectId = CmsUUID.getOpenCmsUUID(); // HACK: to get an offline project id
3256            }
3257
3258            // collect the names of the resources inside the folder, excluding the moved resources
3259            StringBuffer errorResNames = new StringBuffer(128);
3260            while (childResources.hasNext()) {
3261                CmsResource errorRes = childResources.next();
3262                if (errorRes.getState().isDeleted()) {
3263                    continue;
3264                }
3265                // if deleting offline, or not moved, or just renamed inside the deleted folder
3266                // so, it may remain some orphan online entries for moved resources
3267                // which will be fixed during the publishing of the moved resources
3268                boolean error = !dbc.currentProject().isOnlineProject();
3269                if (!error) {
3270                    try {
3271                        String originalPath = getVfsDriver(
3272                            dbc).readResource(dbc, projectId, errorRes.getRootPath(), true).getRootPath();
3273                        error = originalPath.equals(errorRes.getRootPath())
3274                            || originalPath.startsWith(resource.getRootPath());
3275                    } catch (CmsVfsResourceNotFoundException e) {
3276                        // ignore
3277                    }
3278                }
3279                if (error) {
3280                    if (errorResNames.length() != 0) {
3281                        errorResNames.append(", ");
3282                    }
3283                    errorResNames.append("[" + dbc.removeSiteRoot(errorRes.getRootPath()) + "]");
3284                }
3285            }
3286
3287            // the current implementation only deletes empty folders
3288            if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(errorResNames.toString())) {
3289                throw new CmsVfsException(
3290                    org.opencms.db.generic.Messages.get().container(
3291                        org.opencms.db.generic.Messages.ERR_DELETE_NONEMTY_FOLDER_2,
3292                        dbc.removeSiteRoot(resource.getRootPath()),
3293                        errorResNames.toString()));
3294            }
3295        }
3296
3297        // delete all collected resources
3298        for (int i = 0; i < size; i++) {
3299            CmsResource currentResource = resources.get(i);
3300
3301            // try to delete/remove the resource only if the user has write access to the resource
3302            // check permissions only for the sibling, the resource it self was already checked or
3303            // is to be removed without write permissions, ie. while deleting a folder
3304            if (!currentResource.equals(resource)
3305                && (I_CmsPermissionHandler.PERM_ALLOWED != m_securityManager.hasPermissions(
3306                    dbc,
3307                    currentResource,
3308                    CmsPermissionSet.ACCESS_WRITE,
3309                    LockCheck.yes,
3310                    CmsResourceFilter.ALL))) {
3311
3312                // no write access to sibling - must keep ACE (see below)
3313                allSiblingsRemoved = false;
3314            } else {
3315                // write access to sibling granted
3316                boolean existsOnline = (getVfsDriver(dbc).validateStructureIdExists(
3317                    dbc,
3318                    CmsProject.ONLINE_PROJECT_ID,
3319                    currentResource.getStructureId()) || !(currentResource.getState().equals(CmsResource.STATE_NEW)));
3320                if (!existsOnline) {
3321                    // the resource does not exist online => remove the resource
3322                    // this means the resource is "new" (blue) in the offline project
3323
3324                    // delete all properties of this resource
3325                    deleteAllProperties(dbc, currentResource.getRootPath());
3326
3327                    if (currentResource.isFolder()) {
3328                        getVfsDriver(dbc).removeFolder(dbc, dbc.currentProject(), currentResource);
3329                    } else {
3330                        // check labels
3331                        if (currentResource.isLabeled() && !labelResource(dbc, currentResource, null, 2)) {
3332                            // update the resource flags to "un label" the other siblings
3333                            int flags = currentResource.getFlags();
3334                            flags &= ~CmsResource.FLAG_LABELED;
3335                            currentResource.setFlags(flags);
3336                        }
3337                        getVfsDriver(dbc).removeFile(dbc, dbc.currentProject().getUuid(), currentResource);
3338                    }
3339
3340                    // ensure an exclusive lock is removed in the lock manager for a deleted new resource,
3341                    // otherwise it would "stick" in the lock manager, preventing other users from creating
3342                    // a file with the same name (issue with temp files in editor)
3343                    m_lockManager.removeDeletedResource(dbc, currentResource.getRootPath());
3344                    // delete relations
3345                    getVfsDriver(dbc).deleteRelations(
3346                        dbc,
3347                        dbc.currentProject().getUuid(),
3348                        currentResource,
3349                        CmsRelationFilter.TARGETS);
3350                    getVfsDriver(dbc).deleteUrlNameMappingEntries(
3351                        dbc,
3352                        false,
3353                        CmsUrlNameMappingFilter.ALL.filterStructureId(currentResource.getStructureId()));
3354                    getVfsDriver(dbc).deleteAliases(
3355                        dbc,
3356                        dbc.currentProject(),
3357                        new CmsAliasFilter(null, null, currentResource.getStructureId()));
3358                    log(
3359                        dbc,
3360                        new CmsLogEntry(
3361                            dbc,
3362                            currentResource.getStructureId(),
3363                            CmsLogEntryType.RESOURCE_NEW_DELETED,
3364                            new String[] {currentResource.getRootPath()}),
3365                        true);
3366                } else {
3367                    // the resource exists online => mark the resource as deleted
3368                    // structure record is removed during next publish
3369                    // if one (or more) siblings are not removed, the ACE can not be removed
3370                    removeAce = false;
3371                    // set resource state to deleted
3372                    currentResource.setState(CmsResource.STATE_DELETED);
3373                    getVfsDriver(
3374                        dbc).writeResourceState(dbc, dbc.currentProject(), currentResource, UPDATE_STRUCTURE, false);
3375
3376                    // update the project ID
3377                    getVfsDriver(dbc).writeLastModifiedProjectId(
3378                        dbc,
3379                        dbc.currentProject(),
3380                        dbc.currentProject().getUuid(),
3381                        currentResource);
3382                    // log it
3383
3384                    log(
3385                        dbc,
3386                        new CmsLogEntry(
3387                            dbc,
3388                            currentResource.getStructureId(),
3389                            CmsLogEntryType.RESOURCE_DELETED,
3390                            new String[] {currentResource.getRootPath()}),
3391                        true);
3392                }
3393            }
3394        }
3395
3396        if ((resource.getSiblingCount() <= 1) || allSiblingsRemoved) {
3397            if (removeAce) {
3398                // remove the access control entries
3399                getUserDriver(dbc).removeAccessControlEntries(dbc, dbc.currentProject(), resource.getResourceId());
3400            }
3401        }
3402
3403        // flush all caches
3404        m_monitor.clearAccessControlListCache();
3405        m_monitor.flushCache(
3406            CmsMemoryMonitor.CacheType.PROPERTY,
3407            CmsMemoryMonitor.CacheType.PROPERTY_LIST,
3408            CmsMemoryMonitor.CacheType.PROJECT_RESOURCES);
3409
3410        Map<String, Object> eventData = new HashMap<String, Object>();
3411        eventData.put(I_CmsEventListener.KEY_RESOURCES, resources);
3412        eventData.put(I_CmsEventListener.KEY_DBCONTEXT, dbc);
3413        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_DELETED, eventData));
3414    }
3415
3416    /**
3417     * Deletes an entry in the published resource table.<p>
3418     *
3419     * @param dbc the current database context
3420     * @param resourceName The name of the resource to be deleted in the static export
3421     * @param linkType the type of resource deleted (0= non-parameter, 1=parameter)
3422     * @param linkParameter the parameters of the resource
3423     *
3424     * @throws CmsException if something goes wrong
3425     */
3426    public void deleteStaticExportPublishedResource(
3427        CmsDbContext dbc,
3428        String resourceName,
3429        int linkType,
3430        String linkParameter)
3431    throws CmsException {
3432
3433        getProjectDriver(dbc).deleteStaticExportPublishedResource(dbc, resourceName, linkType, linkParameter);
3434    }
3435
3436    /**
3437     * Deletes a user, where all permissions and resources attributes of the user
3438     * were transfered to a replacement user, if given.<p>
3439     *
3440     * Only users, which are in the group "administrators" are granted.<p>
3441     *
3442     * @param dbc the current database context
3443     * @param project the current project
3444     * @param username the name of the user to be deleted
3445     * @param replacementUsername the name of the user to be transfered, can be <code>null</code>
3446     *
3447     * @throws CmsException if operation was not successful
3448     */
3449    public void deleteUser(CmsDbContext dbc, CmsProject project, String username, String replacementUsername)
3450    throws CmsException {
3451
3452        // Test if the users exists
3453        CmsUser user = readUser(dbc, username);
3454        CmsUser replacementUser = null;
3455        if (replacementUsername != null) {
3456            replacementUser = readUser(dbc, replacementUsername);
3457        }
3458
3459        CmsProject onlineProject = readProject(dbc, CmsProject.ONLINE_PROJECT_ID);
3460        boolean withACEs = true;
3461        if (replacementUser == null) {
3462            withACEs = false;
3463            replacementUser = readUser(dbc, OpenCms.getDefaultUsers().getUserDeletedResource());
3464        }
3465
3466        boolean isVfsManager = m_securityManager.hasRole(dbc, replacementUser, CmsRole.VFS_MANAGER);
3467
3468        // iterate groups and roles
3469        for (int i = 0; i < 2; i++) {
3470            boolean readRoles = i != 0;
3471            Iterator<CmsGroup> itGroups = getGroupsOfUser(
3472                dbc,
3473                username,
3474                "",
3475                true,
3476                readRoles,
3477                true,
3478                dbc.getRequestContext().getRemoteAddress()).iterator();
3479            while (itGroups.hasNext()) {
3480                CmsGroup group = itGroups.next();
3481                if (!isVfsManager) {
3482                    // add replacement user to user groups
3483                    if (!userInGroup(dbc, replacementUser.getName(), group.getName(), readRoles)) {
3484                        addUserToGroup(dbc, replacementUser.getName(), group.getName(), readRoles);
3485                    }
3486                }
3487                // remove user from groups
3488                if (userInGroup(dbc, username, group.getName(), readRoles)) {
3489                    // we need this additional check because removing a user from a group
3490                    // may also automatically remove him from other groups if the group was
3491                    // associated with a role.
3492                    removeUserFromGroup(dbc, username, group.getName(), readRoles);
3493                }
3494            }
3495        }
3496        // remove all locks set for the deleted user
3497        m_lockManager.removeLocks(user.getId());
3498
3499        boolean skipTransfer = false;
3500        try {
3501
3502            // Looking up the resources for the principal transfer is pretty expensive,
3503            // but for webuser OUs without associated OU resources, it should be unnecessary.
3504            // So we can save ourselves the work.
3505            // We cache this "skippable" status on a per-OU basis, so we don't need to a do it for each
3506            // user if we delete a lot of users in a row.
3507            //
3508            // Note: Even if there *were* resources which would be affected by transferPrincipalResources,
3509            // skipping the transfer doesn't do any harm - the UI just displays the ID instead.
3510
3511            skipTransfer = m_skipTransferPrincipalResourceCache.get(user.getOuFqn(), () -> {
3512
3513                String ouName = CmsOrganizationalUnit.removeLeadingSeparator(user.getOuFqn());
3514                CmsOrganizationalUnit ou = readOrganizationalUnit(dbc, ouName);
3515                if (!ou.hasFlagWebuser()) {
3516                    return false;
3517                }
3518                List<CmsResource> ouResources = getResourcesForOrganizationalUnit(dbc, ou);
3519                return ouResources.size() == 0;
3520            });
3521        } catch (ExecutionException e) {
3522            LOG.error(e.getLocalizedMessage(), e);
3523        }
3524        if (skipTransfer) {
3525            LOG.info(
3526                "Skipping principal transfer while deleting user "
3527                    + user.getName()
3528                    + " ("
3529                    + user.getId()
3530                    + ") because it is part of a webuser OU with no OU resources.");
3531        } else {
3532            // offline
3533            if (dbc.getProjectId().isNullUUID()) {
3534                // offline project available
3535                transferPrincipalResources(dbc, project, user.getId(), replacementUser.getId(), withACEs);
3536            }
3537            // online
3538            transferPrincipalResources(dbc, onlineProject, user.getId(), replacementUser.getId(), withACEs);
3539        }
3540        getUserDriver(dbc).removeAccessControlEntriesForPrincipal(dbc, project, onlineProject, user.getId());
3541        getHistoryDriver(dbc).writePrincipal(dbc, user);
3542        getUserDriver(dbc).deleteUser(dbc, username);
3543        // delete user from cache
3544        m_monitor.clearUserCache(user);
3545
3546        if (!dbc.getProjectId().isNullUUID()) {
3547            // user modified event is not needed
3548            return;
3549        }
3550        // fire user modified event
3551        Map<String, Object> eventData = new HashMap<String, Object>();
3552        eventData.put(I_CmsEventListener.KEY_USER_ID, user.getId().toString());
3553        eventData.put(I_CmsEventListener.KEY_USER_NAME, user.getName());
3554        eventData.put(I_CmsEventListener.KEY_USER_ACTION, I_CmsEventListener.VALUE_USER_MODIFIED_ACTION_DELETE_USER);
3555        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_USER_MODIFIED, eventData));
3556    }
3557
3558    /**
3559     * Destroys this driver manager and releases all allocated resources.<p>
3560     */
3561    public void destroy() {
3562
3563        try {
3564            if (m_projectDriver != null) {
3565                try {
3566                    m_projectDriver.destroy();
3567                } catch (Throwable t) {
3568                    LOG.error(Messages.get().getBundle().key(Messages.ERR_CLOSE_PROJECT_DRIVER_0), t);
3569                }
3570                m_projectDriver = null;
3571            }
3572            if (m_userDriver != null) {
3573                try {
3574                    m_userDriver.destroy();
3575                } catch (Throwable t) {
3576                    LOG.error(Messages.get().getBundle().key(Messages.ERR_CLOSE_USER_DRIVER_0), t);
3577                }
3578                m_userDriver = null;
3579            }
3580            if (m_vfsDriver != null) {
3581                try {
3582                    m_vfsDriver.destroy();
3583                } catch (Throwable t) {
3584                    LOG.error(Messages.get().getBundle().key(Messages.ERR_CLOSE_VFS_DRIVER_0), t);
3585                }
3586                m_vfsDriver = null;
3587            }
3588            if (m_historyDriver != null) {
3589                try {
3590                    m_historyDriver.destroy();
3591                } catch (Throwable t) {
3592                    LOG.error(Messages.get().getBundle().key(Messages.ERR_CLOSE_HISTORY_DRIVER_0), t);
3593                }
3594                m_historyDriver = null;
3595            }
3596
3597            if (m_pools != null) {
3598                for (CmsDbPoolV11 pool : m_pools.values()) {
3599                    try {
3600                        pool.close();
3601                        if (CmsLog.INIT.isDebugEnabled()) {
3602                            CmsLog.INIT.debug(Messages.get().getBundle().key(Messages.INIT_CLOSE_CONN_POOL_1, pool));
3603                        }
3604
3605                    } catch (Throwable t) {
3606                        LOG.error(Messages.get().getBundle().key(Messages.LOG_CLOSE_CONN_POOL_ERROR_1, pool), t);
3607                    }
3608                }
3609                m_pools.clear();
3610            }
3611
3612            m_monitor.clearCache();
3613
3614            m_lockManager = null;
3615            m_htmlLinkValidator = null;
3616        } catch (Throwable t) {
3617            // ignore
3618        }
3619        if (CmsLog.INIT.isInfoEnabled()) {
3620            CmsLog.INIT.info(
3621                Messages.get().getBundle().key(Messages.INIT_DRIVER_MANAGER_DESTROY_1, getClass().getName()));
3622        }
3623    }
3624
3625    /**
3626     * Tests if a resource with the given resourceId does already exist in the Database.<p>
3627     *
3628     * @param dbc the current database context
3629     * @param resourceId the resource id to test for
3630     * @return true if a resource with the given id was found, false otherweise
3631     * @throws CmsException if something goes wrong
3632     */
3633    public boolean existsResourceId(CmsDbContext dbc, CmsUUID resourceId) throws CmsException {
3634
3635        return getVfsDriver(dbc).validateResourceIdExists(dbc, dbc.currentProject().getUuid(), resourceId);
3636    }
3637
3638    /**
3639     * Fills the given publish list with the the VFS resources that actually get published.<p>
3640     *
3641     * Please refer to the source code of this method for the rules on how to decide whether a
3642     * new/changed/deleted <code>{@link CmsResource}</code> object can be published or not.<p>
3643     *
3644     * @param dbc the current database context
3645     * @param publishList must be initialized with basic publish information (Project or direct publish operation),
3646     *                    the given publish list will be filled with all new/changed/deleted files from the current
3647     *                    (offline) project that will be actually published
3648     *
3649     * @throws CmsException if something goes wrong
3650     *
3651     * @see org.opencms.db.CmsPublishList
3652     */
3653    public void fillPublishList(CmsDbContext dbc, CmsPublishList publishList) throws CmsException {
3654
3655        if (!publishList.isDirectPublish()) {
3656            // when publishing a project
3657            // all modified resources with the last change done in the current project are candidates if unlocked
3658            List<CmsResource> folderList = getVfsDriver(dbc).readResourceTree(
3659                dbc,
3660                dbc.currentProject().getUuid(),
3661                CmsDriverManager.READ_IGNORE_PARENT,
3662                CmsDriverManager.READ_IGNORE_TYPE,
3663                CmsResource.STATE_UNCHANGED,
3664                CmsDriverManager.READ_IGNORE_TIME,
3665                CmsDriverManager.READ_IGNORE_TIME,
3666                CmsDriverManager.READ_IGNORE_TIME,
3667                CmsDriverManager.READ_IGNORE_TIME,
3668                CmsDriverManager.READ_IGNORE_TIME,
3669                CmsDriverManager.READ_IGNORE_TIME,
3670                CmsDriverManager.READMODE_INCLUDE_TREE
3671                    | CmsDriverManager.READMODE_INCLUDE_PROJECT
3672                    | CmsDriverManager.READMODE_EXCLUDE_STATE
3673                    | CmsDriverManager.READMODE_ONLY_FOLDERS);
3674
3675            List<CmsResource> fileList = getVfsDriver(dbc).readResourceTree(
3676                dbc,
3677                dbc.currentProject().getUuid(),
3678                CmsDriverManager.READ_IGNORE_PARENT,
3679                CmsDriverManager.READ_IGNORE_TYPE,
3680                CmsResource.STATE_UNCHANGED,
3681                CmsDriverManager.READ_IGNORE_TIME,
3682                CmsDriverManager.READ_IGNORE_TIME,
3683                CmsDriverManager.READ_IGNORE_TIME,
3684                CmsDriverManager.READ_IGNORE_TIME,
3685                CmsDriverManager.READ_IGNORE_TIME,
3686                CmsDriverManager.READ_IGNORE_TIME,
3687                CmsDriverManager.READMODE_INCLUDE_TREE
3688                    | CmsDriverManager.READMODE_INCLUDE_PROJECT
3689                    | CmsDriverManager.READMODE_EXCLUDE_STATE
3690                    | CmsDriverManager.READMODE_ONLY_FILES);
3691            CmsRequestContext context = dbc.getRequestContext();
3692            if ((context != null)
3693                && (context.getAttribute(CmsDefaultWorkflowManager.ATTR_CHECK_PUBLISH_RESOURCE_LIMIT) != null)) {
3694
3695                // check if total size and if it exceeds the resource limit and the request
3696                // context attribute is set, throw an exception.
3697                // we do it here since filterResources() can be very expensive on large resource lists
3698
3699                int limit = OpenCms.getWorkflowManager().getResourceLimit();
3700                int total = fileList.size() + folderList.size();
3701                if (total > limit) {
3702                    throw new CmsTooManyPublishResourcesException(total);
3703                }
3704            }
3705            publishList.addAll(filterResources(dbc, null, folderList), true);
3706            publishList.addAll(filterResources(dbc, publishList, fileList), true);
3707        } else {
3708            // this is a direct publish
3709            Iterator<CmsResource> it = publishList.getDirectPublishResources().iterator();
3710            while (it.hasNext()) {
3711                // iterate all resources in the direct publish list
3712                CmsResource directPublishResource = it.next();
3713                if (directPublishResource.isFolder()) {
3714                    // when publishing a folder directly,
3715                    // the folder and all modified resources within the tree below this folder
3716                    // and with the last change done in the current project are candidates if lockable
3717                    CmsLock lock = getLock(dbc, directPublishResource);
3718                    if (!directPublishResource.getState().isUnchanged() && lock.isLockableBy(dbc.currentUser())) {
3719
3720                        try {
3721                            m_securityManager.checkPermissions(
3722                                dbc,
3723                                directPublishResource,
3724                                CmsPermissionSet.ACCESS_DIRECT_PUBLISH,
3725                                false,
3726                                CmsResourceFilter.ALL);
3727                            publishList.add(directPublishResource, true);
3728                        } catch (CmsException e) {
3729                            // skip if not enough permissions
3730                        }
3731                    }
3732                    boolean shouldPublishDeletedSubResources = publishList.isUserPublishList()
3733                        && directPublishResource.getState().isDeleted();
3734                    if (publishList.isPublishSubResources() || shouldPublishDeletedSubResources) {
3735                        addSubResources(dbc, publishList, directPublishResource, resource -> true);
3736                    }
3737                } else if (directPublishResource.isFile() && !directPublishResource.getState().isUnchanged()) {
3738
3739                    // when publishing a file directly this file is the only candidate
3740                    // if it is modified and lockable
3741                    CmsLock lock = getLock(dbc, directPublishResource);
3742                    if (lock.isLockableBy(dbc.currentUser())) {
3743                        // check permissions
3744                        try {
3745                            m_securityManager.checkPermissions(
3746                                dbc,
3747                                directPublishResource,
3748                                CmsPermissionSet.ACCESS_DIRECT_PUBLISH,
3749                                false,
3750                                CmsResourceFilter.ALL);
3751                            publishList.add(directPublishResource, true);
3752                        } catch (CmsException e) {
3753                            // skip if not enough permissions
3754                        }
3755                    }
3756                }
3757            }
3758        }
3759
3760        // Step 2: if desired, extend the list of files to publish with related siblings
3761        if (publishList.isPublishSiblings()) {
3762            List<CmsResource> publishFiles = publishList.getFileList();
3763            int size = publishFiles.size();
3764
3765            // Improved: first calculate closure of all siblings, then filter and add them
3766            Set<CmsResource> siblingsClosure = new HashSet<CmsResource>(publishFiles);
3767            for (int i = 0; i < size; i++) {
3768                CmsResource currentFile = publishFiles.get(i);
3769                if (currentFile.getSiblingCount() > 1) {
3770                    siblingsClosure.addAll(readSiblings(dbc, currentFile, CmsResourceFilter.ALL_MODIFIED));
3771                }
3772            }
3773            publishList.addAll(filterSiblings(dbc, publishList, siblingsClosure), true);
3774        }
3775        publishList.initialize();
3776    }
3777
3778    /**
3779     * Returns the list of access control entries of a resource given its name.<p>
3780     *
3781     * @param dbc the current database context
3782     * @param resource the resource to read the access control entries for
3783     * @param getInherited true if the result should include all access control entries inherited by parent folders
3784     *
3785     * @return a list of <code>{@link CmsAccessControlEntry}</code> objects defining all permissions for the given resource
3786     *
3787     * @throws CmsException if something goes wrong
3788     */
3789    public List<CmsAccessControlEntry> getAccessControlEntries(
3790        CmsDbContext dbc,
3791        CmsResource resource,
3792        boolean getInherited)
3793    throws CmsException {
3794
3795        // get the ACE of the resource itself
3796        I_CmsUserDriver userDriver = getUserDriver(dbc);
3797        I_CmsVfsDriver vfsDriver = getVfsDriver(dbc);
3798        List<CmsAccessControlEntry> ace = userDriver.readAccessControlEntries(
3799            dbc,
3800            dbc.currentProject(),
3801            resource.getResourceId(),
3802            false);
3803
3804        // sort and check if we got the 'overwrite all' ace to stop looking up
3805        boolean overwriteAll = sortAceList(ace);
3806
3807        // get the ACE of each parent folder
3808        // Note: for the immediate parent, get non-inherited access control entries too,
3809        // if the resource is not a folder
3810        String parentPath = CmsResource.getParentFolder(resource.getRootPath());
3811        int d = (resource.isFolder()) ? 1 : 0;
3812
3813        while (!overwriteAll && getInherited && (parentPath != null)) {
3814            resource = vfsDriver.readFolder(dbc, dbc.currentProject().getUuid(), parentPath);
3815            List<CmsAccessControlEntry> entries = userDriver.readAccessControlEntries(
3816                dbc,
3817                dbc.currentProject(),
3818                resource.getResourceId(),
3819                d > 0);
3820
3821            // sort and check if we got the 'overwrite all' ace to stop looking up
3822            overwriteAll = sortAceList(entries);
3823
3824            for (CmsAccessControlEntry e : entries) {
3825                e.setFlags(CmsAccessControlEntry.ACCESS_FLAGS_INHERITED);
3826            }
3827
3828            ace.addAll(entries);
3829            parentPath = CmsResource.getParentFolder(resource.getRootPath());
3830            d++;
3831        }
3832
3833        return ace;
3834    }
3835
3836    /**
3837     * Returns the full access control list of a given resource.<p>
3838     *
3839     * @param dbc the current database context
3840     * @param resource the resource
3841     *
3842     * @return the access control list of the resource
3843     *
3844     * @throws CmsException if something goes wrong
3845     */
3846    public CmsAccessControlList getAccessControlList(CmsDbContext dbc, CmsResource resource) throws CmsException {
3847
3848        return getAccessControlList(dbc, resource, false);
3849    }
3850
3851    /**
3852     * Returns the access control list of a given resource.<p>
3853     *
3854     * If <code>inheritedOnly</code> is set, only inherited access control entries
3855     * are returned.<p>
3856     *
3857     * Note: For file resources, *all* permissions set at the immediate parent folder are inherited,
3858     * not only these marked to inherit.
3859     *
3860     * @param dbc the current database context
3861     * @param resource the resource
3862     * @param inheritedOnly skip non-inherited entries if set
3863     *
3864     * @return the access control list of the resource
3865     *
3866     * @throws CmsException if something goes wrong
3867     */
3868    public CmsAccessControlList getAccessControlList(CmsDbContext dbc, CmsResource resource, boolean inheritedOnly)
3869    throws CmsException {
3870
3871        return getAccessControlList(dbc, resource, inheritedOnly, resource.isFolder(), 0);
3872    }
3873
3874    /**
3875     * Returns the number of active connections managed by a pool.<p>
3876     *
3877     * @param dbPoolUrl the url of a pool
3878     * @return the number of active connections
3879     * @throws CmsDbException if something goes wrong
3880     */
3881    public int getActiveConnections(String dbPoolUrl) throws CmsDbException {
3882
3883        CmsDbPoolV11 pool = m_pools.get(dbPoolUrl);
3884        if (pool == null) {
3885            CmsMessageContainer message = Messages.get().container(Messages.ERR_UNKNOWN_POOL_URL_1, dbPoolUrl);
3886            throw new CmsDbException(message);
3887        }
3888        try {
3889            return pool.getActiveConnections();
3890        } catch (Exception exc) {
3891            CmsMessageContainer message = Messages.get().container(Messages.ERR_ACCESSING_POOL_1, dbPoolUrl);
3892            throw new CmsDbException(message, exc);
3893        }
3894
3895    }
3896
3897    /**
3898     * Reads all access control entries.<p>
3899     *
3900     * @param dbc the current database context
3901     * @return all access control entries for the current project (offline/online)
3902     *
3903     * @throws CmsException if something goes wrong
3904     */
3905    public List<CmsAccessControlEntry> getAllAccessControlEntries(CmsDbContext dbc) throws CmsException {
3906
3907        I_CmsUserDriver userDriver = getUserDriver(dbc);
3908        List<CmsAccessControlEntry> ace = userDriver.readAccessControlEntries(
3909            dbc,
3910            dbc.currentProject(),
3911            CmsAccessControlEntry.PRINCIPAL_READALL_ID,
3912            false);
3913        return ace;
3914    }
3915
3916    /**
3917     * Returns all projects which are owned by the current user or which are
3918     * accessible by the current user.<p>
3919     *
3920     * @param dbc the current database context
3921     * @param orgUnit the organizational unit to search project in
3922     * @param includeSubOus if to include sub organizational units
3923     *
3924     * @return a list of objects of type <code>{@link CmsProject}</code>
3925     *
3926     * @throws CmsException if something goes wrong
3927     */
3928    public List<CmsProject> getAllAccessibleProjects(
3929        CmsDbContext dbc,
3930        CmsOrganizationalUnit orgUnit,
3931        boolean includeSubOus)
3932    throws CmsException {
3933
3934        Set<CmsProject> projects = new HashSet<CmsProject>();
3935
3936        // get the ous where the user has the project manager role
3937        List<CmsOrganizationalUnit> ous = getOrgUnitsForRole(
3938            dbc,
3939            CmsRole.PROJECT_MANAGER.forOrgUnit(orgUnit.getName()),
3940            includeSubOus);
3941
3942        // get the groups of the user if needed
3943        Set<CmsUUID> userGroupIds = new HashSet<CmsUUID>();
3944        Iterator<CmsGroup> itGroups = getGroupsOfUser(dbc, dbc.currentUser().getName(), false).iterator();
3945        while (itGroups.hasNext()) {
3946            CmsGroup group = itGroups.next();
3947            userGroupIds.add(group.getId());
3948        }
3949
3950        // TODO: this could be optimize if this method would have an additional parameter 'includeSubOus'
3951        // get all projects that might come in question
3952        projects.addAll(getProjectDriver(dbc).readProjects(dbc, orgUnit.getName()));
3953
3954        // filter hidden and not accessible projects
3955        Iterator<CmsProject> itProjects = projects.iterator();
3956        while (itProjects.hasNext()) {
3957            CmsProject project = itProjects.next();
3958            boolean accessible = true;
3959            // if hidden
3960            accessible = accessible && !project.isHidden();
3961
3962            if (!includeSubOus) {
3963                // if not exact in the given ou
3964                accessible = accessible && project.getOuFqn().equals(orgUnit.getName());
3965            } else {
3966                // if not in the given ou
3967                accessible = accessible && project.getOuFqn().startsWith(orgUnit.getName());
3968            }
3969
3970            if (!accessible) {
3971                itProjects.remove();
3972                continue;
3973            }
3974
3975            accessible = false;
3976            // online project
3977            accessible = accessible || project.isOnlineProject();
3978            // if owner
3979            accessible = accessible || project.getOwnerId().equals(dbc.currentUser().getId());
3980
3981            // project managers
3982            Iterator<CmsOrganizationalUnit> itOus = ous.iterator();
3983            while (!accessible && itOus.hasNext()) {
3984                CmsOrganizationalUnit ou = itOus.next();
3985                // for project managers check visibility
3986                accessible = accessible || project.getOuFqn().startsWith(ou.getName());
3987            }
3988
3989            if (!accessible) {
3990                // if direct user or manager of project
3991                CmsUUID groupId = null;
3992                if (userGroupIds.contains(project.getGroupId())) {
3993                    groupId = project.getGroupId();
3994                } else if (userGroupIds.contains(project.getManagerGroupId())) {
3995                    groupId = project.getManagerGroupId();
3996                }
3997                if (groupId != null) {
3998                    String oufqn = readGroup(dbc, groupId).getOuFqn();
3999                    accessible = accessible || (oufqn.startsWith(dbc.getRequestContext().getOuFqn()));
4000                }
4001            }
4002            if (!accessible) {
4003                // remove not accessible project
4004                itProjects.remove();
4005            }
4006        }
4007
4008        List<CmsProject> accessibleProjects = new ArrayList<CmsProject>(projects);
4009        // sort the list of projects based on the project name
4010        Collections.sort(accessibleProjects);
4011        // ensure the online project is in first place
4012        CmsProject onlineProject = readProject(dbc, CmsProject.ONLINE_PROJECT_ID);
4013        if (accessibleProjects.contains(onlineProject)) {
4014            accessibleProjects.remove(onlineProject);
4015        }
4016        accessibleProjects.add(0, onlineProject);
4017
4018        return accessibleProjects;
4019    }
4020
4021    /**
4022     * Returns a list with all projects from history.<p>
4023     *
4024     * @param dbc the current database context
4025     *
4026     * @return list of <code>{@link CmsHistoryProject}</code> objects
4027     *           with all projects from history.
4028     *
4029     * @throws CmsException if operation was not successful
4030     */
4031    public List<CmsHistoryProject> getAllHistoricalProjects(CmsDbContext dbc) throws CmsException {
4032
4033        // user is allowed to access all existing projects for the ous he has the project_manager role
4034        Set<CmsOrganizationalUnit> manOus = new HashSet<CmsOrganizationalUnit>(
4035            getOrgUnitsForRole(dbc, CmsRole.PROJECT_MANAGER, true));
4036
4037        List<CmsHistoryProject> projects = getHistoryDriver(dbc).readProjects(dbc);
4038        Iterator<CmsHistoryProject> itProjects = projects.iterator();
4039        while (itProjects.hasNext()) {
4040            CmsHistoryProject project = itProjects.next();
4041            if (project.isHidden()) {
4042                // project is hidden
4043                itProjects.remove();
4044                continue;
4045            }
4046            if (!project.getOuFqn().startsWith(dbc.currentUser().getOuFqn())) {
4047                // project is not visible from the users ou
4048                itProjects.remove();
4049                continue;
4050            }
4051            CmsOrganizationalUnit ou = readOrganizationalUnit(dbc, project.getOuFqn());
4052            if (manOus.contains(ou)) {
4053                // user is project manager for this project
4054                continue;
4055            } else if (project.getOwnerId().equals(dbc.currentUser().getId())) {
4056                // user is owner of the project
4057                continue;
4058            } else {
4059                boolean found = false;
4060                Iterator<CmsGroup> itGroups = getGroupsOfUser(dbc, dbc.currentUser().getName(), false).iterator();
4061                while (itGroups.hasNext()) {
4062                    CmsGroup group = itGroups.next();
4063                    if (project.getManagerGroupId().equals(group.getId())) {
4064                        found = true;
4065                        break;
4066                    }
4067                }
4068                if (found) {
4069                    // user is member of the manager group of the project
4070                    continue;
4071                }
4072            }
4073            itProjects.remove();
4074        }
4075        return projects;
4076    }
4077
4078    /**
4079     * Returns all projects which are owned by the current user or which are manageable
4080     * for the group of the user.<p>
4081     *
4082     * @param dbc the current database context
4083     * @param orgUnit the organizational unit to search project in
4084     * @param includeSubOus if to include sub organizational units
4085     *
4086     * @return a list of objects of type <code>{@link CmsProject}</code>
4087     *
4088     * @throws CmsException if operation was not successful
4089     */
4090    public List<CmsProject> getAllManageableProjects(
4091        CmsDbContext dbc,
4092        CmsOrganizationalUnit orgUnit,
4093        boolean includeSubOus)
4094    throws CmsException {
4095
4096        Set<CmsProject> projects = new HashSet<CmsProject>();
4097
4098        // get the ous where the user has the project manager role
4099        List<CmsOrganizationalUnit> ous = getOrgUnitsForRole(
4100            dbc,
4101            CmsRole.PROJECT_MANAGER.forOrgUnit(orgUnit.getName()),
4102            includeSubOus);
4103
4104        // get the groups of the user if needed
4105        Set<CmsUUID> userGroupIds = new HashSet<CmsUUID>();
4106        Iterator<CmsGroup> itGroups = getGroupsOfUser(dbc, dbc.currentUser().getName(), false).iterator();
4107        while (itGroups.hasNext()) {
4108            CmsGroup group = itGroups.next();
4109            userGroupIds.add(group.getId());
4110        }
4111
4112        // TODO: this could be optimize if this method would have an additional parameter 'includeSubOus'
4113        // get all projects that might come in question
4114        projects.addAll(getProjectDriver(dbc).readProjects(dbc, orgUnit.getName()));
4115
4116        // filter hidden and not manageable projects
4117        Iterator<CmsProject> itProjects = projects.iterator();
4118        while (itProjects.hasNext()) {
4119            CmsProject project = itProjects.next();
4120            boolean manageable = true;
4121            // if online
4122            manageable = manageable && !project.isOnlineProject();
4123            // if hidden
4124            manageable = manageable && !project.isHidden();
4125
4126            if (!includeSubOus) {
4127                // if not exact in the given ou
4128                manageable = manageable && project.getOuFqn().equals(orgUnit.getName());
4129            } else {
4130                // if not in the given ou
4131                manageable = manageable && project.getOuFqn().startsWith(orgUnit.getName());
4132            }
4133
4134            if (!manageable) {
4135                itProjects.remove();
4136                continue;
4137            }
4138
4139            manageable = false;
4140            // if owner
4141            manageable = manageable || project.getOwnerId().equals(dbc.currentUser().getId());
4142
4143            // project managers
4144            Iterator<CmsOrganizationalUnit> itOus = ous.iterator();
4145            while (!manageable && itOus.hasNext()) {
4146                CmsOrganizationalUnit ou = itOus.next();
4147                // for project managers check visibility
4148                manageable = manageable || project.getOuFqn().startsWith(ou.getName());
4149            }
4150
4151            if (!manageable) {
4152                // if manager of project
4153                if (userGroupIds.contains(project.getManagerGroupId())) {
4154                    String oufqn = readGroup(dbc, project.getManagerGroupId()).getOuFqn();
4155                    manageable = manageable || (oufqn.startsWith(dbc.getRequestContext().getOuFqn()));
4156                }
4157            }
4158            if (!manageable) {
4159                // remove not accessible project
4160                itProjects.remove();
4161            }
4162        }
4163
4164        List<CmsProject> manageableProjects = new ArrayList<CmsProject>(projects);
4165        // sort the list of projects based on the project name
4166        Collections.sort(manageableProjects);
4167        // ensure the online project is not in the list
4168        CmsProject onlineProject = readProject(dbc, CmsProject.ONLINE_PROJECT_ID);
4169        if (manageableProjects.contains(onlineProject)) {
4170            manageableProjects.remove(onlineProject);
4171        }
4172
4173        return manageableProjects;
4174    }
4175
4176    /**
4177     * Returns all child groups of a group.<p>
4178     *
4179     * @param dbc the current database context
4180     * @param group the group to get the child for
4181     * @param includeSubChildren if set also returns all sub-child groups of the given group
4182     *
4183     * @return a list of all child <code>{@link CmsGroup}</code> objects
4184     *
4185     * @throws CmsException if operation was not successful
4186     */
4187    public List<CmsGroup> getChildren(CmsDbContext dbc, CmsGroup group, boolean includeSubChildren)
4188    throws CmsException {
4189
4190        if (!includeSubChildren) {
4191            return getUserDriver(dbc).readChildGroups(dbc, group.getName());
4192        }
4193        Set<CmsGroup> allChildren = new TreeSet<CmsGroup>();
4194        // iterate all child groups
4195        Iterator<CmsGroup> it = getUserDriver(dbc).readChildGroups(dbc, group.getName()).iterator();
4196        while (it.hasNext()) {
4197            CmsGroup child = it.next();
4198            // add the group itself
4199            allChildren.add(child);
4200            // now get all sub-children for each group
4201            allChildren.addAll(getChildren(dbc, child, true));
4202        }
4203        return new ArrayList<CmsGroup>(allChildren);
4204    }
4205
4206    /**
4207     * Returns the date when the resource was last visited by the user.<p>
4208     *
4209     * @param dbc the database context
4210     * @param poolName the name of the database pool to use
4211     * @param user the user to check the date
4212     * @param resource the resource to check the date
4213     *
4214     * @return the date when the resource was last visited by the user
4215     *
4216     * @throws CmsException if something goes wrong
4217     */
4218    public long getDateLastVisitedBy(CmsDbContext dbc, String poolName, CmsUser user, CmsResource resource)
4219    throws CmsException {
4220
4221        return m_subscriptionDriver.getDateLastVisitedBy(dbc, poolName, user, resource);
4222    }
4223
4224    /**
4225     * Returns all groups of the given organizational unit.<p>
4226     *
4227     * @param dbc the current db context
4228     * @param orgUnit the organizational unit to get the groups for
4229     * @param includeSubOus if all groups of sub-organizational units should be retrieved too
4230     * @param readRoles if to read roles or groups
4231     *
4232     * @return all <code>{@link CmsGroup}</code> objects in the organizational unit
4233     *
4234     * @throws CmsException if operation was not successful
4235     *
4236     * @see org.opencms.security.CmsOrgUnitManager#getResourcesForOrganizationalUnit(CmsObject, String)
4237     * @see org.opencms.security.CmsOrgUnitManager#getGroups(CmsObject, String, boolean)
4238     */
4239    public List<CmsGroup> getGroups(
4240        CmsDbContext dbc,
4241        CmsOrganizationalUnit orgUnit,
4242        boolean includeSubOus,
4243        boolean readRoles)
4244    throws CmsException {
4245
4246        return getUserDriver(dbc).getGroups(dbc, orgUnit, includeSubOus, readRoles);
4247    }
4248
4249    /**
4250     * Returns the groups of an user filtered by the specified IP address.<p>
4251     *
4252     * @param dbc the current database context
4253     * @param username the name of the user
4254     * @param readRoles if to read roles or groups
4255     *
4256     * @return the groups of the given user, as a list of {@link CmsGroup} objects
4257     *
4258     * @throws CmsException if something goes wrong
4259     */
4260    public List<CmsGroup> getGroupsOfUser(CmsDbContext dbc, String username, boolean readRoles) throws CmsException {
4261
4262        return getGroupsOfUser(dbc, username, "", true, readRoles, false, dbc.getRequestContext().getRemoteAddress());
4263    }
4264
4265    /**
4266     * Returns the groups of an user filtered by the specified IP address.<p>
4267     *
4268     * @param dbc the current database context
4269     * @param username the name of the user
4270     * @param ouFqn the fully qualified name of the organizational unit to restrict the result set for
4271     * @param includeChildOus include groups of child organizational units
4272     * @param readRoles if to read roles or groups
4273     * @param directGroupsOnly if set only the direct assigned groups will be returned, if not also indirect groups
4274     * @param remoteAddress the IP address to filter the groups in the result list
4275     *
4276     * @return a list of <code>{@link CmsGroup}</code> objects
4277     *
4278     * @throws CmsException if operation was not successful
4279     */
4280    public List<CmsGroup> getGroupsOfUser(
4281        CmsDbContext dbc,
4282        String username,
4283        String ouFqn,
4284        boolean includeChildOus,
4285        boolean readRoles,
4286        boolean directGroupsOnly,
4287        String remoteAddress)
4288    throws CmsException {
4289
4290        CmsUser user = readUser(dbc, username);
4291        String prefix = ouFqn + "_" + includeChildOus + "_" + directGroupsOnly + "_" + readRoles + "_" + remoteAddress;
4292        String cacheKey = m_keyGenerator.getCacheKeyForUserGroups(prefix, dbc, user);
4293        List<CmsGroup> groups = m_monitor.getCachedUserGroups(user.getId(), cacheKey);
4294        if (groups == null) {
4295            // get all groups of the user
4296            List<CmsGroup> directGroups = getUserDriver(dbc).readGroupsOfUser(
4297                dbc,
4298                user.getId(),
4299                readRoles ? "" : ouFqn,
4300                readRoles ? true : includeChildOus,
4301                remoteAddress,
4302                readRoles);
4303            Set<CmsGroup> allGroups = new HashSet<CmsGroup>();
4304            if (!readRoles) {
4305                allGroups.addAll(directGroups);
4306            }
4307            if (!directGroupsOnly) {
4308                if (!readRoles) {
4309                    // now get all parents of the groups
4310                    for (int i = 0; i < directGroups.size(); i++) {
4311                        CmsGroup parent = getParent(dbc, directGroups.get(i).getName());
4312                        while ((parent != null) && (!allGroups.contains(parent))) {
4313                            if (parent.getOuFqn().startsWith(ouFqn)) {
4314                                allGroups.add(parent);
4315                            }
4316                            // read next parent group
4317                            parent = getParent(dbc, parent.getName());
4318                        }
4319                    }
4320                }
4321            }
4322            if (readRoles) {
4323                // for each for role
4324                for (int i = 0; i < directGroups.size(); i++) {
4325                    CmsGroup group = directGroups.get(i);
4326                    CmsRole role = CmsRole.valueOf(group);
4327                    if (!includeChildOus && role.getOuFqn().equals(ouFqn)) {
4328                        allGroups.add(group);
4329                    }
4330                    if (includeChildOus && role.getOuFqn().startsWith(ouFqn)) {
4331                        allGroups.add(group);
4332                    }
4333                    if (directGroupsOnly || (!includeChildOus && !role.getOuFqn().equals(ouFqn))) {
4334                        // if roles of child OUs are not requested and the role does not belong to the requested OU don't include the role children
4335                        continue;
4336                    }
4337                    CmsOrganizationalUnit currentOu = readOrganizationalUnit(dbc, group.getOuFqn());
4338                    boolean readChildRoleGroups = true;
4339                    if (currentOu.hasFlagWebuser() && role.forOrgUnit(null).equals(CmsRole.ACCOUNT_MANAGER)) {
4340                        readChildRoleGroups = false;
4341                    }
4342                    if (readChildRoleGroups) {
4343                        // get the child roles
4344                        Iterator<CmsRole> itChildRoles = role.getChildren(true).iterator();
4345                        while (itChildRoles.hasNext()) {
4346                            CmsRole childRole = itChildRoles.next();
4347                            if (childRole.isSystemRole()) {
4348                                if (canReadRoleInOu(currentOu, childRole)) {
4349                                    // include system roles only
4350                                    try {
4351                                        allGroups.add(readGroup(dbc, childRole.getGroupName()));
4352                                    } catch (CmsDataAccessException e) {
4353                                        // should not happen, log error if it does
4354                                        LOG.error(e.getLocalizedMessage(), e);
4355                                    }
4356                                }
4357                            }
4358                        }
4359                    } else {
4360                        LOG.info("Skipping child role group check for web user OU " + currentOu.getName());
4361                    }
4362                    if (includeChildOus) {
4363                        // if needed include the roles of child ous
4364                        Iterator<CmsOrganizationalUnit> itSubOus = getOrganizationalUnits(
4365                            dbc,
4366                            readOrganizationalUnit(dbc, group.getOuFqn()),
4367                            true).iterator();
4368                        while (itSubOus.hasNext()) {
4369                            CmsOrganizationalUnit subOu = itSubOus.next();
4370                            // add role in child ou
4371                            try {
4372                                if (canReadRoleInOu(subOu, role)) {
4373                                    allGroups.add(readGroup(dbc, role.forOrgUnit(subOu.getName()).getGroupName()));
4374                                }
4375                            } catch (CmsDbEntryNotFoundException e) {
4376                                // ignore, this may happen while deleting an orgunit
4377                                if (LOG.isDebugEnabled()) {
4378                                    LOG.debug(e.getLocalizedMessage(), e);
4379                                }
4380                            }
4381                            // add child roles in child ous
4382                            Iterator<CmsRole> itChildRoles = role.getChildren(true).iterator();
4383                            while (itChildRoles.hasNext()) {
4384                                CmsRole childRole = itChildRoles.next();
4385                                try {
4386                                    if (canReadRoleInOu(subOu, childRole)) {
4387                                        allGroups.add(
4388                                            readGroup(dbc, childRole.forOrgUnit(subOu.getName()).getGroupName()));
4389                                    }
4390                                } catch (CmsDbEntryNotFoundException e) {
4391                                    // ignore, this may happen while deleting an orgunit
4392                                    if (LOG.isDebugEnabled()) {
4393                                        LOG.debug(e.getLocalizedMessage(), e);
4394                                    }
4395                                }
4396                            }
4397                        }
4398                    }
4399                }
4400            }
4401            // make group list unmodifiable for caching
4402            groups = Collections.unmodifiableList(new ArrayList<CmsGroup>(allGroups));
4403            if (dbc.getProjectId().isNullUUID()) {
4404                m_monitor.getGroupListCache().setGroups(user, cacheKey, groups);
4405            }
4406        }
4407
4408        return groups;
4409    }
4410
4411    /**
4412     * Returns the history driver.<p>
4413     *
4414     * @return the history driver
4415     */
4416    public I_CmsHistoryDriver getHistoryDriver() {
4417
4418        return m_historyDriver;
4419    }
4420
4421    /**
4422     * Returns the history driver for a given database context.<p>
4423     *
4424     * @param dbc the database context
4425     * @return the history driver for the database context
4426     */
4427    public I_CmsHistoryDriver getHistoryDriver(CmsDbContext dbc) {
4428
4429        if ((dbc == null) || (dbc.getProjectId() == null) || dbc.getProjectId().isNullUUID()) {
4430            return m_historyDriver;
4431        }
4432        I_CmsHistoryDriver driver = dbc.getHistoryDriver(dbc.getProjectId());
4433        return driver != null ? driver : m_historyDriver;
4434
4435    }
4436
4437    /**
4438     * Returns the number of idle connections managed by a pool.<p>
4439     *
4440     * @param dbPoolUrl the url of a pool
4441     * @return the number of idle connections
4442     * @throws CmsDbException if something goes wrong
4443     */
4444    public int getIdleConnections(String dbPoolUrl) throws CmsDbException {
4445
4446        CmsDbPoolV11 pool = m_pools.get(dbPoolUrl);
4447        if (pool == null) {
4448            CmsMessageContainer message = Messages.get().container(Messages.ERR_UNKNOWN_POOL_URL_1, dbPoolUrl);
4449            throw new CmsDbException(message);
4450        }
4451        try {
4452            return pool.getIdleConnections();
4453        } catch (Exception exc) {
4454            CmsMessageContainer message = Messages.get().container(Messages.ERR_ACCESSING_POOL_1, dbPoolUrl);
4455            throw new CmsDbException(message, exc);
4456        }
4457
4458    }
4459
4460    /**
4461     * Returns the lock state of a resource.<p>
4462     *
4463     * @param dbc the current database context
4464     * @param resource the resource to return the lock state for
4465     *
4466     * @return the lock state of the resource
4467     *
4468     * @throws CmsException if something goes wrong
4469     */
4470    public CmsLock getLock(CmsDbContext dbc, CmsResource resource) throws CmsException {
4471
4472        return m_lockManager.getLock(dbc, resource);
4473    }
4474
4475    /**
4476     * Returns all locked resources in a given folder.<p>
4477     *
4478     * @param dbc the current database context
4479     * @param resource the folder to search in
4480     * @param filter the lock filter
4481     *
4482     * @return a list of locked resource paths (relative to current site)
4483     *
4484     * @throws CmsException if the current project is locked
4485     */
4486    public List<String> getLockedResources(CmsDbContext dbc, CmsResource resource, CmsLockFilter filter)
4487    throws CmsException {
4488
4489        List<String> lockedResources = new ArrayList<String>();
4490        // get locked resources
4491        Iterator<CmsLock> it = m_lockManager.getLocks(dbc, resource.getRootPath(), filter).iterator();
4492        while (it.hasNext()) {
4493            CmsLock lock = it.next();
4494            lockedResources.add(dbc.removeSiteRoot(lock.getResourceName()));
4495        }
4496        Collections.sort(lockedResources);
4497        return lockedResources;
4498    }
4499
4500    /**
4501     * Returns all locked resources in a given folder.<p>
4502     *
4503     * @param dbc the current database context
4504     * @param resource the folder to search in
4505     * @param filter the lock filter
4506     *
4507     * @return a list of locked resources
4508     *
4509     * @throws CmsException if the current project is locked
4510     */
4511    public List<CmsResource> getLockedResourcesObjects(CmsDbContext dbc, CmsResource resource, CmsLockFilter filter)
4512    throws CmsException {
4513
4514        return m_lockManager.getLockedResources(dbc, resource, filter);
4515    }
4516
4517    /**
4518     * Returns all locked resources in a given folder, but uses a cache for resource lookups.<p>
4519     *
4520     * @param dbc the current database context
4521     * @param resource the folder to search in
4522     * @param filter the lock filter
4523     * @param cache the cache to use for resource lookups
4524     *
4525     * @return a list of locked resources
4526     *
4527     * @throws CmsException if the current project is locked
4528     */
4529    public List<CmsResource> getLockedResourcesObjectsWithCache(
4530        CmsDbContext dbc,
4531        CmsResource resource,
4532        CmsLockFilter filter,
4533        Map<String, CmsResource> cache)
4534    throws CmsException {
4535
4536        return m_lockManager.getLockedResourcesWithCache(dbc, resource, filter, cache);
4537    }
4538
4539    /**
4540     * Returns all log entries matching the given filter.<p>
4541     *
4542     * @param dbc the current db context
4543     * @param filter the filter to match the log entries
4544     *
4545     * @return all log entries matching the given filter
4546     *
4547     * @throws CmsException if something goes wrong
4548     *
4549     * @see CmsSecurityManager#getLogEntries(CmsRequestContext, CmsLogFilter)
4550     */
4551    public List<CmsLogEntry> getLogEntries(CmsDbContext dbc, CmsLogFilter filter) throws CmsException {
4552
4553        updateLog(dbc);
4554        return m_projectDriver.readLog(dbc, filter);
4555    }
4556
4557    /**
4558     * Returns the next publish tag for the published historical resources.<p>
4559     *
4560     * @param dbc the current database context
4561     *
4562     * @return the next available publish tag
4563     */
4564    public int getNextPublishTag(CmsDbContext dbc) {
4565
4566        if (dbc.getProjectId().isNullUUID()) {
4567            synchronized (m_publishTagLock) {
4568                int dbNextPublishTag = getHistoryDriver(dbc).readNextPublishTag(dbc);
4569                if (m_lastPublishTag == null) {
4570                    m_lastPublishTag = Integer.valueOf(dbNextPublishTag);
4571                    return dbNextPublishTag;
4572                } else {
4573                    int newValue = Math.max(dbNextPublishTag, m_lastPublishTag.intValue() + 1);
4574                    m_lastPublishTag = Integer.valueOf(newValue);
4575                    return newValue;
4576
4577                }
4578            }
4579        } else {
4580            return getHistoryDriver(dbc).readNextPublishTag(dbc);
4581        }
4582
4583    }
4584
4585    /**
4586     * Returns all child organizational units of the given parent organizational unit including
4587     * hierarchical deeper organization units if needed.<p>
4588     *
4589     * @param dbc the current db context
4590     * @param parent the parent organizational unit, or <code>null</code> for the root
4591     * @param includeChildren if hierarchical deeper organization units should also be returned
4592     *
4593     * @return a list of <code>{@link CmsOrganizationalUnit}</code> objects
4594     *
4595     * @throws CmsException if operation was not successful
4596     *
4597     * @see org.opencms.security.CmsOrgUnitManager#getOrganizationalUnits(CmsObject, String, boolean)
4598     */
4599    public List<CmsOrganizationalUnit> getOrganizationalUnits(
4600        CmsDbContext dbc,
4601        CmsOrganizationalUnit parent,
4602        boolean includeChildren)
4603    throws CmsException {
4604
4605        if (parent == null) {
4606            throw new CmsIllegalArgumentException(Messages.get().container(Messages.ERR_PARENT_ORGUNIT_NULL_0));
4607        }
4608        return getUserDriver(dbc).getOrganizationalUnits(dbc, parent, includeChildren);
4609    }
4610
4611    /**
4612     * Returns all the organizational units for which the current user has the given role.<p>
4613     *
4614     * @param dbc the current database context
4615     * @param role the role to check
4616     * @param includeSubOus if sub organizational units should be included in the search
4617     *
4618     * @return a list of {@link org.opencms.security.CmsOrganizationalUnit} objects
4619     *
4620     * @throws CmsException if something goes wrong
4621     */
4622    public List<CmsOrganizationalUnit> getOrgUnitsForRole(CmsDbContext dbc, CmsRole role, boolean includeSubOus)
4623    throws CmsException {
4624
4625        String ouFqn = role.getOuFqn();
4626        if (ouFqn == null) {
4627            ouFqn = "";
4628            role = role.forOrgUnit("");
4629        }
4630        CmsOrganizationalUnit ou = readOrganizationalUnit(dbc, ouFqn);
4631        List<CmsOrganizationalUnit> orgUnits = new ArrayList<CmsOrganizationalUnit>();
4632        if (m_securityManager.hasRole(dbc, dbc.currentUser(), role)) {
4633            orgUnits.add(ou);
4634        }
4635        if (includeSubOus) {
4636            Iterator<CmsOrganizationalUnit> it = getOrganizationalUnits(dbc, ou, true).iterator();
4637            while (it.hasNext()) {
4638                CmsOrganizationalUnit orgUnit = it.next();
4639                if (m_securityManager.hasRole(dbc, dbc.currentUser(), role.forOrgUnit(orgUnit.getName()))) {
4640                    orgUnits.add(orgUnit);
4641                }
4642            }
4643        }
4644        return orgUnits;
4645    }
4646
4647    /**
4648     * Returns the parent group of a group.<p>
4649     *
4650     * @param dbc the current database context
4651     * @param groupname the name of the group
4652     *
4653     * @return group the parent group or <code>null</code>
4654     *
4655     * @throws CmsException if operation was not successful
4656     */
4657    public CmsGroup getParent(CmsDbContext dbc, String groupname) throws CmsException {
4658
4659        CmsGroup group = readGroup(dbc, groupname);
4660        if (group.getParentId().isNullUUID()) {
4661            return null;
4662        }
4663
4664        // try to read from cache
4665        CmsGroup parent = m_monitor.getCachedGroup(group.getParentId().toString());
4666        if (parent == null) {
4667            parent = getUserDriver(dbc).readGroup(dbc, group.getParentId());
4668            m_monitor.cacheGroup(parent);
4669        }
4670        return parent;
4671    }
4672
4673    /**
4674     * Returns the set of permissions of the current user for a given resource.<p>
4675     *
4676     * @param dbc the current database context
4677     * @param resource the resource
4678     * @param user the user
4679     *
4680     * @return bit set with allowed permissions
4681     *
4682     * @throws CmsException if something goes wrong
4683     */
4684    public CmsPermissionSetCustom getPermissions(CmsDbContext dbc, CmsResource resource, CmsUser user)
4685    throws CmsException {
4686
4687        CmsAccessControlList acList = getAccessControlList(dbc, resource, false);
4688        List<CmsGroup> groups = getGroupsOfUser(dbc, user.getName(), false);
4689        List<CmsRole> roles = getRolesForUser(dbc, user);
4690        CmsPermissionSetCustom permissions = acList.getPermissions(user, groups, roles);
4691
4692        if (acList.getExclusiveAccessPrincipals().size() > 0) {
4693            long now;
4694            @SuppressWarnings("unchecked")
4695            Supplier<Long> alternativeClock = (Supplier<Long>)(dbc.getRequestContext().getAttribute(
4696                ATTR_EXCLUSIVE_ACCESS_CLOCK));
4697            if (alternativeClock != null) {
4698                // used for testing
4699                now = alternativeClock.get().longValue();
4700            } else {
4701                // *NOT* using dbc.getRequestContext().getRequestTime(), even though that value is used for normal resource availability checks, because
4702                // that value may be manipulated by some workplace classes, and we want the real time for permission checks
4703                now = System.currentTimeMillis();
4704            }
4705            permissions.setCacheable(false); // resources going in/out of availability can change permissions - don't cache
4706            if (!resource.isReleasedAndNotExpired(now)) {
4707                boolean hasExclusiveAccess = false;
4708                for (CmsGroup group : groups) {
4709                    if (acList.getExclusiveAccessPrincipals().contains(group.getId())) {
4710                        hasExclusiveAccess = true;
4711                        break;
4712                    }
4713                }
4714                hasExclusiveAccess |= acList.getExclusiveAccessPrincipals().contains(user.getId());
4715                if (!hasExclusiveAccess) {
4716                    permissions.denyPermissions(CmsPermissionSet.PERMISSION_FULL);
4717                }
4718            }
4719        }
4720        return permissions;
4721    }
4722
4723    /**
4724     * Returns the project driver.<p>
4725     *
4726     * @return the project driver
4727     */
4728    public I_CmsProjectDriver getProjectDriver() {
4729
4730        return m_projectDriver;
4731    }
4732
4733    /**
4734     * Returns the project driver for a given DB context.<p>
4735     *
4736     * @param dbc the database context
4737     *
4738     * @return the project driver for the database context
4739     */
4740    public I_CmsProjectDriver getProjectDriver(CmsDbContext dbc) {
4741
4742        if ((dbc == null) || (dbc.getProjectId() == null) || dbc.getProjectId().isNullUUID()) {
4743            return m_projectDriver;
4744        }
4745        I_CmsProjectDriver driver = dbc.getProjectDriver(dbc.getProjectId());
4746        return driver != null ? driver : m_projectDriver;
4747    }
4748
4749    /**
4750     * Returns either the project driver for the DB context (if it has one) or a default project driver.<p>
4751     *
4752     * @param dbc the DB context
4753     * @param defaultDriver the driver which should be returned if there is no project driver for the DB context
4754     *
4755     * @return either the project driver for the DB context, or the default driver
4756     */
4757    public I_CmsProjectDriver getProjectDriver(CmsDbContext dbc, I_CmsProjectDriver defaultDriver) {
4758
4759        if ((dbc == null) || (dbc.getProjectId() == null) || dbc.getProjectId().isNullUUID()) {
4760            return defaultDriver;
4761        }
4762        I_CmsProjectDriver driver = dbc.getProjectDriver(dbc.getProjectId());
4763        return driver != null ? driver : defaultDriver;
4764    }
4765
4766    /**
4767     * Returns the uuid id for the given id.<p>
4768     *
4769     * TODO: remove this method as soon as possible
4770     *
4771     * @param dbc the current database context
4772     * @param id the old project id
4773     *
4774     * @return the new uuid for the given id
4775     *
4776     * @throws CmsException if something goes wrong
4777     */
4778    public CmsUUID getProjectId(CmsDbContext dbc, int id) throws CmsException {
4779
4780        Iterator<CmsProject> itProjects = getAllAccessibleProjects(
4781            dbc,
4782            readOrganizationalUnit(dbc, ""),
4783            true).iterator();
4784        while (itProjects.hasNext()) {
4785            CmsProject project = itProjects.next();
4786            if (project.getUuid().hashCode() == id) {
4787                return project.getUuid();
4788            }
4789        }
4790        return null;
4791    }
4792
4793    /**
4794     * Returns the configuration read from the <code>opencms.properties</code> file.<p>
4795     *
4796     * @return the configuration read from the <code>opencms.properties</code> file
4797     */
4798    public CmsParameterConfiguration getPropertyConfiguration() {
4799
4800        return m_propertyConfiguration;
4801    }
4802
4803    /**
4804     * Returns a new publish list that contains the unpublished resources related
4805     * to all resources in the given publish list, the related resources exclude
4806     * all resources in the given publish list and also locked (by other users) resources.<p>
4807     *
4808     * @param dbc the current database context
4809     * @param publishList the publish list to exclude from result
4810     * @param filter the relation filter to use to get the related resources
4811     *
4812     * @return a new publish list that contains the related resources
4813     *
4814     * @throws CmsException if something goes wrong
4815     *
4816     * @see org.opencms.publish.CmsPublishManager#getRelatedResourcesToPublish(CmsObject, CmsPublishList)
4817     */
4818    public CmsPublishList getRelatedResourcesToPublish(
4819        CmsDbContext dbc,
4820        CmsPublishList publishList,
4821        CmsRelationFilter filter)
4822    throws CmsException {
4823
4824        Map<String, CmsResource> relations = new HashMap<String, CmsResource>();
4825
4826        // check if progress should be set in the thread
4827        A_CmsProgressThread thread = null;
4828        if (Thread.currentThread() instanceof A_CmsProgressThread) {
4829            thread = (A_CmsProgressThread)Thread.currentThread();
4830        }
4831
4832        // get all resources to publish
4833        List<CmsResource> publishResources = publishList.getAllResources();
4834        Iterator<CmsResource> itCheckList = publishResources.iterator();
4835        // iterate over them
4836        int count = 0;
4837        while (itCheckList.hasNext()) {
4838
4839            // set progress in thread
4840            count++;
4841            if (thread != null) {
4842
4843                if (thread.isInterrupted()) {
4844                    throw new CmsIllegalStateException(
4845                        org.opencms.workplace.commons.Messages.get().container(
4846                            org.opencms.workplace.commons.Messages.ERR_PROGRESS_INTERRUPTED_0));
4847                }
4848                thread.setProgress((count * 20) / publishResources.size());
4849                thread.setDescription(
4850                    org.opencms.workplace.commons.Messages.get().getBundle().key(
4851                        org.opencms.workplace.commons.Messages.GUI_PROGRESS_PUBLISH_STEP1_2,
4852                        Integer.valueOf(count),
4853                        Integer.valueOf(publishResources.size())));
4854            }
4855
4856            CmsResource checkResource = itCheckList.next();
4857            // get and iterate over all related resources
4858            Iterator<CmsRelation> itRelations = getRelationsForResource(dbc, checkResource, filter).iterator();
4859            while (itRelations.hasNext()) {
4860                CmsRelation relation = itRelations.next();
4861                try {
4862                    // get the target of the relation, see CmsRelation#getTarget(CmsObject, CmsResourceFilter)
4863                    CmsResource target;
4864                    try {
4865                        // first look up by id
4866                        target = readResource(dbc, relation.getTargetId(), CmsResourceFilter.ALL);
4867                    } catch (CmsVfsResourceNotFoundException e) {
4868                        // then look up by name, but from the root site
4869                        String storedSiteRoot = dbc.getRequestContext().getSiteRoot();
4870                        try {
4871                            dbc.getRequestContext().setSiteRoot("");
4872                            target = readResource(dbc, relation.getTargetPath(), CmsResourceFilter.ALL);
4873                        } finally {
4874                            dbc.getRequestContext().setSiteRoot(storedSiteRoot);
4875                        }
4876                    }
4877                    CmsLock lock = getLock(dbc, target);
4878                    // just add resources that may come in question
4879                    if (!publishResources.contains(target) // is not in the original list
4880                        && !relations.containsKey(target.getRootPath()) // has not been already added by another relation
4881                        && !target.getState().isUnchanged() // has been changed
4882                        && lock.isLockableBy(dbc.currentUser())) { // is lockable by current user
4883
4884                        relations.put(target.getRootPath(), target);
4885                        // now check the folder structure
4886                        CmsResource parent = getVfsDriver(dbc).readParentFolder(
4887                            dbc,
4888                            dbc.currentProject().getUuid(),
4889                            target.getStructureId());
4890                        while ((parent != null) && parent.getState().isNew()) {
4891                            // just add resources that may come in question
4892                            if (!publishResources.contains(parent) // is not in the original list
4893                                && !relations.containsKey(parent.getRootPath())) { // has not been already added by another relation
4894
4895                                relations.put(parent.getRootPath(), parent);
4896                            }
4897                            parent = getVfsDriver(dbc).readParentFolder(
4898                                dbc,
4899                                dbc.currentProject().getUuid(),
4900                                parent.getStructureId());
4901                        }
4902                    }
4903                } catch (CmsVfsResourceNotFoundException e) {
4904                    // ignore broken links
4905                    if (LOG.isDebugEnabled()) {
4906                        LOG.debug(e.getLocalizedMessage(), e);
4907                    }
4908                }
4909            }
4910        }
4911
4912        CmsPublishList ret = new CmsPublishList(publishList.getDirectPublishResources(), false, false);
4913        ret.addAll(relations.values(), false);
4914        ret.initialize();
4915        return ret;
4916    }
4917
4918    /**
4919     * Returns all relations for the given resource matching the given filter.<p>
4920     *
4921     * @param dbc the current db context
4922     * @param resource the resource to retrieve the relations for
4923     * @param filter the filter to match the relation
4924     *
4925     * @return all relations for the given resource matching the given filter
4926     *
4927     * @throws CmsException if something goes wrong
4928     *
4929     * @see CmsSecurityManager#getRelationsForResource(CmsRequestContext, CmsResource, CmsRelationFilter)
4930     */
4931    public List<CmsRelation> getRelationsForResource(CmsDbContext dbc, CmsResource resource, CmsRelationFilter filter)
4932    throws CmsException {
4933
4934        CmsUUID projectId = getProjectIdForContext(dbc);
4935        return getVfsDriver(dbc).readRelations(dbc, projectId, resource, filter);
4936    }
4937
4938    /**
4939     * Returns the list of organizational units the given resource belongs to.<p>
4940     *
4941     * @param dbc the current database context
4942     * @param resource the resource
4943     *
4944     * @return list of {@link CmsOrganizationalUnit} objects
4945     *
4946     * @throws CmsException if something goes wrong
4947     */
4948    public List<CmsOrganizationalUnit> getResourceOrgUnits(CmsDbContext dbc, CmsResource resource) throws CmsException {
4949
4950        boolean nullDbcProjectId = (dbc.getProjectId() == null) || dbc.getProjectId().isNullUUID();
4951        if (nullDbcProjectId && resourceOrgUnitCachingEnabled) {
4952            try {
4953                return m_monitor.getResourceOuCache().get(new ResourceOUCacheKey(this, dbc)).getResourceOrgUnits(
4954                    resource.getRootPath());
4955            } catch (ExecutionException e) {
4956                LOG.error(e.getLocalizedMessage(), e);
4957            }
4958        }
4959        List<CmsOrganizationalUnit> result = getVfsDriver(dbc).getResourceOus(
4960            dbc,
4961            dbc.currentProject().getUuid(),
4962            resource);
4963
4964        return result;
4965    }
4966
4967    /**
4968     * Returns all resources of the given organizational unit.<p>
4969     *
4970     * @param dbc the current db context
4971     * @param orgUnit the organizational unit to get all resources for
4972     *
4973     * @return all <code>{@link CmsResource}</code> objects in the organizational unit
4974     *
4975     * @throws CmsException if operation was not successful
4976     *
4977     * @see org.opencms.security.CmsOrgUnitManager#getResourcesForOrganizationalUnit(CmsObject, String)
4978     * @see org.opencms.security.CmsOrgUnitManager#getUsers(CmsObject, String, boolean)
4979     * @see org.opencms.security.CmsOrgUnitManager#getGroups(CmsObject, String, boolean)
4980     */
4981    public List<CmsResource> getResourcesForOrganizationalUnit(CmsDbContext dbc, CmsOrganizationalUnit orgUnit)
4982    throws CmsException {
4983
4984        return getUserDriver(dbc).getResourcesForOrganizationalUnit(dbc, orgUnit);
4985    }
4986
4987    /**
4988     * Returns all resources associated to a given principal via an ACE with the given permissions.<p>
4989     *
4990     * If the <code>includeAttr</code> flag is set it returns also all resources associated to
4991     * a given principal through some of following attributes.<p>
4992     *
4993     * <ul>
4994     *    <li>User Created</li>
4995     *    <li>User Last Modified</li>
4996     * </ul><p>
4997     *
4998     * @param dbc the current database context
4999     * @param project the to read the entries from
5000     * @param principalId the id of the principal
5001     * @param permissions a set of permissions to match, can be <code>null</code> for all ACEs
5002     * @param includeAttr a flag to include resources associated by attributes
5003     *
5004     * @return a set of <code>{@link CmsResource}</code> objects
5005     *
5006     * @throws CmsException if something goes wrong
5007     */
5008    public Set<CmsResource> getResourcesForPrincipal(
5009        CmsDbContext dbc,
5010        CmsProject project,
5011        CmsUUID principalId,
5012        CmsPermissionSet permissions,
5013        boolean includeAttr)
5014    throws CmsException {
5015
5016        Set<CmsResource> resources = new HashSet<CmsResource>(
5017            getVfsDriver(dbc).readResourcesForPrincipalACE(dbc, project, principalId));
5018        if (permissions != null) {
5019            Iterator<CmsResource> itRes = resources.iterator();
5020            while (itRes.hasNext()) {
5021                CmsAccessControlEntry ace = readAccessControlEntry(dbc, itRes.next(), principalId);
5022                if ((ace.getPermissions().getPermissions()
5023                    & permissions.getPermissions()) != permissions.getPermissions()) {
5024                    // remove if permissions does not match
5025                    itRes.remove();
5026                }
5027            }
5028        }
5029        if (includeAttr) {
5030            resources.addAll(getVfsDriver(dbc).readResourcesForPrincipalAttr(dbc, project, principalId));
5031        }
5032        return resources;
5033    }
5034
5035    /**
5036     * Gets the rewrite aliases matching a given filter.<p>
5037     *
5038     * @param dbc the current database context
5039     * @param filter the filter used for filtering rewrite aliases
5040     *
5041     * @return the rewrite aliases matching the given filter
5042     *
5043     * @throws CmsException if something goes wrong
5044     */
5045    public List<CmsRewriteAlias> getRewriteAliases(CmsDbContext dbc, CmsRewriteAliasFilter filter) throws CmsException {
5046
5047        return getVfsDriver(dbc).readRewriteAliases(dbc, filter);
5048    }
5049
5050    /**
5051     * Collects the groups which constitute a given role.<p>
5052     *
5053     * @param dbc the database context
5054     * @param roleGroupName the group related to the role
5055     * @param directUsersOnly if true, only the group belonging to the entry itself wil
5056     *
5057     * @return the set of groups which constitute the role
5058     *
5059     * @throws CmsException if something goes wrong
5060     */
5061    public Set<CmsGroup> getRoleGroups(CmsDbContext dbc, String roleGroupName, boolean directUsersOnly)
5062    throws CmsException {
5063
5064        return getRoleGroupsImpl(dbc, roleGroupName, directUsersOnly, new HashMap<String, Set<CmsGroup>>());
5065    }
5066
5067    /**
5068     * Collects the groups which constitute a given role.<p>
5069     *
5070     * @param dbc the database context
5071     * @param roleGroupName the group related to the role
5072     * @param directUsersOnly if true, only the group belonging to the entry itself wil
5073     * @param accumulator a map for memoizing return values of recursive calls
5074     *
5075     * @return the set of groups which constitute the role
5076     *
5077     * @throws CmsException if something goes wrong
5078     */
5079    public Set<CmsGroup> getRoleGroupsImpl(
5080        CmsDbContext dbc,
5081        String roleGroupName,
5082        boolean directUsersOnly,
5083        Map<String, Set<CmsGroup>> accumulator)
5084    throws CmsException {
5085
5086        Set<CmsGroup> result = new HashSet<CmsGroup>();
5087        if (accumulator.get(roleGroupName) != null) {
5088            return accumulator.get(roleGroupName);
5089        }
5090        CmsGroup group = readGroup(dbc, roleGroupName); // check that the group really exists
5091        if ((group == null) || (!group.isRole())) {
5092            throw new CmsDbEntryNotFoundException(
5093                Messages.get().container(Messages.ERR_UNKNOWN_GROUP_1, roleGroupName));
5094        }
5095        result.add(group);
5096        if (!directUsersOnly) {
5097            CmsRole role = CmsRole.valueOf(group);
5098            if (role.getParentRole() != null) {
5099                try {
5100                    String parentGroup = role.getParentRole().getGroupName();
5101                    // iterate the parent roles
5102                    result.addAll(getRoleGroupsImpl(dbc, parentGroup, directUsersOnly, accumulator));
5103                } catch (CmsDbEntryNotFoundException e) {
5104                    // ignore, this may happen while deleting an orgunit
5105                    if (LOG.isDebugEnabled()) {
5106                        LOG.debug(e.getLocalizedMessage(), e);
5107                    }
5108                }
5109            }
5110            String parentOu = CmsOrganizationalUnit.getParentFqn(group.getOuFqn());
5111            if (parentOu != null) {
5112                // iterate the parent ou's
5113                result.addAll(getRoleGroupsImpl(dbc, parentOu + group.getSimpleName(), directUsersOnly, accumulator));
5114            }
5115        }
5116        accumulator.put(roleGroupName, result);
5117        return result;
5118    }
5119
5120    /**
5121     * Returns all roles the given user has for the given resource.<p>
5122     *
5123     * @param dbc the current database context
5124     * @param user the user to check
5125     * @param resource the resource to check the roles for
5126     *
5127     * @return a list of {@link CmsRole} objects
5128     *
5129     * @throws CmsException if something goes wrong
5130     */
5131    public List<CmsRole> getRolesForResource(CmsDbContext dbc, CmsUser user, CmsResource resource) throws CmsException {
5132
5133        // guest user has no role
5134        if (user.isGuestUser()) {
5135            return Collections.emptyList();
5136        }
5137
5138        // try to read from cache
5139        String key = user.getId().toString() + resource.getRootPath();
5140        List<CmsRole> result = m_monitor.getCachedRoleList(key);
5141        if (result != null) {
5142            return result;
5143        }
5144        result = new ArrayList<CmsRole>();
5145
5146        Iterator<CmsOrganizationalUnit> itOus = getResourceOrgUnits(dbc, resource).iterator();
5147        while (itOus.hasNext()) {
5148            CmsOrganizationalUnit ou = itOus.next();
5149
5150            // read all roles of the current user
5151            List<CmsGroup> groups = new ArrayList<CmsGroup>(
5152                getGroupsOfUser(
5153                    dbc,
5154                    user.getName(),
5155                    ou.getName(),
5156                    false,
5157                    true,
5158                    false,
5159                    dbc.getRequestContext().getRemoteAddress()));
5160            // check the roles applying to the given resource
5161            Iterator<CmsGroup> it = groups.iterator();
5162            while (it.hasNext()) {
5163                CmsGroup group = it.next();
5164                CmsRole givenRole = CmsRole.valueOf(group).forOrgUnit(null);
5165                if (givenRole.isOrganizationalUnitIndependent() || result.contains(givenRole)) {
5166                    // skip already added roles
5167                    continue;
5168                }
5169                result.add(givenRole);
5170            }
5171        }
5172
5173        result = Collections.unmodifiableList(result);
5174        m_monitor.cacheRoleList(key, result);
5175        return result;
5176    }
5177
5178    /**
5179     * Returns all roles the given user has independent of the resource.<p>
5180     *
5181     * @param dbc the current database context
5182     * @param user the user to check
5183     *
5184     * @return a list of {@link CmsRole} objects
5185     *
5186     * @throws CmsException if something goes wrong
5187     */
5188    public List<CmsRole> getRolesForUser(CmsDbContext dbc, CmsUser user) throws CmsException {
5189
5190        // guest user has no role
5191        if (user.isGuestUser()) {
5192            return Collections.emptyList();
5193        }
5194
5195        // try to read from cache
5196        List<CmsRole> result = m_monitor.getGroupListCache().getBareRoles(user.getId());
5197        if (result != null) {
5198            return result;
5199        }
5200        result = new ArrayList<CmsRole>();
5201
5202        // read all roles of the current user
5203        List<CmsGroup> groups = new ArrayList<CmsGroup>(
5204            getGroupsOfUser(dbc, user.getName(), "", true, true, false, dbc.getRequestContext().getRemoteAddress()));
5205
5206        // check the roles applying to the given resource
5207        Iterator<CmsGroup> it = groups.iterator();
5208        while (it.hasNext()) {
5209            CmsGroup group = it.next();
5210            CmsRole givenRole = CmsRole.valueOf(group);
5211            givenRole = givenRole.forOrgUnit(null);
5212            if (!result.contains(givenRole)) {
5213                result.add(givenRole);
5214            }
5215        }
5216        result = Collections.unmodifiableList(result);
5217        m_monitor.getGroupListCache().setBareRoles(user, result);
5218        return result;
5219    }
5220
5221    /**
5222     * Returns the security manager this driver manager belongs to.<p>
5223     *
5224     * @return the security manager this driver manager belongs to
5225     */
5226    public CmsSecurityManager getSecurityManager() {
5227
5228        return m_securityManager;
5229    }
5230
5231    /**
5232     * Returns an instance of the common sql manager.<p>
5233     *
5234     * @return an instance of the common sql manager
5235     */
5236    public CmsSqlManager getSqlManager() {
5237
5238        return m_sqlManager;
5239    }
5240
5241    /**
5242     * Returns the subscription driver of this driver manager.<p>
5243     *
5244     * @return a subscription driver
5245     */
5246    public I_CmsSubscriptionDriver getSubscriptionDriver() {
5247
5248        return m_subscriptionDriver;
5249    }
5250
5251    /**
5252     * Returns the user driver.<p>
5253     *
5254     * @return the user driver
5255     */
5256    public I_CmsUserDriver getUserDriver() {
5257
5258        return m_userDriver;
5259    }
5260
5261    /**
5262     * Returns the user driver for a given database context.<p>
5263     *
5264     * @param dbc the database context
5265     *
5266     * @return the user driver for the database context
5267     */
5268    public I_CmsUserDriver getUserDriver(CmsDbContext dbc) {
5269
5270        if ((dbc == null) || (dbc.getProjectId() == null) || dbc.getProjectId().isNullUUID()) {
5271            return m_userDriver;
5272        }
5273        I_CmsUserDriver driver = dbc.getUserDriver(dbc.getProjectId());
5274        return driver != null ? driver : m_userDriver;
5275
5276    }
5277
5278    /**
5279     * Returns either the user driver for the given DB context (if it has one) or a default value instead.<p>
5280     *
5281     * @param dbc the DB context
5282     * @param defaultDriver the driver that should be returned if no driver for the DB context was found
5283     *
5284     * @return either the user driver for the DB context, or <code>defaultDriver</code> if none were found
5285     */
5286    public I_CmsUserDriver getUserDriver(CmsDbContext dbc, I_CmsUserDriver defaultDriver) {
5287
5288        if ((dbc == null) || (dbc.getProjectId() == null) || dbc.getProjectId().isNullUUID()) {
5289            return defaultDriver;
5290        }
5291        I_CmsUserDriver driver = dbc.getUserDriver(dbc.getProjectId());
5292        return driver != null ? driver : defaultDriver;
5293    }
5294
5295    /**
5296     * Returns all direct users of the given organizational unit.<p>
5297     *
5298     * @param dbc the current db context
5299     * @param orgUnit the organizational unit to get all users for
5300     * @param recursive if all groups of sub-organizational units should be retrieved too
5301     *
5302     * @return all <code>{@link CmsUser}</code> objects in the organizational unit
5303     *
5304     * @throws CmsException if operation was not successful
5305     *
5306     * @see org.opencms.security.CmsOrgUnitManager#getResourcesForOrganizationalUnit(CmsObject, String)
5307     * @see org.opencms.security.CmsOrgUnitManager#getUsers(CmsObject, String, boolean)
5308     */
5309    public List<CmsUser> getUsers(CmsDbContext dbc, CmsOrganizationalUnit orgUnit, boolean recursive)
5310    throws CmsException {
5311
5312        return getUserDriver(dbc).getUsers(dbc, orgUnit, recursive);
5313    }
5314
5315    /**
5316     * Returns a list of users in a group.<p>
5317     *
5318     * @param dbc the current database context
5319     * @param groupname the name of the group to list users from
5320     * @param includeOtherOuUsers include users of other organizational units
5321     * @param directUsersOnly if set only the direct assigned users will be returned,
5322     *                        if not also indirect users, ie. members of parent roles,
5323     *                        this parameter only works with roles
5324     * @param readRoles if to read roles or groups
5325     *
5326     * @return all <code>{@link CmsUser}</code> objects in the group
5327     *
5328     * @throws CmsException if operation was not successful
5329     */
5330    public List<CmsUser> getUsersOfGroup(
5331        CmsDbContext dbc,
5332        String groupname,
5333        boolean includeOtherOuUsers,
5334        boolean directUsersOnly,
5335        boolean readRoles)
5336    throws CmsException {
5337
5338        return internalUsersOfGroup(
5339            dbc,
5340            CmsOrganizationalUnit.getParentFqn(groupname),
5341            groupname,
5342            includeOtherOuUsers,
5343            directUsersOnly,
5344            readRoles);
5345    }
5346
5347    /**
5348     * Returns the given user's publish list.<p>
5349     *
5350     * @param dbc the database context
5351     * @param userId the user's id
5352     *
5353     * @return the given user's publish list
5354     *
5355     * @throws CmsDataAccessException if something goes wrong
5356     */
5357    public List<CmsResource> getUsersPubList(CmsDbContext dbc, CmsUUID userId) throws CmsDataAccessException {
5358
5359        synchronized (m_publishListUpdateLock) {
5360            updateLog(dbc);
5361            return m_projectDriver.getUsersPubList(dbc, userId);
5362        }
5363    }
5364
5365    /**
5366     * Returns all direct users of the given organizational unit, without their additional info.<p>
5367     *
5368     * @param dbc the current db context
5369     * @param orgUnit the organizational unit to get all users for
5370     * @param recursive if all groups of sub-organizational units should be retrieved too
5371     *
5372     * @return all <code>{@link CmsUser}</code> objects in the organizational unit
5373     *
5374     * @throws CmsException if operation was not successful
5375     *
5376     * @see org.opencms.security.CmsOrgUnitManager#getResourcesForOrganizationalUnit(CmsObject, String)
5377     * @see org.opencms.security.CmsOrgUnitManager#getUsers(CmsObject, String, boolean)
5378     */
5379    public List<CmsUser> getUsersWithoutAdditionalInfo(
5380        CmsDbContext dbc,
5381        CmsOrganizationalUnit orgUnit,
5382        boolean recursive)
5383    throws CmsException {
5384
5385        return getUserDriver(dbc).getUsersWithoutAdditionalInfo(dbc, orgUnit, recursive);
5386    }
5387
5388    /**
5389     * Returns the VFS driver.<p>
5390     *
5391     * @return the VFS driver
5392     */
5393    public I_CmsVfsDriver getVfsDriver() {
5394
5395        return m_vfsDriver;
5396    }
5397
5398    /**
5399     * Returns the VFS driver for the given database context.<p>
5400     *
5401     * @param dbc the database context
5402     *
5403     * @return a VFS driver
5404     */
5405    public I_CmsVfsDriver getVfsDriver(CmsDbContext dbc) {
5406
5407        if ((dbc == null) || (dbc.getProjectId() == null) || dbc.getProjectId().isNullUUID()) {
5408            return m_vfsDriver;
5409        }
5410        I_CmsVfsDriver driver = dbc.getVfsDriver(dbc.getProjectId());
5411        return driver != null ? driver : m_vfsDriver;
5412
5413    }
5414
5415    /**
5416     * Writes a vector of access control entries as new access control entries of a given resource.<p>
5417     *
5418     * Already existing access control entries of this resource are removed before.
5419     * Access is granted, if:<p>
5420     * <ul>
5421     * <li>the current user has control permission on the resource</li>
5422     * </ul>
5423     *
5424     * @param dbc the current database context
5425     * @param resource the resource
5426     * @param acEntries a list of <code>{@link CmsAccessControlEntry}</code> objects
5427     *
5428     * @throws CmsException if something goes wrong
5429     */
5430    public void importAccessControlEntries(
5431        CmsDbContext dbc,
5432        CmsResource resource,
5433        List<CmsAccessControlEntry> acEntries)
5434    throws CmsException {
5435
5436        I_CmsUserDriver userDriver = getUserDriver(dbc);
5437        userDriver.removeAccessControlEntries(dbc, dbc.currentProject(), resource.getResourceId());
5438        List<CmsAccessControlEntry> fixedAces = new ArrayList<>();
5439        for (CmsAccessControlEntry entry : acEntries) {
5440            if (entry.getResource() == null) {
5441                entry = new CmsAccessControlEntry(
5442                    resource.getResourceId(),
5443                    entry.getPrincipal(),
5444                    entry.getPermissions(),
5445                    entry.getFlags());
5446            }
5447            fixedAces.add(entry);
5448        }
5449
5450        Iterator<CmsAccessControlEntry> i = fixedAces.iterator();
5451        while (i.hasNext()) {
5452            userDriver.writeAccessControlEntry(dbc, dbc.currentProject(), i.next());
5453        }
5454        m_monitor.clearAccessControlListCache();
5455    }
5456
5457    /**
5458     * Imports a rewrite alias.<p>
5459     *
5460     * @param dbc the database context
5461     * @param siteRoot the site root of the alias
5462     * @param source the source of the alias
5463     * @param target the target of the alias
5464     * @param mode the alias mode
5465     *
5466     * @return the import result
5467     *
5468     * @throws CmsException if something goes wrong
5469     */
5470    public CmsAliasImportResult importRewriteAlias(
5471        CmsDbContext dbc,
5472        String siteRoot,
5473        String source,
5474        String target,
5475        CmsAliasMode mode)
5476    throws CmsException {
5477
5478        I_CmsVfsDriver vfs = getVfsDriver(dbc);
5479        List<CmsRewriteAlias> existingAliases = vfs.readRewriteAliases(
5480            dbc,
5481            new CmsRewriteAliasFilter().setSiteRoot(siteRoot));
5482        CmsUUID idToDelete = null;
5483        for (CmsRewriteAlias alias : existingAliases) {
5484            if (alias.getPatternString().equals(source)) {
5485                idToDelete = alias.getId();
5486            }
5487        }
5488        if (idToDelete != null) {
5489            vfs.deleteRewriteAliases(dbc, new CmsRewriteAliasFilter().setId(idToDelete));
5490        }
5491        CmsRewriteAlias alias = new CmsRewriteAlias(new CmsUUID(), siteRoot, source, target, mode);
5492        List<CmsRewriteAlias> aliases = new ArrayList<CmsRewriteAlias>();
5493        aliases.add(alias);
5494        getVfsDriver(dbc).insertRewriteAliases(dbc, aliases);
5495        CmsAliasImportResult result = new CmsAliasImportResult(
5496            CmsAliasImportStatus.aliasNew,
5497            "OK",
5498            source,
5499            target,
5500            mode);
5501        return result;
5502    }
5503
5504    /**
5505     * Creates a new user by import.<p>
5506     *
5507     * @param dbc the current database context
5508     * @param id the id of the user
5509     * @param name the new name for the user
5510     * @param password the new password for the user (already encrypted)
5511     * @param firstname the firstname of the user
5512     * @param lastname the lastname of the user
5513     * @param email the email of the user
5514     * @param flags the flags for a user (for example <code>{@link I_CmsPrincipal#FLAG_ENABLED}</code>)
5515     * @param dateCreated the creation date
5516     * @param additionalInfos the additional user infos
5517     *
5518     * @return the imported user
5519     *
5520     * @throws CmsException if something goes wrong
5521     */
5522    public CmsUser importUser(
5523        CmsDbContext dbc,
5524        String id,
5525        String name,
5526        String password,
5527        String firstname,
5528        String lastname,
5529        String email,
5530        int flags,
5531        long dateCreated,
5532        Map<String, Object> additionalInfos)
5533    throws CmsException {
5534
5535        // no space before or after the name
5536        name = name.trim();
5537        // check the user name
5538        String userName = CmsOrganizationalUnit.getSimpleName(name);
5539        OpenCms.getValidationHandler().checkUserName(userName);
5540        if (CmsStringUtil.isEmptyOrWhitespaceOnly(userName)) {
5541            throw new CmsIllegalArgumentException(Messages.get().container(Messages.ERR_BAD_USER_1, userName));
5542        }
5543        // check the ou
5544        CmsOrganizationalUnit ou = readOrganizationalUnit(dbc, CmsOrganizationalUnit.getParentFqn(name));
5545
5546        // check webuser ou
5547        if (ou.hasFlagWebuser() && ((flags & I_CmsPrincipal.FLAG_USER_WEBUSER) == 0)) {
5548            flags += I_CmsPrincipal.FLAG_USER_WEBUSER;
5549        }
5550        CmsUser newUser = getUserDriver(dbc).createUser(
5551            dbc,
5552            new CmsUUID(id),
5553            name,
5554            password,
5555            firstname,
5556            lastname,
5557            email,
5558            0,
5559            flags,
5560            dateCreated,
5561            additionalInfos);
5562        return newUser;
5563    }
5564
5565    /**
5566     * Increments a counter and returns its value before incrementing.<p>
5567     *
5568     * @param dbc the current database context
5569     * @param name the name of the counter which should be incremented
5570     *
5571     * @return the value of the counter
5572     *
5573     * @throws CmsException if something goes wrong
5574     */
5575    public int incrementCounter(CmsDbContext dbc, String name) throws CmsException {
5576
5577        return getVfsDriver(dbc).incrementCounter(dbc, name);
5578    }
5579
5580    /**
5581     * Initializes the driver and sets up all required modules and connections.<p>
5582     *
5583     * @param configurationManager the configuration manager
5584     * @param dbContextFactory the db context factory
5585     *
5586     * @throws CmsException if something goes wrong
5587     * @throws Exception if something goes wrong
5588     */
5589    public void init(CmsConfigurationManager configurationManager, I_CmsDbContextFactory dbContextFactory)
5590    throws CmsException, Exception {
5591
5592        // initialize the access-module.
5593        if (CmsLog.INIT.isInfoEnabled()) {
5594            CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_DRIVER_MANAGER_START_PHASE4_0));
5595        }
5596        // store local reference to the memory monitor to avoid multiple lookups through the OpenCms singelton
5597        m_monitor = OpenCms.getMemoryMonitor();
5598
5599        CmsSystemConfiguration systemConfiguation = (CmsSystemConfiguration)configurationManager.getConfiguration(
5600            CmsSystemConfiguration.class);
5601        CmsCacheSettings settings = systemConfiguation.getCacheSettings();
5602
5603        // initialize the key generator
5604        m_keyGenerator = (I_CmsCacheKey)Class.forName(settings.getCacheKeyGenerator()).newInstance();
5605
5606        // initialize the HTML link validator
5607        m_htmlLinkValidator = new CmsRelationSystemValidator(this);
5608
5609        // fills the defaults if needed
5610        CmsDbContext dbc1 = dbContextFactory.getDbContext();
5611        getUserDriver().fillDefaults(dbc1);
5612        getProjectDriver().fillDefaults(dbc1);
5613
5614        // set the driver manager in the publish engine
5615        m_publishEngine.setDriverManager(this);
5616        // create the root organizational unit if needed
5617        CmsDbContext dbc2 = dbContextFactory.getDbContext(
5618            new CmsRequestContext(
5619                readUser(dbc1, OpenCms.getDefaultUsers().getUserAdmin()),
5620                readProject(dbc1, CmsProject.ONLINE_PROJECT_ID),
5621                null,
5622                CmsSiteMatcher.DEFAULT_MATCHER,
5623                "",
5624                false,
5625                null,
5626                null,
5627                null,
5628                0,
5629                null,
5630                null,
5631                "",
5632                false));
5633        dbc1.clear();
5634        getUserDriver().createRootOrganizationalUnit(dbc2);
5635        dbc2.clear();
5636    }
5637
5638    /**
5639     * Initializes the organizational unit.<p>
5640     *
5641     * @param dbc the DB context
5642     * @param ou the organizational unit
5643     */
5644    public void initOrgUnit(CmsDbContext dbc, CmsOrganizationalUnit ou) {
5645
5646        try {
5647            dbc.setAttribute(ATTR_INIT_OU, ou);
5648            m_userDriver.fillDefaults(dbc);
5649        } finally {
5650            dbc.removeAttribute(ATTR_INIT_OU);
5651        }
5652    }
5653
5654    /**
5655     * Checks if the specified resource is inside the current project.<p>
5656     *
5657     * The project "view" is determined by a set of path prefixes.
5658     * If the resource starts with any one of this prefixes, it is considered to
5659     * be "inside" the project.<p>
5660     *
5661     * @param dbc the current database context
5662     * @param resourcename the specified resource name (full path)
5663     *
5664     * @return <code>true</code>, if the specified resource is inside the current project
5665     */
5666    public boolean isInsideCurrentProject(CmsDbContext dbc, String resourcename) {
5667
5668        List<String> projectResources = null;
5669        try {
5670            projectResources = readProjectResources(dbc, dbc.currentProject());
5671        } catch (CmsException e) {
5672            if (LOG.isErrorEnabled()) {
5673                LOG.error(
5674                    Messages.get().getBundle().key(
5675                        Messages.LOG_CHECK_RESOURCE_INSIDE_CURRENT_PROJECT_2,
5676                        resourcename,
5677                        dbc.currentProject().getName()),
5678                    e);
5679            }
5680            return false;
5681        }
5682        return CmsProject.isInsideProject(projectResources, resourcename);
5683    }
5684
5685    /**
5686     * Checks whether the subscription driver is available.<p>
5687     *
5688     * @return true if the subscription driver is available
5689     */
5690    public boolean isSubscriptionDriverAvailable() {
5691
5692        return m_subscriptionDriver != null;
5693    }
5694
5695    /**
5696     * Checks if a project is the tempfile project.<p>
5697     * @param project the project to test
5698     * @return true if the project is the tempfile project
5699     */
5700    public boolean isTempfileProject(CmsProject project) {
5701
5702        return project.getName().equals("tempFileProject");
5703    }
5704
5705    /**
5706     * Checks if one of the resources (except the resource itself)
5707     * is a sibling in a "labeled" site folder.<p>
5708     *
5709     * This method is used when creating a new sibling
5710     * (use the <code>newResource</code> parameter & <code>action = 1</code>)
5711     * or deleting/importing a resource (call with <code>action = 2</code>).<p>
5712     *
5713     * @param dbc the current database context
5714     * @param resource the resource
5715     * @param newResource absolute path for a resource sibling which will be created
5716     * @param action the action which has to be performed (1: create VFS link, 2: all other actions)
5717     *
5718     * @return <code>true</code> if the flag should be set for the resource, otherwise <code>false</code>
5719     *
5720     * @throws CmsDataAccessException if something goes wrong
5721     */
5722    public boolean labelResource(CmsDbContext dbc, CmsResource resource, String newResource, int action)
5723    throws CmsDataAccessException {
5724
5725        // get the list of labeled site folders from the runtime property
5726        List<String> labeledSites = OpenCms.getWorkplaceManager().getLabelSiteFolders();
5727
5728        if (labeledSites.size() == 0) {
5729            // no labeled sites defined, just return false
5730            return false;
5731        }
5732
5733        if (action == 1) {
5734            // CASE 1: a new resource is created, check the sites
5735            if (!resource.isLabeled()) {
5736                // source isn't labeled yet, so check!
5737                boolean linkInside = false;
5738                boolean sourceInside = false;
5739                for (int i = 0; i < labeledSites.size(); i++) {
5740                    String curSite = labeledSites.get(i);
5741                    if (newResource.startsWith(curSite)) {
5742                        // the link lies in a labeled site
5743                        linkInside = true;
5744                    }
5745                    if (resource.getRootPath().startsWith(curSite)) {
5746                        // the source lies in a labeled site
5747                        sourceInside = true;
5748                    }
5749                    if (linkInside && sourceInside) {
5750                        break;
5751                    }
5752                }
5753                // return true when either source or link is in labeled site, otherwise false
5754                return (linkInside != sourceInside);
5755            }
5756            // resource is already labeled
5757            return false;
5758
5759        } else {
5760            // CASE 2: the resource will be deleted or created (import)
5761            // check if at least one of the other siblings resides inside a "labeled site"
5762            // and if at least one of the other siblings resides outside a "labeled site"
5763            boolean isInside = false;
5764            boolean isOutside = false;
5765            // check if one of the other vfs links lies in a labeled site folder
5766            List<CmsResource> siblings = getVfsDriver(
5767                dbc).readSiblings(dbc, dbc.currentProject().getUuid(), resource, false);
5768            updateContextDates(dbc, siblings);
5769            Iterator<CmsResource> i = siblings.iterator();
5770            while (i.hasNext() && (!isInside || !isOutside)) {
5771                CmsResource currentResource = i.next();
5772                if (currentResource.equals(resource)) {
5773                    // dont't check the resource itself!
5774                    continue;
5775                }
5776                String curPath = currentResource.getRootPath();
5777                boolean curInside = false;
5778                for (int k = 0; k < labeledSites.size(); k++) {
5779                    if (curPath.startsWith(labeledSites.get(k))) {
5780                        // the link is in the labeled site
5781                        isInside = true;
5782                        curInside = true;
5783                        break;
5784                    }
5785                }
5786                if (!curInside) {
5787                    // the current link was not found in labeled site, so it is outside
5788                    isOutside = true;
5789                }
5790            }
5791            // now check the new resource name if present
5792            if (newResource != null) {
5793                boolean curInside = false;
5794                for (int k = 0; k < labeledSites.size(); k++) {
5795                    if (newResource.startsWith(labeledSites.get(k))) {
5796                        // the new resource is in the labeled site
5797                        isInside = true;
5798                        curInside = true;
5799                        break;
5800                    }
5801                }
5802                if (!curInside) {
5803                    // the new resource was not found in labeled site, so it is outside
5804                    isOutside = true;
5805                }
5806            }
5807            return (isInside && isOutside);
5808        }
5809    }
5810
5811    /**
5812     * Returns the user, who had locked the resource.<p>
5813     *
5814     * A user can lock a resource, so he is the only one who can write this
5815     * resource. This methods checks, if a resource was locked.
5816     *
5817     * @param dbc the current database context
5818     * @param resource the resource
5819     *
5820     * @return the user, who had locked the resource
5821     *
5822     * @throws CmsException will be thrown, if the user has not the rights for this resource
5823     */
5824    public CmsUser lockedBy(CmsDbContext dbc, CmsResource resource) throws CmsException {
5825
5826        return readUser(dbc, m_lockManager.getLock(dbc, resource).getEditionLock().getUserId());
5827    }
5828
5829    /**
5830     * Locks a resource.<p>
5831     *
5832     * The <code>type</code> parameter controls what kind of lock is used.<br>
5833     * Possible values for this parameter are: <br>
5834     * <ul>
5835     * <li><code>{@link org.opencms.lock.CmsLockType#EXCLUSIVE}</code></li>
5836     * <li><code>{@link org.opencms.lock.CmsLockType#TEMPORARY}</code></li>
5837     * <li><code>{@link org.opencms.lock.CmsLockType#PUBLISH}</code></li>
5838     * </ul><p>
5839     *
5840     * @param dbc the current database context
5841     * @param resource the resource to lock
5842     * @param type type of the lock
5843     *
5844     * @throws CmsException if something goes wrong
5845     *
5846     * @see CmsObject#lockResource(String)
5847     * @see CmsObject#lockResourceTemporary(String)
5848     * @see org.opencms.file.types.I_CmsResourceType#lockResource(CmsObject, CmsSecurityManager, CmsResource, CmsLockType)
5849     */
5850    public void lockResource(CmsDbContext dbc, CmsResource resource, CmsLockType type) throws CmsException {
5851
5852        // update the resource cache
5853        m_monitor.clearResourceCache();
5854
5855        CmsProject project = dbc.currentProject();
5856
5857        // add the resource to the lock dispatcher
5858        m_lockManager.addResource(dbc, resource, dbc.currentUser(), project, type);
5859        boolean changedProjectLastModified = false;
5860        if (!resource.getState().isUnchanged() && !resource.getState().isKeep()) {
5861            // update the project flag of a modified resource as "last modified inside the current project"
5862            getVfsDriver(dbc).writeLastModifiedProjectId(dbc, project, project.getUuid(), resource);
5863            changedProjectLastModified = true;
5864        }
5865
5866        // we must also clear the permission cache
5867        m_monitor.flushCache(CmsMemoryMonitor.CacheType.PERMISSION);
5868
5869        // fire resource modification event
5870        Map<String, Object> data = new HashMap<String, Object>(2);
5871        data.put(I_CmsEventListener.KEY_RESOURCE, resource);
5872        data.put(
5873            I_CmsEventListener.KEY_CHANGE,
5874            Integer.valueOf(changedProjectLastModified ? CHANGED_PROJECT : NOTHING_CHANGED));
5875        data.put(I_CmsEventListener.KEY_SKIPINDEX, Boolean.TRUE);
5876        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_MODIFIED, data));
5877    }
5878
5879    /**
5880     * Adds the given log entry to the current user's log.<p>
5881     *
5882     * This operation works only on memory, to get the log entries actually
5883     * written to DB you have to call the {@link #updateLog(CmsDbContext)} method.<p>
5884     *
5885     * @param dbc the current database context
5886     * @param logEntry the log entry to create
5887     * @param force forces the log entry to be counted,
5888     *              if not only the first log entry in a transaction will be taken into account
5889     */
5890    public void log(CmsDbContext dbc, CmsLogEntry logEntry, boolean force) {
5891
5892        if (dbc == null) {
5893            return;
5894        }
5895        // check log level
5896        if (!logEntry.getType().isActive()) {
5897            // do not log inactive entries
5898            return;
5899        }
5900        // if not forcing
5901        if (!force) {
5902            // operation already logged
5903            boolean abort = (dbc.getAttribute(CmsLogEntry.ATTR_LOG_ENTRY) != null);
5904            // disabled logging from outside
5905            abort |= (dbc.getRequestContext().getAttribute(CmsLogEntry.ATTR_LOG_ENTRY) != null);
5906            if (abort) {
5907                return;
5908            }
5909        }
5910        // prevent several entries for the same operation
5911        dbc.setAttribute(CmsLogEntry.ATTR_LOG_ENTRY, Boolean.TRUE);
5912        // keep it for later
5913        m_log.add(logEntry);
5914    }
5915
5916    /**
5917     * Attempts to authenticate a user into OpenCms with the given password.
5918     *
5919     * <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,
5920     * while check mode merely checks the login details without firing the events normally fired during login, and without modifying the user. However,
5921     * in the case an incorrect password is given, the invalid login counter is still incremented.
5922     *
5923     * @param dbc the current database context
5924     * @param userName the name of the user to be logged in
5925     * @param password the password of the user
5926     * @param secondFactorInfo the second factor information for 2FA (may be null)
5927     * @param remoteAddress the ip address of the request
5928     * @param mode the mode to use (real login or check only)
5929     *
5930     * @return the logged in user
5931     *
5932     * @throws CmsAuthentificationException if the login was not successful
5933     * @throws CmsDataAccessException in case of errors accessing the database
5934     * @throws CmsPasswordEncryptionException in case of errors encrypting the users password
5935     */
5936    public CmsUser loginUser(
5937        CmsDbContext dbc,
5938        String userName,
5939        String password,
5940        CmsSecondFactorInfo secondFactorInfo,
5941        String remoteAddress,
5942        LoginUserMode mode)
5943    throws CmsAuthentificationException, CmsDataAccessException, CmsPasswordEncryptionException {
5944
5945        if (CmsStringUtil.isEmptyOrWhitespaceOnly(password)) {
5946            throw new CmsDbEntryNotFoundException(Messages.get().container(Messages.ERR_UNKNOWN_USER_1, userName));
5947        }
5948        CmsUser newUser;
5949        CmsUser userCopy;
5950        try {
5951            // read the user from the driver to avoid the cache
5952            newUser = getUserDriver(dbc).readUser(dbc, userName, password, remoteAddress);
5953            userCopy = newUser.clone();
5954            userName = newUser.getName();
5955
5956        } catch (CmsDbEntryNotFoundException e) {
5957            // this indicates that the username / password combination does not exist
5958            // any other exception indicates database issues, these are not catched here
5959
5960            // check if a user with this name exists at all
5961            CmsUser user = null;
5962            try {
5963                user = readUser(dbc, userName);
5964                userName = user.getName();
5965            } catch (CmsDataAccessException e2) {
5966                // apparently this user does not exist in the database
5967            }
5968
5969            if (user != null) {
5970                if (dbc.currentUser().isGuestUser()) {
5971                    // add an invalid login attempt for this user to the storage
5972                    OpenCms.getLoginManager().addInvalidLogin(userName, remoteAddress);
5973                }
5974                OpenCms.getLoginManager().checkInvalidLogins(userName, remoteAddress);
5975                throw new CmsAuthentificationException(
5976                    org.opencms.security.Messages.get().container(
5977                        org.opencms.security.Messages.ERR_LOGIN_FAILED_2,
5978                        userName,
5979                        remoteAddress),
5980                    e);
5981            } else {
5982                String userOu = CmsOrganizationalUnit.getParentFqn(userName);
5983                if (userOu != null) {
5984                    String parentOu = CmsOrganizationalUnit.getParentFqn(userOu);
5985                    if (parentOu != null) {
5986                        // try a higher level ou
5987                        String uName = CmsOrganizationalUnit.getSimpleName(userName);
5988                        return loginUser(dbc, parentOu + uName, password, secondFactorInfo, remoteAddress, mode);
5989                    }
5990                }
5991                throw new CmsAuthentificationException(
5992                    org.opencms.security.Messages.get().container(
5993                        org.opencms.security.Messages.ERR_LOGIN_FAILED_NO_USER_2,
5994                        userName,
5995                        remoteAddress),
5996                    e);
5997            }
5998        }
5999        // check if the "enabled" flag is set for the user
6000        if (!newUser.isEnabled()) {
6001            // user is disabled, throw a securiy exception
6002            throw new CmsAuthentificationException(
6003                org.opencms.security.Messages.get().container(
6004                    org.opencms.security.Messages.ERR_LOGIN_FAILED_DISABLED_2,
6005                    userName,
6006                    remoteAddress));
6007        }
6008
6009        if (mode == LoginUserMode.standard) {
6010            CmsTwoFactorAuthenticationHandler handler = OpenCms.getTwoFactorAuthenticationHandler();
6011            if (handler.needsTwoFactorAuthentication(newUser)) {
6012                // note that password check must already have been successful at this stage
6013
6014                if (handler.hasSecondFactor(newUser)) {
6015                    if (!handler.verifySecondFactor(newUser, secondFactorInfo)) {
6016                        if (dbc.currentUser().isGuestUser()) {
6017                            // add an invalid login attempt for this user to the storage
6018                            OpenCms.getLoginManager().addInvalidLogin(userName, remoteAddress);
6019                        }
6020                        OpenCms.getLoginManager().checkInvalidLogins(userName, remoteAddress);
6021                        throw new CmsAuthentificationException(
6022                            org.opencms.security.Messages.get().container(
6023                                org.opencms.security.Messages.ERR_VERIFICATION_FAILED_1,
6024                                userName));
6025                    }
6026                } else {
6027                    try {
6028                        if (handler.setUpAndVerifySecondFactor(newUser, secondFactorInfo)) {
6029                            LOG.info("Second factor setup successful for user " + newUser.getName());
6030                        } else {
6031                            if (dbc.currentUser().isGuestUser()) {
6032                                // add an invalid login attempt for this user to the storage
6033                                OpenCms.getLoginManager().addInvalidLogin(userName, remoteAddress);
6034                            }
6035                            OpenCms.getLoginManager().checkInvalidLogins(userName, remoteAddress);
6036                            throw new CmsAuthentificationException(
6037                                org.opencms.security.Messages.get().container(
6038                                    org.opencms.security.Messages.ERR_VERIFICATION_FAILED_1,
6039                                    userName));
6040                        }
6041                    } catch (CmsSecondFactorSetupException e) {
6042                        throw new CmsAuthentificationException(
6043                            org.opencms.security.Messages.get().container(
6044                                org.opencms.security.Messages.ERR_VERIFICATION_FAILED_1,
6045                                userName),
6046                            e);
6047                    }
6048                }
6049            }
6050        }
6051        if (dbc.currentUser().isGuestUser()) {
6052            // check if this account is temporarily disabled because of too many invalid login attempts
6053            // this will throw an exception if the test fails
6054            OpenCms.getLoginManager().checkInvalidLogins(userName, remoteAddress);
6055            if (mode == LoginUserMode.standard) {
6056                // test successful, remove all previous invalid login attempts for this user from the storage
6057                OpenCms.getLoginManager().removeInvalidLogins(userName, remoteAddress);
6058            }
6059        }
6060
6061        if (!m_securityManager.hasRole(
6062            dbc,
6063            newUser,
6064            CmsRole.ADMINISTRATOR.forOrgUnit(dbc.getRequestContext().getOuFqn()))) {
6065            // new user is not Administrator, check if login is currently allowed
6066            OpenCms.getLoginManager().checkLoginAllowed();
6067        }
6068
6069        if (mode == LoginUserMode.standard) {
6070
6071            newUser.setLastlogin(System.currentTimeMillis());
6072            m_monitor.clearUserCache(newUser);
6073
6074            // write the changed user object back to the user driver
6075            Map<String, Object> additionalInfosForRepositories = OpenCms.getRepositoryManager().getAdditionalInfoForLogin(
6076                newUser.getName(),
6077                password);
6078            boolean requiresAddInfoUpdate = false;
6079
6080            // check for changes
6081            for (Entry<String, Object> entry : additionalInfosForRepositories.entrySet()) {
6082                Object value = entry.getValue();
6083                Object current = newUser.getAdditionalInfo(entry.getKey());
6084                if (((value == null) && (current != null)) || ((value != null) && !value.equals(current))) {
6085                    requiresAddInfoUpdate = true;
6086                    break;
6087                }
6088            }
6089            if (requiresAddInfoUpdate) {
6090                newUser.getAdditionalInfo().putAll(additionalInfosForRepositories);
6091            }
6092            String lastPasswordChange = (String)newUser.getAdditionalInfo(
6093                CmsUserSettings.ADDITIONAL_INFO_LAST_PASSWORD_CHANGE);
6094            if (lastPasswordChange == null) {
6095                requiresAddInfoUpdate = true;
6096                newUser.getAdditionalInfo().put(
6097                    CmsUserSettings.ADDITIONAL_INFO_LAST_PASSWORD_CHANGE,
6098                    "" + System.currentTimeMillis());
6099            }
6100            if (!requiresAddInfoUpdate) {
6101                dbc.setAttribute(ATTRIBUTE_LOGIN, newUser.getName());
6102            }
6103
6104            if (mode == LoginUserMode.standard) {
6105                OpenCms.getTwoFactorAuthenticationHandler().trackUserChange(dbc.getRequestContext(), userCopy, newUser);
6106                getUserDriver(dbc).writeUser(dbc, newUser);
6107            }
6108            int changes = CmsUser.FLAG_LAST_LOGIN;
6109
6110            // check if we need to update the password
6111            if (!OpenCms.getPasswordHandler().checkPassword(password, newUser.getPassword(), false)
6112                && OpenCms.getPasswordHandler().checkPassword(password, newUser.getPassword(), true)) {
6113                // the password does not check with the current hash algorithm but with the fall back, update the password
6114                getUserDriver(dbc).writePassword(dbc, userName, password, password);
6115                changes = changes | CmsUser.FLAG_CORE_DATA;
6116            }
6117
6118            // update cache
6119            m_monitor.cacheUser(newUser);
6120
6121            // invalidate all user dependent caches
6122            m_monitor.flushCache(
6123                CmsMemoryMonitor.CacheType.ACL,
6124                CmsMemoryMonitor.CacheType.GROUP,
6125                CmsMemoryMonitor.CacheType.ORG_UNIT,
6126                CmsMemoryMonitor.CacheType.USER_LIST,
6127                CmsMemoryMonitor.CacheType.PERMISSION,
6128                CmsMemoryMonitor.CacheType.RESOURCE_LIST);
6129
6130            // fire user modified event
6131            Map<String, Object> eventData = new HashMap<String, Object>();
6132            eventData.put(I_CmsEventListener.KEY_USER_ID, newUser.getId().toString());
6133            eventData.put(I_CmsEventListener.KEY_USER_NAME, newUser.getName());
6134            eventData.put(I_CmsEventListener.KEY_USER_ACTION, I_CmsEventListener.VALUE_USER_MODIFIED_ACTION_WRITE_USER);
6135            eventData.put(I_CmsEventListener.KEY_USER_CHANGES, Integer.valueOf(changes));
6136            OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_USER_MODIFIED, eventData));
6137        }
6138
6139        // return the user object read from the driver
6140        return newUser.clone();
6141    }
6142
6143    /**
6144     * Lookup and read the user or group with the given UUID.<p>
6145     *
6146     * @param dbc the current database context
6147     * @param principalId the UUID of the principal to lookup
6148     *
6149     * @return the principal (group or user) if found, otherwise <code>null</code>
6150     */
6151    public I_CmsPrincipal lookupPrincipal(CmsDbContext dbc, CmsUUID principalId) {
6152
6153        try {
6154            CmsGroup group = getUserDriver(dbc).readGroup(dbc, principalId);
6155            if (group != null) {
6156                return group;
6157            }
6158        } catch (Exception e) {
6159            // ignore this exception
6160        }
6161
6162        try {
6163            CmsUser user = readUser(dbc, principalId);
6164            if (user != null) {
6165                return user;
6166            }
6167        } catch (Exception e) {
6168            // ignore this exception
6169        }
6170
6171        return null;
6172    }
6173
6174    /**
6175     * Lookup and read the user or group with the given name.<p>
6176     *
6177     * @param dbc the current database context
6178     * @param principalName the name of the principal to lookup
6179     *
6180     * @return the principal (group or user) if found, otherwise <code>null</code>
6181     */
6182    public I_CmsPrincipal lookupPrincipal(CmsDbContext dbc, String principalName) {
6183
6184        try {
6185            CmsGroup group = getUserDriver(dbc).readGroup(dbc, principalName);
6186            if (group != null) {
6187                return group;
6188            }
6189        } catch (Exception e) {
6190            // ignore this exception
6191        }
6192
6193        try {
6194            CmsUser user = readUser(dbc, principalName);
6195            if (user != null) {
6196                return user;
6197            }
6198        } catch (Exception e) {
6199            // ignore this exception
6200        }
6201
6202        return null;
6203    }
6204
6205    /**
6206     * Mark the given resource as visited by the user.<p>
6207     *
6208     * @param dbc the database context
6209     * @param poolName the name of the database pool to use
6210     * @param resource the resource to mark as visited
6211     * @param user the user that visited the resource
6212     *
6213     * @throws CmsException if something goes wrong
6214     */
6215    public void markResourceAsVisitedBy(CmsDbContext dbc, String poolName, CmsResource resource, CmsUser user)
6216    throws CmsException {
6217
6218        getSubscriptionDriver().markResourceAsVisitedBy(dbc, poolName, resource, user);
6219    }
6220
6221    /**
6222     * Moves a resource.<p>
6223     *
6224     * You must ensure that the parent of the destination path is an absolute, valid and
6225     * existing VFS path. Relative paths from the source are not supported.<p>
6226     *
6227     * The moved resource will always be locked to the current user
6228     * after the move operation.<p>
6229     *
6230     * In case the target resource already exists, it will be overwritten with the
6231     * source resource if possible.<p>
6232     *
6233     * @param dbc the current database context
6234     * @param source the resource to move
6235     * @param destination the name of the move destination with complete path
6236     * @param internal if set nothing more than the path is modified
6237     *
6238     * @throws CmsException if something goes wrong
6239     *
6240     * @see CmsSecurityManager#moveResource(CmsRequestContext, CmsResource, String)
6241     */
6242    public void moveResource(CmsDbContext dbc, CmsResource source, String destination, boolean internal)
6243    throws CmsException {
6244
6245        CmsFolder destinationFolder = readFolder(dbc, CmsResource.getParentFolder(destination), CmsResourceFilter.ALL);
6246        m_securityManager.checkPermissions(
6247            dbc,
6248            destinationFolder,
6249            CmsPermissionSet.ACCESS_WRITE,
6250            false,
6251            CmsResourceFilter.ALL);
6252
6253        if (source.isFolder()) {
6254            m_monitor.flushCache(CmsMemoryMonitor.CacheType.HAS_ROLE, CmsMemoryMonitor.CacheType.ROLE_LIST);
6255        }
6256        getVfsDriver(dbc).moveResource(dbc, dbc.getRequestContext().getCurrentProject().getUuid(), source, destination);
6257
6258        if (!internal) {
6259            CmsResourceState newState = CmsResource.STATE_CHANGED;
6260            if (source.getState().isNew()) {
6261                newState = CmsResource.STATE_NEW;
6262            } else if (source.getState().isDeleted()) {
6263                newState = CmsResource.STATE_DELETED;
6264            }
6265            source.setState(newState);
6266            // safe since this operation always uses the ids instead of the resource path
6267            getVfsDriver(dbc).writeResourceState(
6268                dbc,
6269                dbc.currentProject(),
6270                source,
6271                CmsDriverManager.UPDATE_STRUCTURE_STATE,
6272                false);
6273            // log it
6274            log(
6275                dbc,
6276                new CmsLogEntry(
6277                    dbc,
6278                    source.getStructureId(),
6279                    CmsLogEntryType.RESOURCE_MOVED,
6280                    new String[] {source.getRootPath(), destination}),
6281                false);
6282        }
6283
6284        CmsResource destRes = readResource(dbc, destination, CmsResourceFilter.ALL);
6285        // move lock
6286        m_lockManager.moveResource(source.getRootPath(), destRes.getRootPath());
6287
6288        // flush all relevant caches
6289        m_monitor.clearAccessControlListCache();
6290        m_monitor.flushCache(
6291            CmsMemoryMonitor.CacheType.PROPERTY,
6292            CmsMemoryMonitor.CacheType.PROPERTY_LIST,
6293            CmsMemoryMonitor.CacheType.PROJECT_RESOURCES);
6294
6295        List<CmsResource> resources = new ArrayList<CmsResource>(4);
6296        // source
6297        resources.add(source);
6298        try {
6299            resources.add(readFolder(dbc, CmsResource.getParentFolder(source.getRootPath()), CmsResourceFilter.ALL));
6300        } catch (Exception e) {
6301            if (LOG.isDebugEnabled()) {
6302                LOG.debug(e.getLocalizedMessage(), e);
6303            }
6304        }
6305        // destination
6306        resources.add(destRes);
6307        resources.add(destinationFolder);
6308
6309        Map<String, Object> eventData = new HashMap<String, Object>();
6310        eventData.put(I_CmsEventListener.KEY_RESOURCES, resources);
6311        eventData.put(I_CmsEventListener.KEY_DBCONTEXT, dbc);
6312
6313        // fire the events
6314        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_MOVED, eventData));
6315    }
6316
6317    /**
6318     * Moves a resource to the "lost and found" folder.<p>
6319     *
6320     * The method can also be used to check get the name of a resource
6321     * in the "lost and found" folder only without actually moving the
6322     * the resource. To do this, the <code>returnNameOnly</code> flag
6323     * must be set to <code>true</code>.<p>
6324     *
6325     * @param dbc the current database context
6326     * @param resource the resource to apply this operation to
6327     * @param returnNameOnly if <code>true</code>, only the name of the resource in the "lost and found"
6328     *        folder is returned, the move operation is not really performed
6329     *
6330     * @return the name of the resource inside the "lost and found" folder
6331     *
6332     * @throws CmsException if something goes wrong
6333     * @throws CmsIllegalArgumentException if the <code>resourcename</code> argument is null or of length 0
6334     *
6335     * @see CmsObject#moveToLostAndFound(String)
6336     * @see CmsObject#getLostAndFoundName(String)
6337     */
6338    public String moveToLostAndFound(CmsDbContext dbc, CmsResource resource, boolean returnNameOnly)
6339    throws CmsException, CmsIllegalArgumentException {
6340
6341        String resourcename = dbc.removeSiteRoot(resource.getRootPath());
6342
6343        String siteRoot = dbc.getRequestContext().getSiteRoot();
6344        dbc.getRequestContext().setSiteRoot("");
6345        String destination = CmsDriverManager.LOST_AND_FOUND_FOLDER + resourcename;
6346        // create the required folders if necessary
6347        try {
6348            // collect all folders...
6349            String folderPath = CmsResource.getParentFolder(destination);
6350            folderPath = folderPath.substring(1, folderPath.length() - 1); // cut out leading and trailing '/'
6351            Iterator<String> folders = CmsStringUtil.splitAsList(folderPath, '/').iterator();
6352            // ...now create them....
6353            folderPath = "/";
6354            while (folders.hasNext()) {
6355                folderPath += folders.next().toString() + "/";
6356                try {
6357                    readFolder(dbc, folderPath, CmsResourceFilter.IGNORE_EXPIRATION);
6358                } catch (Exception e1) {
6359                    if (returnNameOnly) {
6360                        // we can use the original name without risk, and we do not need to recreate the parent folders
6361                        break;
6362                    }
6363                    // the folder is not existing, so create it
6364                    createResource(
6365                        dbc,
6366                        folderPath,
6367                        CmsResourceTypeFolder.RESOURCE_TYPE_ID,
6368                        null,
6369                        new ArrayList<CmsProperty>());
6370                }
6371            }
6372            // check if this resource name does already exist
6373            // if so add a postfix to the name
6374            String des = destination;
6375            int postfix = 1;
6376            boolean found = true;
6377            while (found) {
6378                try {
6379                    // try to read the file.....
6380                    found = true;
6381                    readResource(dbc, des, CmsResourceFilter.ALL);
6382                    // ....it's there, so add a postfix and try again
6383                    String path = destination.substring(0, destination.lastIndexOf('/') + 1);
6384                    String filename = destination.substring(destination.lastIndexOf('/') + 1, destination.length());
6385
6386                    des = path;
6387
6388                    if (filename.lastIndexOf('.') > 0) {
6389                        des += filename.substring(0, filename.lastIndexOf('.'));
6390                    } else {
6391                        des += filename;
6392                    }
6393                    des += "_" + postfix;
6394                    if (filename.lastIndexOf('.') > 0) {
6395                        des += filename.substring(filename.lastIndexOf('.'), filename.length());
6396                    }
6397                    postfix++;
6398                } catch (CmsException e3) {
6399                    // the file does not exist, so we can use this filename
6400                    found = false;
6401                }
6402            }
6403            destination = des;
6404
6405            if (!returnNameOnly) {
6406                // do not use the move semantic here! to prevent links pointing to the lost & found folder
6407                copyResource(dbc, resource, destination, CmsResource.COPY_AS_SIBLING);
6408                deleteResource(dbc, resource, CmsResource.DELETE_PRESERVE_SIBLINGS);
6409            }
6410        } catch (CmsException e2) {
6411            throw e2;
6412        } finally {
6413            // set the site root to the old value again
6414            dbc.getRequestContext().setSiteRoot(siteRoot);
6415        }
6416        return destination;
6417    }
6418
6419    /**
6420     * Gets a new driver instance.<p>
6421     *
6422     * @param dbc the database context
6423     * @param configurationManager the configuration manager
6424     * @param driverName the driver name
6425     * @param successiveDrivers the list of successive drivers
6426     *
6427     * @return the driver object
6428     * @throws CmsInitException if the selected driver could not be initialized
6429     */
6430    public Object newDriverInstance(
6431        CmsDbContext dbc,
6432        CmsConfigurationManager configurationManager,
6433        String driverName,
6434        List<String> successiveDrivers)
6435    throws CmsInitException {
6436
6437        Class<?> driverClass = null;
6438        I_CmsDriver driver = null;
6439
6440        try {
6441            // try to get the class
6442            driverClass = Class.forName(driverName);
6443            if (CmsLog.INIT.isInfoEnabled()) {
6444                CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_DRIVER_START_1, driverName));
6445            }
6446
6447            // try to create a instance
6448            driver = (I_CmsDriver)driverClass.newInstance();
6449            if (CmsLog.INIT.isInfoEnabled()) {
6450                CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_DRIVER_INITIALIZING_1, driverName));
6451            }
6452
6453            // invoke the init-method of this access class
6454            driver.init(dbc, configurationManager, successiveDrivers, this);
6455            if (CmsLog.INIT.isInfoEnabled()) {
6456                CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_DRIVER_INIT_FINISHED_0));
6457            }
6458
6459        } catch (Throwable t) {
6460            CmsMessageContainer message = Messages.get().container(
6461                Messages.ERR_ERROR_INITIALIZING_DRIVER_1,
6462                driverName);
6463            if (LOG.isErrorEnabled()) {
6464                LOG.error(message.key(), t);
6465            }
6466            throw new CmsInitException(message, t);
6467        }
6468
6469        return driver;
6470    }
6471
6472    /**
6473     * Method to create a new instance of a driver.<p>
6474     *
6475     * @param configuration the configurations from the propertyfile
6476     * @param driverName the class name of the driver
6477     * @param driverPoolUrl the pool url for the driver
6478     * @return an initialized instance of the driver
6479     * @throws CmsException if something goes wrong
6480     */
6481    public Object newDriverInstance(CmsParameterConfiguration configuration, String driverName, String driverPoolUrl)
6482    throws CmsException {
6483
6484        Class<?>[] initParamClasses = {CmsParameterConfiguration.class, String.class, CmsDriverManager.class};
6485        Object[] initParams = {configuration, driverPoolUrl, this};
6486
6487        Class<?> driverClass = null;
6488        Object driver = null;
6489
6490        try {
6491            // try to get the class
6492            driverClass = Class.forName(driverName);
6493            if (CmsLog.INIT.isInfoEnabled()) {
6494                CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_DRIVER_START_1, driverName));
6495            }
6496
6497            // try to create a instance
6498            driver = driverClass.newInstance();
6499            if (CmsLog.INIT.isInfoEnabled()) {
6500                CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_DRIVER_INITIALIZING_1, driverName));
6501            }
6502
6503            // invoke the init-method of this access class
6504            driver.getClass().getMethod("init", initParamClasses).invoke(driver, initParams);
6505            if (CmsLog.INIT.isInfoEnabled()) {
6506                CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_DRIVER_INIT_FINISHED_1, driverPoolUrl));
6507            }
6508
6509        } catch (Exception exc) {
6510
6511            CmsMessageContainer message = Messages.get().container(Messages.ERR_INIT_DRIVER_MANAGER_1);
6512            if (LOG.isFatalEnabled()) {
6513                LOG.fatal(message.key(), exc);
6514            }
6515            throw new CmsDbException(message, exc);
6516
6517        }
6518
6519        return driver;
6520    }
6521
6522    /**
6523     * Method to create a new instance of a pool.<p>
6524     *
6525     * @param configuration the configurations from the propertyfile
6526     * @param poolName the configuration name of the pool
6527     *
6528     * @throws CmsInitException if the pools could not be initialized
6529     */
6530    public void newPoolInstance(CmsParameterConfiguration configuration, String poolName) throws CmsInitException {
6531
6532        CmsDbPoolV11 pool;
6533
6534        try {
6535            pool = new CmsDbPoolV11(configuration, poolName);
6536        } catch (Exception e) {
6537
6538            CmsMessageContainer message = Messages.get().container(Messages.ERR_INIT_CONN_POOL_1, poolName);
6539            if (LOG.isErrorEnabled()) {
6540                LOG.error(message.key(), e);
6541            }
6542            throw new CmsInitException(message, e);
6543        }
6544        addPool(pool);
6545    }
6546
6547    /**
6548     * Publishes the given publish job.<p>
6549     *
6550     * @param cms the cms context
6551     * @param dbc the db context
6552     * @param publishList the list of resources to publish
6553     * @param report the report to write to
6554     *
6555     * @throws CmsException if something goes wrong
6556     */
6557    public void publishJob(CmsObject cms, CmsDbContext dbc, CmsPublishList publishList, I_CmsReport report)
6558    throws CmsException {
6559
6560        try {
6561            // check state and lock
6562            List<CmsResource> allResources = new ArrayList<CmsResource>(publishList.getFolderList());
6563            allResources.addAll(publishList.getDeletedFolderList());
6564            allResources.addAll(publishList.getFileList());
6565            Iterator<CmsResource> itResources = allResources.iterator();
6566            while (itResources.hasNext()) {
6567                CmsResource resource = itResources.next();
6568                try {
6569                    resource = readResource(dbc, resource.getStructureId(), CmsResourceFilter.ALL);
6570                } catch (CmsVfsResourceNotFoundException e) {
6571                    continue;
6572                }
6573                if (resource.getState().isUnchanged()) {
6574                    // remove files that were published by a concurrent job
6575                    if (LOG.isDebugEnabled()) {
6576                        LOG.debug(
6577                            Messages.get().getBundle().key(
6578                                Messages.RPT_PUBLISH_REMOVED_RESOURCE_1,
6579                                dbc.removeSiteRoot(resource.getRootPath())));
6580                    }
6581                    publishList.remove(resource);
6582                    unlockResource(dbc, resource, true, true);
6583                    continue;
6584                }
6585                if (!CmsModificationContext.isInOnlineFolder(resource.getRootPath())) {
6586                    CmsLock lock = m_lockManager.getLock(dbc, resource, false);
6587                    if (!lock.getSystemLock().isPublish()) {
6588                        // remove files that are not locked for publishing
6589                        if (LOG.isDebugEnabled()) {
6590                            LOG.debug(
6591                                Messages.get().getBundle().key(
6592                                    Messages.RPT_PUBLISH_REMOVED_RESOURCE_1,
6593                                    dbc.removeSiteRoot(resource.getRootPath())));
6594                        }
6595                        publishList.remove(resource);
6596                        continue;
6597                    }
6598                }
6599            }
6600
6601            CmsProject onlineProject = readProject(dbc, CmsProject.ONLINE_PROJECT_ID);
6602
6603            // clear the cache
6604            m_monitor.clearCacheForPublishing();
6605
6606            int publishTag = getNextPublishTag(dbc);
6607            getProjectDriver(dbc).publishProject(dbc, report, onlineProject, publishList, publishTag);
6608
6609            // iterate the initialized module action instances
6610            Iterator<String> i = OpenCms.getModuleManager().getModuleNames().iterator();
6611            while (i.hasNext()) {
6612                CmsModule module = OpenCms.getModuleManager().getModule(i.next());
6613                if ((module != null) && (module.getActionInstance() != null)) {
6614                    module.getActionInstance().publishProject(cms, publishList, publishTag, report);
6615                }
6616            }
6617
6618            boolean temporaryProject = (cms.getRequestContext().getCurrentProject().getType() == CmsProject.PROJECT_TYPE_TEMPORARY);
6619            // the project was stored in the history tables for history
6620            // it will be deleted if the project_flag is PROJECT_TYPE_TEMPORARY
6621            if ((temporaryProject) && (!publishList.isDirectPublish())) {
6622                try {
6623                    getProjectDriver(dbc).deleteProject(dbc, dbc.currentProject());
6624                } catch (CmsException e) {
6625                    LOG.error(
6626                        Messages.get().getBundle().key(
6627                            Messages.LOG_DELETE_TEMP_PROJECT_FAILED_1,
6628                            cms.getRequestContext().getCurrentProject().getName()));
6629                }
6630                // if project was temporary set context to online project
6631                cms.getRequestContext().setCurrentProject(onlineProject);
6632            }
6633        } finally {
6634            // clear the cache again
6635            m_monitor.clearCacheForPublishing();
6636        }
6637    }
6638
6639    /**
6640     * Publishes the resources of a specified publish list.<p>
6641     *
6642     * @param cms the current request context
6643     * @param dbc the current database context
6644     * @param publishList a publish list
6645     * @param report an instance of <code>{@link I_CmsReport}</code> to print messages
6646     *
6647     * @throws CmsException if something goes wrong
6648     *
6649     * @see #fillPublishList(CmsDbContext, CmsPublishList)
6650     */
6651    public synchronized void publishProject(
6652        CmsObject cms,
6653        CmsDbContext dbc,
6654        CmsPublishList publishList,
6655        I_CmsReport report)
6656    throws CmsException {
6657
6658        // check the parent folders
6659        checkParentFolders(dbc, publishList);
6660        ensureSubResourcesOfMovedFoldersPublished(cms, dbc, publishList);
6661        OpenCms.getPublishManager().getPublishListVerifier().checkPublishList(publishList);
6662
6663        try {
6664            // fire an event that a project is to be published
6665            Map<String, Object> eventData = new HashMap<String, Object>();
6666            eventData.put(I_CmsEventListener.KEY_REPORT, report);
6667            eventData.put(I_CmsEventListener.KEY_PUBLISHLIST, publishList);
6668            eventData.put(I_CmsEventListener.KEY_PROJECTID, dbc.currentProject().getUuid());
6669            eventData.put(I_CmsEventListener.KEY_DBCONTEXT, dbc);
6670            CmsEvent beforePublishEvent = new CmsEvent(I_CmsEventListener.EVENT_BEFORE_PUBLISH_PROJECT, eventData);
6671            OpenCms.fireCmsEvent(beforePublishEvent);
6672        } catch (Throwable t) {
6673            if (report != null) {
6674                report.addError(t);
6675                report.println(t);
6676            }
6677            if (LOG.isErrorEnabled()) {
6678                LOG.error(t.getLocalizedMessage(), t);
6679            }
6680        }
6681
6682        // lock all resources with the special publish lock
6683        Iterator<CmsResource> itResources = new ArrayList<CmsResource>(publishList.getAllResources()).iterator();
6684        while (itResources.hasNext()) {
6685            CmsResource resource = itResources.next();
6686            CmsLock lock = m_lockManager.getLock(dbc, resource, false);
6687            if (lock.getSystemLock().isUnlocked() && lock.isLockableBy(dbc.currentUser())) {
6688                if (getLock(dbc, resource).getEditionLock().isNullLock()) {
6689                    lockResource(dbc, resource, CmsLockType.PUBLISH);
6690                } else {
6691                    changeLock(dbc, resource, CmsLockType.PUBLISH);
6692                }
6693            } else if (lock.getSystemLock().isPublish()) {
6694                if (LOG.isWarnEnabled()) {
6695                    LOG.warn(
6696                        Messages.get().getBundle().key(
6697                            Messages.RPT_PUBLISH_REMOVED_RESOURCE_1,
6698                            dbc.removeSiteRoot(resource.getRootPath())));
6699                }
6700                // remove files that are already waiting to be published
6701                publishList.remove(resource);
6702                continue;
6703            } else {
6704                // this is needed to fix TestPublishIsssues#testPublishScenarioE
6705                changeLock(dbc, resource, CmsLockType.PUBLISH);
6706            }
6707            // now re-check the lock state
6708            lock = m_lockManager.getLock(dbc, resource, false);
6709            if (!lock.getSystemLock().isPublish()) {
6710                if (report != null) {
6711                    report.println(
6712                        Messages.get().container(
6713                            Messages.RPT_PUBLISH_REMOVED_RESOURCE_1,
6714                            dbc.removeSiteRoot(resource.getRootPath())),
6715                        I_CmsReport.FORMAT_WARNING);
6716                }
6717                if (LOG.isWarnEnabled()) {
6718                    LOG.warn(
6719                        Messages.get().getBundle().key(
6720                            Messages.RPT_PUBLISH_REMOVED_RESOURCE_1,
6721                            dbc.removeSiteRoot(resource.getRootPath())));
6722                }
6723                // remove files that could not be locked
6724                publishList.remove(resource);
6725            }
6726        }
6727
6728        // enqueue the publish job
6729        CmsException enqueueException = null;
6730        try {
6731            m_publishEngine.enqueuePublishJob(cms, publishList, report);
6732        } catch (CmsException exc) {
6733            enqueueException = exc;
6734        }
6735
6736        // if an exception was raised, remove the publish locks
6737        // and throw the exception again
6738        if (enqueueException != null) {
6739            itResources = publishList.getAllResources().iterator();
6740            while (itResources.hasNext()) {
6741                CmsResource resource = itResources.next();
6742                CmsLock lock = m_lockManager.getLock(dbc, resource, false);
6743                if (lock.getSystemLock().isPublish()
6744                    && lock.getSystemLock().isOwnedInProjectBy(
6745                        cms.getRequestContext().getCurrentUser(),
6746                        cms.getRequestContext().getCurrentProject())) {
6747                    unlockResource(dbc, resource, true, true);
6748                }
6749            }
6750
6751            throw enqueueException;
6752        }
6753    }
6754
6755    /**
6756     * Transfers the new URL name mappings (if any) for a given resource to the online project.<p>
6757     *
6758     * @param dbc the current database context
6759     * @param res the resource whose new URL name mappings should be transferred to the online project
6760     *
6761     * @throws CmsDataAccessException if something goes wrong
6762     */
6763    public void publishUrlNameMapping(CmsDbContext dbc, CmsResource res) throws CmsDataAccessException {
6764
6765        I_CmsVfsDriver vfsDriver = getVfsDriver(dbc);
6766
6767        if (res.getState().isDeleted()) {
6768            // remove both offline and online mappings
6769            CmsUrlNameMappingFilter idFilter = CmsUrlNameMappingFilter.ALL.filterStructureId(res.getStructureId());
6770            vfsDriver.deleteUrlNameMappingEntries(dbc, true, idFilter);
6771            vfsDriver.deleteUrlNameMappingEntries(dbc, false, idFilter);
6772        } else {
6773            // copy the new entries to the online table
6774            List<CmsUrlNameMappingEntry> entries = vfsDriver.readUrlNameMappingEntries(
6775                dbc,
6776                false,
6777                CmsUrlNameMappingFilter.ALL.filterStructureId(res.getStructureId()).filterStates(
6778                    CmsUrlNameMappingEntry.MAPPING_STATUS_NEW,
6779                    CmsUrlNameMappingEntry.MAPPING_STATUS_REPLACE_ON_PUBLISH));
6780
6781            boolean isReplaceOnPublish = false;
6782            for (CmsUrlNameMappingEntry entry : entries) {
6783                isReplaceOnPublish |= entry.getState() == CmsUrlNameMappingEntry.MAPPING_STATUS_REPLACE_ON_PUBLISH;
6784            }
6785
6786            if (!entries.isEmpty()) {
6787
6788                long now = System.currentTimeMillis();
6789                if (isReplaceOnPublish) {
6790                    vfsDriver.deleteUrlNameMappingEntries(
6791                        dbc,
6792                        true,
6793                        CmsUrlNameMappingFilter.ALL.filterStructureId(res.getStructureId()));
6794                    vfsDriver.deleteUrlNameMappingEntries(
6795                        dbc,
6796                        false,
6797                        CmsUrlNameMappingFilter.ALL.filterStructureId(res.getStructureId()));
6798                }
6799
6800                for (CmsUrlNameMappingEntry entry : entries) {
6801                    CmsUrlNameMappingFilter nameFilter = CmsUrlNameMappingFilter.ALL.filterName(entry.getName());
6802                    if (!isReplaceOnPublish) { // we already handled the other case above
6803                        vfsDriver.deleteUrlNameMappingEntries(dbc, true, nameFilter);
6804                        vfsDriver.deleteUrlNameMappingEntries(dbc, false, nameFilter);
6805                    }
6806                }
6807                for (CmsUrlNameMappingEntry entry : entries) {
6808                    CmsUrlNameMappingEntry newEntry = new CmsUrlNameMappingEntry(
6809                        entry.getName(),
6810                        entry.getStructureId(),
6811                        entry.getState() == CmsUrlNameMappingEntry.MAPPING_STATUS_NEW
6812                        ? CmsUrlNameMappingEntry.MAPPING_STATUS_PUBLISHED
6813                        : CmsUrlNameMappingEntry.MAPPING_STATUS_REPLACE_ON_PUBLISH_PUBLISHED,
6814                        now,
6815                        entry.getLocale());
6816                    vfsDriver.addUrlNameMappingEntry(dbc, true, newEntry);
6817                    vfsDriver.addUrlNameMappingEntry(dbc, false, newEntry);
6818                }
6819            }
6820        }
6821    }
6822
6823    /**
6824     * Reads an access control entry from the cms.<p>
6825     *
6826     * The access control entries of a resource are readable by everyone.
6827     *
6828     * @param dbc the current database context
6829     * @param resource the resource
6830     * @param principal the id of a group or a user any other entity
6831     * @return an access control entry that defines the permissions of the entity for the given resource
6832     * @throws CmsException if something goes wrong
6833     */
6834    public CmsAccessControlEntry readAccessControlEntry(CmsDbContext dbc, CmsResource resource, CmsUUID principal)
6835    throws CmsException {
6836
6837        return getUserDriver(
6838            dbc).readAccessControlEntry(dbc, dbc.currentProject(), resource.getResourceId(), principal);
6839    }
6840
6841    /**
6842     * Finds the alias with a given path.<p>
6843     *
6844     * If no alias is found, null is returned.<p>
6845     *
6846     * @param dbc the current database context
6847     * @param project the current project
6848     * @param siteRoot the site root
6849     * @param path the path of the alias
6850     *
6851     * @return the alias with the given path
6852     *
6853     * @throws CmsException if something goes wrong
6854     */
6855
6856    public CmsAlias readAliasByPath(CmsDbContext dbc, CmsProject project, String siteRoot, String path)
6857    throws CmsException {
6858
6859        List<CmsAlias> aliases = getVfsDriver(dbc).readAliases(dbc, project, new CmsAliasFilter(siteRoot, path, null));
6860        if (aliases.isEmpty()) {
6861            return null;
6862        } else {
6863            return aliases.get(0);
6864        }
6865    }
6866
6867    /**
6868     * Reads the aliases for a given site root.<p>
6869     *
6870     * @param dbc the current database context
6871     * @param currentProject the current project
6872     * @param siteRoot the site root
6873     *
6874     * @return the list of aliases for the given site root
6875     *
6876     * @throws CmsException if something goes wrong
6877     */
6878    public List<CmsAlias> readAliasesBySite(CmsDbContext dbc, CmsProject currentProject, String siteRoot)
6879    throws CmsException {
6880
6881        return getVfsDriver(dbc).readAliases(dbc, currentProject, new CmsAliasFilter(siteRoot, null, null));
6882    }
6883
6884    /**
6885     * Reads the aliases which point to a given structure id.<p>
6886     *
6887     * @param dbc the current database context
6888     * @param project the current project
6889     * @param structureId the structure id for which we want to read the aliases
6890     *
6891     * @return the list of aliases pointing to the structure id
6892     * @throws CmsException if something goes wrong
6893     */
6894    public List<CmsAlias> readAliasesByStructureId(CmsDbContext dbc, CmsProject project, CmsUUID structureId)
6895    throws CmsException {
6896
6897        return getVfsDriver(dbc).readAliases(dbc, project, new CmsAliasFilter(null, null, structureId));
6898    }
6899
6900    /**
6901     * Reads all versions of the given resource.<br>
6902     *
6903     * This method returns a list with the history of the given resource, i.e.
6904     * the historical resource entries, independent of the project they were attached to.<br>
6905     *
6906     * The reading excludes the file content.<p>
6907     *
6908     * @param dbc the current database context
6909     * @param resource the resource to read the history for
6910     *
6911     * @return a list of file headers, as <code>{@link I_CmsHistoryResource}</code> objects
6912     *
6913     * @throws CmsException if something goes wrong
6914     */
6915    public List<I_CmsHistoryResource> readAllAvailableVersions(CmsDbContext dbc, CmsResource resource)
6916    throws CmsException {
6917
6918        // read the historical resources
6919        List<I_CmsHistoryResource> versions = getHistoryDriver(dbc).readAllAvailableVersions(
6920            dbc,
6921            resource.getStructureId());
6922        if ((versions.size() > OpenCms.getSystemInfo().getHistoryVersions())
6923            && (OpenCms.getSystemInfo().getHistoryVersions() > -1)) {
6924            return versions.subList(0, OpenCms.getSystemInfo().getHistoryVersions());
6925        }
6926        return versions;
6927    }
6928
6929    /**
6930     * Reads all property definitions for the given mapping type.<p>
6931     *
6932     * @param dbc the current database context
6933     *
6934     * @return a list with the <code>{@link CmsPropertyDefinition}</code> objects (may be empty)
6935     *
6936     * @throws CmsException if something goes wrong
6937     */
6938    public List<CmsPropertyDefinition> readAllPropertyDefinitions(CmsDbContext dbc) throws CmsException {
6939
6940        List<CmsPropertyDefinition> result = getVfsDriver(dbc).readPropertyDefinitions(
6941            dbc,
6942            dbc.currentProject().getUuid());
6943        Collections.sort(result);
6944        return result;
6945    }
6946
6947    /**
6948     * Returns all resources subscribed by the given user or group.<p>
6949     *
6950     * @param dbc the database context
6951     * @param poolName the name of the database pool to use
6952     * @param principal the principal to read the subscribed resources
6953     *
6954     * @return all resources subscribed by the given user or group
6955     *
6956     * @throws CmsException if something goes wrong
6957     */
6958    public List<CmsResource> readAllSubscribedResources(CmsDbContext dbc, String poolName, CmsPrincipal principal)
6959    throws CmsException {
6960
6961        List<CmsResource> result = getSubscriptionDriver().readAllSubscribedResources(dbc, poolName, principal);
6962        result = filterPermissions(dbc, result, CmsResourceFilter.DEFAULT);
6963        return result;
6964    }
6965
6966    /**
6967     * Selects the best url name for a given resource and locale.<p>
6968     *
6969     * @param dbc the database context
6970     * @param id the resource's structure id
6971     * @param locale the requested locale
6972     * @param defaultLocales the default locales to use if the locale isn't available
6973     *
6974     * @return the URL name which was found
6975     *
6976     * @throws CmsDataAccessException if the database operation failed
6977     */
6978    public String readBestUrlName(CmsDbContext dbc, CmsUUID id, Locale locale, List<Locale> defaultLocales)
6979    throws CmsDataAccessException {
6980
6981        List<CmsUrlNameMappingEntry> entries = getVfsDriver(dbc).readUrlNameMappingEntries(
6982            dbc,
6983            dbc.currentProject().isOnlineProject(),
6984            CmsUrlNameMappingFilter.ALL.filterStructureId(id));
6985        if (entries.isEmpty()) {
6986            return null;
6987        }
6988
6989        ArrayListMultimap<String, CmsUrlNameMappingEntry> entriesByLocale = ArrayListMultimap.create();
6990        for (CmsUrlNameMappingEntry entry : entries) {
6991            entriesByLocale.put(entry.getLocale(), entry);
6992        }
6993        List<CmsUrlNameMappingEntry> lastEntries = new ArrayList<CmsUrlNameMappingEntry>();
6994        Comparator<CmsUrlNameMappingEntry> dateChangedComparator = new UrlNameMappingComparator();
6995        for (String localeKey : entriesByLocale.keySet()) {
6996            // for each locale select the latest mapping entry
6997            CmsUrlNameMappingEntry latestEntryForLocale = Collections.max(
6998                entriesByLocale.get(localeKey),
6999                dateChangedComparator);
7000            lastEntries.add(latestEntryForLocale);
7001        }
7002        CmsLocaleManager localeManager = OpenCms.getLocaleManager();
7003        List<Locale> availableLocales = new ArrayList<Locale>();
7004        for (CmsUrlNameMappingEntry entry : lastEntries) {
7005            availableLocales.add(CmsLocaleManager.getLocale(entry.getLocale()));
7006        }
7007        Locale bestLocale = localeManager.getBestMatchingLocale(locale, defaultLocales, availableLocales);
7008        String bestLocaleStr = bestLocale.toString();
7009        for (CmsUrlNameMappingEntry entry : lastEntries) {
7010            if (entry.getLocale().equals(bestLocaleStr)) {
7011                return entry.getName();
7012            }
7013        }
7014        return null;
7015    }
7016
7017    /**
7018     * Returns the child resources of a resource, that is the resources
7019     * contained in a folder.<p>
7020     *
7021     * With the parameters <code>getFolders</code> and <code>getFiles</code>
7022     * you can control what type of resources you want in the result list:
7023     * files, folders, or both.<p>
7024     *
7025     * This method is mainly used by the workplace explorer.<p>
7026     *
7027     * @param dbc the current database context
7028     * @param resource the resource to return the child resources for
7029     * @param filter the resource filter to use
7030     * @param getFolders if true the child folders are included in the result
7031     * @param getFiles if true the child files are included in the result
7032     * @param checkPermissions if the resources should be filtered with the current user permissions
7033     *
7034     * @return a list of all child resources
7035     *
7036     * @throws CmsException if something goes wrong
7037     */
7038    public List<CmsResource> readChildResources(
7039        CmsDbContext dbc,
7040        CmsResource resource,
7041        CmsResourceFilter filter,
7042        boolean getFolders,
7043        boolean getFiles,
7044        boolean checkPermissions)
7045    throws CmsException {
7046
7047        String cacheKey = null;
7048        List<CmsResource> resourceList = null;
7049        if (m_monitor.isEnabled(CmsMemoryMonitor.CacheType.RESOURCE_LIST)) { // check this here to skip the complex cache key generation
7050            String time = "";
7051            if (checkPermissions) {
7052                // ensure correct caching if site time offset is set
7053                if ((dbc.getRequestContext() != null)
7054                    && (OpenCms.getSiteManager().getSiteForSiteRoot(dbc.getRequestContext().getSiteRoot()) != null)) {
7055                    time += OpenCms.getSiteManager().getSiteForSiteRoot(
7056                        dbc.getRequestContext().getSiteRoot()).getSiteMatcher().getTimeOffset();
7057                }
7058            }
7059            // try to get the sub resources from the cache
7060            cacheKey = getCacheKey(
7061                new String[] {
7062                    dbc.currentUser().getName(),
7063                    getFolders
7064                    ? (getFiles ? CmsCacheKey.CACHE_KEY_SUBALL : CmsCacheKey.CACHE_KEY_SUBFOLDERS)
7065                    : CmsCacheKey.CACHE_KEY_SUBFILES,
7066                    checkPermissions ? "+" + time : "-",
7067                    filter.getCacheId(),
7068                    resource.getRootPath()},
7069                dbc);
7070
7071            resourceList = m_monitor.getCachedResourceList(cacheKey);
7072        }
7073        if ((resourceList == null) || !dbc.getProjectId().isNullUUID()) {
7074            // read the result form the database
7075            resourceList = getVfsDriver(
7076                dbc).readChildResources(dbc, dbc.currentProject(), resource, getFolders, getFiles);
7077
7078            if (checkPermissions) {
7079                // apply the permission filter
7080                resourceList = filterPermissions(dbc, resourceList, filter);
7081            }
7082            // cache the sub resources
7083            if (dbc.getProjectId().isNullUUID()) {
7084                m_monitor.cacheResourceList(cacheKey, resourceList);
7085            }
7086        }
7087
7088        // we must always apply the result filter and update the context dates
7089        return updateContextDates(dbc, resourceList, filter);
7090    }
7091
7092    /**
7093     * Returns the default file for the given folder.<p>
7094     *
7095     * If the given resource is a file, then this file is returned.<p>
7096     *
7097     * Otherwise, in case of a folder:<br>
7098     * <ol>
7099     *   <li>the {@link CmsPropertyDefinition#PROPERTY_DEFAULT_FILE} is checked, and
7100     *   <li>if still no file could be found, the configured default files in the
7101     *       <code>opencms-vfs.xml</code> configuration are iterated until a match is
7102     *       found, and
7103     *   <li>if still no file could be found, <code>null</code> is retuned
7104     * </ol>
7105     *
7106     * @param dbc the database context
7107     * @param resource the folder to get the default file for
7108     * @param resourceFilter the resource filter
7109     *
7110     * @return the default file for the given folder
7111     */
7112    public CmsResource readDefaultFile(CmsDbContext dbc, CmsResource resource, CmsResourceFilter resourceFilter) {
7113
7114        // resource exists, lets check if we have a file or a folder
7115        if (resource.isFolder()) {
7116            // the resource is a folder, check if PROPERTY_DEFAULT_FILE is set on folder
7117            try {
7118                String defaultFileName = readPropertyObject(
7119                    dbc,
7120                    resource,
7121                    CmsPropertyDefinition.PROPERTY_DEFAULT_FILE,
7122                    false).getValue();
7123                // check if the default file property does not match the navigation level folder marker value
7124                if ((defaultFileName != null) && !CmsJspNavBuilder.NAVIGATION_LEVEL_FOLDER.equals(defaultFileName)) {
7125                    // property was set, so look up this file first
7126                    String folderName = CmsResource.getFolderPath(resource.getRootPath());
7127                    resource = readResource(dbc, folderName + defaultFileName, resourceFilter.addRequireFile());
7128                }
7129            } catch (CmsException e) {
7130                // ignore all other exceptions and continue the lookup process
7131                if (LOG.isDebugEnabled()) {
7132                    LOG.debug(e.getLocalizedMessage(), e);
7133                }
7134            }
7135            if (resource.isFolder()) {
7136                String folderName = CmsResource.getFolderPath(resource.getRootPath());
7137                // resource is (still) a folder, check default files specified in configuration
7138                Iterator<String> it = OpenCms.getDefaultFiles().iterator();
7139                while (it.hasNext()) {
7140                    String tmpResourceName = folderName + it.next();
7141                    try {
7142                        resource = readResource(dbc, tmpResourceName, resourceFilter.addRequireFile());
7143                        // no exception? So we have found the default file
7144                        // stop looking for default files
7145                        break;
7146                    } catch (CmsException e) {
7147                        // ignore all other exceptions and continue the lookup process
7148                        if (LOG.isDebugEnabled()) {
7149                            LOG.debug(e.getLocalizedMessage(), e);
7150                        }
7151                    }
7152                }
7153            }
7154        }
7155        if (resource.isFolder()) {
7156            // we only want files as a result for further processing
7157            resource = null;
7158        }
7159        return resource;
7160    }
7161
7162    /**
7163     * Reads all deleted (historical) resources below the given path,
7164     * including the full tree below the path, if required.<p>
7165     *
7166     * @param dbc the current db context
7167     * @param resource the parent resource to read the resources from
7168     * @param readTree <code>true</code> to read all subresources
7169     * @param isVfsManager <code>true</code> if the current user has the vfs manager role
7170     *
7171     * @return a list of <code>{@link I_CmsHistoryResource}</code> objects
7172     *
7173     * @throws CmsException if something goes wrong
7174     *
7175     * @see CmsObject#readResource(CmsUUID, int)
7176     * @see CmsObject#readResources(String, CmsResourceFilter, boolean)
7177     * @see CmsObject#readDeletedResources(String, boolean)
7178     */
7179    public List<I_CmsHistoryResource> readDeletedResources(
7180        CmsDbContext dbc,
7181        CmsResource resource,
7182        boolean readTree,
7183        boolean isVfsManager)
7184    throws CmsException {
7185
7186        Set<I_CmsHistoryResource> result = new HashSet<I_CmsHistoryResource>();
7187        List<I_CmsHistoryResource> deletedResources;
7188        dbc.getRequestContext().setAttribute("ATTR_RESOURCE_NAME", resource.getRootPath());
7189        try {
7190            deletedResources = getHistoryDriver(dbc).readDeletedResources(
7191                dbc,
7192                resource.getStructureId(),
7193                isVfsManager ? null : dbc.currentUser().getId());
7194        } finally {
7195            dbc.getRequestContext().removeAttribute("ATTR_RESOURCE_NAME");
7196        }
7197        result.addAll(deletedResources);
7198        Set<I_CmsHistoryResource> newResult = new HashSet<I_CmsHistoryResource>(result.size());
7199        I_CmsVfsDriver vfsDriver = getVfsDriver(dbc);
7200        Iterator<I_CmsHistoryResource> it = result.iterator();
7201        while (it.hasNext()) {
7202            I_CmsHistoryResource histRes = it.next();
7203            // adjust the paths
7204            try {
7205                if (vfsDriver.validateStructureIdExists(
7206                    dbc,
7207                    dbc.currentProject().getUuid(),
7208                    histRes.getStructureId())) {
7209                    newResult.add(histRes);
7210                    continue;
7211                }
7212                // adjust the path in case of deleted files
7213                String resourcePath = histRes.getRootPath();
7214                String resName = CmsResource.getName(resourcePath);
7215                String path = CmsResource.getParentFolder(resourcePath);
7216
7217                CmsUUID parentId = histRes.getParentId();
7218                try {
7219                    // first look for the path through the parent id
7220                    path = readResource(dbc, parentId, CmsResourceFilter.IGNORE_EXPIRATION).getRootPath();
7221                } catch (CmsDataAccessException e) {
7222                    // if the resource with the parent id is not found, try to get a new parent id with the path
7223                    try {
7224                        parentId = readResource(dbc, path, CmsResourceFilter.IGNORE_EXPIRATION).getStructureId();
7225                    } catch (CmsDataAccessException e1) {
7226                        // ignore, the parent folder has been completely deleted
7227                    }
7228                }
7229                resourcePath = path + resName;
7230
7231                boolean isFolder = resourcePath.endsWith("/");
7232                if (isFolder) {
7233                    newResult.add(
7234                        new CmsHistoryFolder(
7235                            histRes.getPublishTag(),
7236                            histRes.getStructureId(),
7237                            histRes.getResourceId(),
7238                            resourcePath,
7239                            histRes.getTypeId(),
7240                            histRes.getFlags(),
7241                            histRes.getProjectLastModified(),
7242                            histRes.getState(),
7243                            histRes.getDateCreated(),
7244                            histRes.getUserCreated(),
7245                            histRes.getDateLastModified(),
7246                            histRes.getUserLastModified(),
7247                            histRes.getDateReleased(),
7248                            histRes.getDateExpired(),
7249                            histRes.getVersion(),
7250                            parentId,
7251                            histRes.getResourceVersion(),
7252                            histRes.getStructureVersion()));
7253                } else {
7254                    newResult.add(
7255                        new CmsHistoryFile(
7256                            histRes.getPublishTag(),
7257                            histRes.getStructureId(),
7258                            histRes.getResourceId(),
7259                            resourcePath,
7260                            histRes.getTypeId(),
7261                            histRes.getFlags(),
7262                            histRes.getProjectLastModified(),
7263                            histRes.getState(),
7264                            histRes.getDateCreated(),
7265                            histRes.getUserCreated(),
7266                            histRes.getDateLastModified(),
7267                            histRes.getUserLastModified(),
7268                            histRes.getDateReleased(),
7269                            histRes.getDateExpired(),
7270                            histRes.getLength(),
7271                            histRes.getDateContent(),
7272                            histRes.getVersion(),
7273                            parentId,
7274                            null,
7275                            histRes.getResourceVersion(),
7276                            histRes.getStructureVersion()));
7277                }
7278            } catch (CmsDataAccessException e) {
7279                // should never happen
7280                if (LOG.isErrorEnabled()) {
7281                    LOG.error(e.getLocalizedMessage(), e);
7282                }
7283            }
7284        }
7285        if (readTree) {
7286            Iterator<I_CmsHistoryResource> itDeleted = deletedResources.iterator();
7287            while (itDeleted.hasNext()) {
7288                I_CmsHistoryResource delResource = itDeleted.next();
7289                if (delResource.isFolder()) {
7290                    newResult.addAll(readDeletedResources(dbc, (CmsFolder)delResource, readTree, isVfsManager));
7291                }
7292            }
7293            try {
7294                readResource(dbc, resource.getStructureId(), CmsResourceFilter.ALL);
7295                // resource exists, so recurse
7296                Iterator<CmsResource> itResources = readResources(
7297                    dbc,
7298                    resource,
7299                    CmsResourceFilter.ALL.addRequireFolder(),
7300                    readTree).iterator();
7301                while (itResources.hasNext()) {
7302                    CmsResource subResource = itResources.next();
7303                    if (subResource.isFolder()) {
7304                        newResult.addAll(readDeletedResources(dbc, subResource, readTree, isVfsManager));
7305                    }
7306                }
7307            } catch (Exception e) {
7308                // resource does not exists
7309                if (LOG.isDebugEnabled()) {
7310                    LOG.debug(e.getLocalizedMessage(), e);
7311                }
7312            }
7313        }
7314        List<I_CmsHistoryResource> finalRes = new ArrayList<I_CmsHistoryResource>(newResult);
7315        Collections.sort(finalRes, I_CmsResource.COMPARE_ROOT_PATH);
7316        return finalRes;
7317    }
7318
7319    /**
7320     * Reads a file resource (including it's binary content) from the VFS,
7321     * using the specified resource filter.<p>
7322     *
7323     * In case you do not need the file content,
7324     * use <code>{@link #readResource(CmsDbContext, String, CmsResourceFilter)}</code> instead.<p>
7325     *
7326     * The specified filter controls what kind of resources should be "found"
7327     * during the read operation. This will depend on the application. For example,
7328     * using <code>{@link CmsResourceFilter#DEFAULT}</code> will only return currently
7329     * "valid" resources, while using <code>{@link CmsResourceFilter#IGNORE_EXPIRATION}</code>
7330     * will ignore the date release / date expired information of the resource.<p>
7331     *
7332     * @param dbc the current database context
7333     * @param resource the base file resource (without content)
7334     * @return the file read from the VFS
7335     * @throws CmsException if operation was not successful
7336     */
7337    public CmsFile readFile(CmsDbContext dbc, CmsResource resource) throws CmsException {
7338
7339        if (resource.isFolder()) {
7340            throw new CmsVfsResourceNotFoundException(
7341                Messages.get().container(
7342                    Messages.ERR_ACCESS_FOLDER_AS_FILE_1,
7343                    dbc.removeSiteRoot(resource.getRootPath())));
7344        }
7345
7346        CmsUUID projectId = dbc.currentProject().getUuid();
7347        CmsFile file = null;
7348        if (resource instanceof I_CmsHistoryResource) {
7349            file = new CmsHistoryFile((I_CmsHistoryResource)resource);
7350            file.setContents(
7351                getHistoryDriver(dbc).readContent(
7352                    dbc,
7353                    resource.getResourceId(),
7354                    ((I_CmsHistoryResource)resource).getPublishTag()));
7355        } else {
7356            file = new CmsFile(resource);
7357            file.setContents(getVfsDriver(dbc).readContent(dbc, projectId, resource.getResourceId()));
7358        }
7359        return file;
7360    }
7361
7362    /**
7363     * Reads a folder from the VFS,
7364     * using the specified resource filter.<p>
7365     *
7366     * @param dbc the current database context
7367     * @param resourcename the name of the folder to read (full path)
7368     * @param filter the resource filter to use while reading
7369     *
7370     * @return the folder that was read
7371     *
7372     * @throws CmsDataAccessException if something goes wrong
7373     *
7374     * @see #readResource(CmsDbContext, String, CmsResourceFilter)
7375     * @see CmsObject#readFolder(String)
7376     * @see CmsObject#readFolder(String, CmsResourceFilter)
7377     */
7378    public CmsFolder readFolder(CmsDbContext dbc, String resourcename, CmsResourceFilter filter)
7379    throws CmsDataAccessException {
7380
7381        CmsResource resource = readResource(dbc, resourcename, filter);
7382
7383        return convertResourceToFolder(resource);
7384    }
7385
7386    public List<CmsFolderSizeEntry> readFolderSizeStats(CmsDbContext dbc, CmsFolderSizeOptions options)
7387    throws CmsException {
7388
7389        I_CmsVfsDriver vfsDriver = getVfsDriver(dbc);
7390        List<CmsFolderSizeEntry> stats = vfsDriver.readFolderSizeStats(dbc, false, options);
7391        return stats;
7392
7393    }
7394
7395    /**
7396     * Reads the group of a project.<p>
7397     *
7398     * @param dbc the current database context
7399     * @param project the project to read from
7400     *
7401     * @return the group of a resource
7402     */
7403    public CmsGroup readGroup(CmsDbContext dbc, CmsProject project) {
7404
7405        try {
7406            return readGroup(dbc, project.getGroupId());
7407        } catch (CmsException exc) {
7408            return new CmsGroup(
7409                CmsUUID.getNullUUID(),
7410                CmsUUID.getNullUUID(),
7411                project.getGroupId() + "",
7412                "deleted group",
7413                0);
7414        }
7415    }
7416
7417    /**
7418     * Reads a group based on its id.<p>
7419     *
7420     * @param dbc the current database context
7421     * @param groupId the id of the group that is to be read
7422     *
7423     * @return the requested group
7424     *
7425     * @throws CmsException if operation was not successful
7426     */
7427    public CmsGroup readGroup(CmsDbContext dbc, CmsUUID groupId) throws CmsException {
7428
7429        CmsGroup group = null;
7430        // try to read group from cache
7431        group = m_monitor.getCachedGroup(groupId.toString());
7432        if (group == null) {
7433            group = getUserDriver(dbc).readGroup(dbc, groupId);
7434            m_monitor.cacheGroup(group);
7435        }
7436        return group;
7437    }
7438
7439    /**
7440     * Reads a group based on its name.<p>
7441     *
7442     * @param dbc the current database context
7443     * @param groupname the name of the group that is to be read
7444     *
7445     * @return the requested group
7446     *
7447     * @throws CmsDataAccessException if operation was not successful
7448     */
7449    public CmsGroup readGroup(CmsDbContext dbc, String groupname) throws CmsDataAccessException {
7450
7451        CmsGroup group = null;
7452        // try to read group from cache
7453        group = m_monitor.getCachedGroup(groupname);
7454        if (group == null) {
7455            group = getUserDriver(dbc).readGroup(dbc, groupname);
7456            m_monitor.cacheGroup(group);
7457        }
7458        return group;
7459    }
7460
7461    /**
7462     * Reads a principal (an user or group) from the historical archive based on its ID.<p>
7463     *
7464     * @param dbc the current database context
7465     * @param principalId the id of the principal to read
7466     *
7467     * @return the historical principal entry with the given id
7468     *
7469     * @throws CmsException if something goes wrong, ie. {@link CmsDbEntryNotFoundException}
7470     *
7471     * @see CmsObject#readUser(CmsUUID)
7472     * @see CmsObject#readGroup(CmsUUID)
7473     * @see CmsObject#readHistoryPrincipal(CmsUUID)
7474     */
7475    public CmsHistoryPrincipal readHistoricalPrincipal(CmsDbContext dbc, CmsUUID principalId) throws CmsException {
7476
7477        return getHistoryDriver(dbc).readPrincipal(dbc, principalId);
7478    }
7479
7480    /**
7481     * Returns the latest historical project entry with the given id.<p>
7482     *
7483     * @param dbc the current database context
7484     * @param projectId the project id
7485     *
7486     * @return the requested historical project entry
7487     *
7488     * @throws CmsException if something goes wrong
7489     */
7490    public CmsHistoryProject readHistoryProject(CmsDbContext dbc, CmsUUID projectId) throws CmsException {
7491
7492        return getHistoryDriver(dbc).readProject(dbc, projectId);
7493    }
7494
7495    /**
7496     * Returns a historical project entry.<p>
7497     *
7498     * @param dbc the current database context
7499     * @param publishTag the publish tag of the project
7500     *
7501     * @return the requested historical project entry
7502     *
7503     * @throws CmsException if something goes wrong
7504     */
7505    public CmsHistoryProject readHistoryProject(CmsDbContext dbc, int publishTag) throws CmsException {
7506
7507        return getHistoryDriver(dbc).readProject(dbc, publishTag);
7508    }
7509
7510    /**
7511     * Reads the list of all <code>{@link CmsProperty}</code> objects that belongs to the given historical resource.<p>
7512     *
7513     * @param dbc the current database context
7514     * @param historyResource the historical resource to read the properties for
7515     *
7516     * @return the list of <code>{@link CmsProperty}</code> objects
7517     *
7518     * @throws CmsException if something goes wrong
7519     */
7520    public List<CmsProperty> readHistoryPropertyObjects(CmsDbContext dbc, I_CmsHistoryResource historyResource)
7521    throws CmsException {
7522
7523        return getHistoryDriver(dbc).readProperties(dbc, historyResource);
7524    }
7525
7526    /**
7527     * Reads the structure id which is mapped to a given URL name.<p>
7528     *
7529     * @param dbc the current database context
7530     * @param name the name for which the mapped structure id should be looked up
7531     *
7532     * @return the structure id which is mapped to the given name, or null if there is no such id
7533     *
7534     * @throws CmsDataAccessException if something goes wrong
7535     */
7536    public CmsUUID readIdForUrlName(CmsDbContext dbc, String name) throws CmsDataAccessException {
7537
7538        List<CmsUrlNameMappingEntry> entries = getVfsDriver(dbc).readUrlNameMappingEntries(
7539            dbc,
7540            dbc.currentProject().isOnlineProject(),
7541            CmsUrlNameMappingFilter.ALL.filterName(name));
7542        if (entries.isEmpty()) {
7543            return null;
7544        }
7545        return entries.get(0).getStructureId();
7546    }
7547
7548    /**
7549     * Reads the locks that were saved to the database in the previous run of OpenCms.<p>
7550     *
7551     * @param dbc the current database context
7552     *
7553     * @throws CmsException if something goes wrong
7554     */
7555    public void readLocks(CmsDbContext dbc) throws CmsException {
7556
7557        m_lockManager.readLocks(dbc);
7558    }
7559
7560    /**
7561     * Reads the manager group of a project.<p>
7562     *
7563     * @param dbc the current database context
7564     * @param project the project to read from
7565     *
7566     * @return the group of a resource
7567     */
7568    public CmsGroup readManagerGroup(CmsDbContext dbc, CmsProject project) {
7569
7570        try {
7571            return readGroup(dbc, project.getManagerGroupId());
7572        } catch (CmsException exc) {
7573            // the group does not exist any more - return a dummy-group
7574            return new CmsGroup(
7575                CmsUUID.getNullUUID(),
7576                CmsUUID.getNullUUID(),
7577                project.getManagerGroupId() + "",
7578                "deleted group",
7579                0);
7580        }
7581    }
7582
7583    /**
7584     * Reads the URL name which has been most recently mapped to the given structure id, or null
7585     * if no URL name is mapped to the id.<p>
7586     *
7587     * @param dbc the current database context
7588     * @param id a structure id
7589     * @return the name which has been most recently mapped to the given structure id
7590     *
7591     * @throws CmsDataAccessException if something goes wrong
7592     */
7593    public String readNewestUrlNameForId(CmsDbContext dbc, CmsUUID id) throws CmsDataAccessException {
7594
7595        List<CmsUrlNameMappingEntry> entries = getVfsDriver(dbc).readUrlNameMappingEntries(
7596            dbc,
7597            dbc.currentProject().isOnlineProject(),
7598            CmsUrlNameMappingFilter.ALL.filterStructureId(id));
7599        if (entries.isEmpty()) {
7600            return null;
7601        }
7602
7603        Collections.sort(entries, new UrlNameMappingComparator());
7604        CmsUrlNameMappingEntry lastEntry = entries.get(entries.size() - 1);
7605        return lastEntry.getName();
7606    }
7607
7608    /**
7609     * Reads an organizational Unit based on its fully qualified name.<p>
7610     *
7611     * @param dbc the current db context
7612     * @param ouFqn the fully qualified name of the organizational Unit to be read
7613     *
7614     * @return the organizational Unit that with the provided fully qualified name
7615     *
7616     * @throws CmsException if something goes wrong
7617     */
7618    public CmsOrganizationalUnit readOrganizationalUnit(CmsDbContext dbc, String ouFqn) throws CmsException {
7619
7620        CmsOrganizationalUnit organizationalUnit = null;
7621        // try to read organizational unit from cache
7622        organizationalUnit = m_monitor.getCachedOrgUnit(ouFqn);
7623        if (organizationalUnit == null) {
7624            organizationalUnit = getUserDriver(dbc).readOrganizationalUnit(dbc, ouFqn);
7625            m_monitor.cacheOrgUnit(organizationalUnit);
7626        }
7627        return organizationalUnit;
7628    }
7629
7630    /**
7631     * Reads the owner of a project.<p>
7632     *
7633     * @param dbc the current database context
7634     * @param project the project to get the owner from
7635     *
7636     * @return the owner of a resource
7637     * @throws CmsException if something goes wrong
7638     */
7639    public CmsUser readOwner(CmsDbContext dbc, CmsProject project) throws CmsException {
7640
7641        return readUser(dbc, project.getOwnerId());
7642    }
7643
7644    /**
7645     * Reads the parent folder to a given structure id.<p>
7646     *
7647     * @param dbc the current database context
7648     * @param structureId the structure id of the child
7649     *
7650     * @return the parent folder resource
7651     *
7652     * @throws CmsDataAccessException if something goes wrong
7653     */
7654    public CmsResource readParentFolder(CmsDbContext dbc, CmsUUID structureId) throws CmsDataAccessException {
7655
7656        return getVfsDriver(dbc).readParentFolder(dbc, dbc.currentProject().getUuid(), structureId);
7657    }
7658
7659    /**
7660     * Builds a list of resources for a given path.<p>
7661     *
7662     * @param dbc the current database context
7663     * @param path the requested path
7664     * @param filter a filter object (only "includeDeleted" information is used!)
7665     *
7666     * @return list of <code>{@link CmsResource}</code>s
7667     *
7668     * @throws CmsException if something goes wrong
7669     */
7670    public List<CmsResource> readPath(CmsDbContext dbc, String path, CmsResourceFilter filter) throws CmsException {
7671
7672        // splits the path into folder and filename tokens
7673        List<String> tokens = CmsStringUtil.splitAsList(path, '/');
7674
7675        // the root folder is no token in the path but a resource which has to be added to the path
7676        int count = tokens.size() + 1;
7677        // holds the CmsResource instances in the path
7678        List<CmsResource> pathList = new ArrayList<CmsResource>(count);
7679
7680        // true if the path doesn't end with a folder
7681        boolean lastResourceIsFile = false;
7682        // number of folders in the path
7683        int folderCount = count;
7684        if (!path.endsWith("/")) {
7685            folderCount--;
7686            lastResourceIsFile = true;
7687        }
7688
7689        // read the root folder, because it's ID is required to read any sub-resources
7690        String currentResourceName = "/";
7691        StringBuffer currentPath = new StringBuffer(64);
7692        currentPath.append('/');
7693
7694        String cp = currentPath.toString();
7695        CmsUUID projectId = getProjectIdForContext(dbc);
7696
7697        // key to cache the resources
7698        String cacheKey = getCacheKey(null, false, projectId, cp);
7699        // the current resource
7700        CmsResource currentResource = m_monitor.getCachedResource(cacheKey);
7701        if ((currentResource == null) || !dbc.getProjectId().isNullUUID()) {
7702            currentResource = getVfsDriver(dbc).readFolder(dbc, projectId, cp);
7703            if (dbc.getProjectId().isNullUUID()) {
7704                m_monitor.cacheResource(cacheKey, currentResource);
7705            }
7706        }
7707
7708        pathList.add(0, currentResource);
7709
7710        if (count == 1) {
7711            // the root folder was requested- no further operations required
7712            return pathList;
7713        }
7714
7715        Iterator<String> it = tokens.iterator();
7716        currentResourceName = it.next();
7717
7718        // read the folder resources in the path /a/b/c/
7719        int i = 0;
7720        for (i = 1; i < folderCount; i++) {
7721            currentPath.append(currentResourceName);
7722            currentPath.append('/');
7723            // read the folder
7724            cp = currentPath.toString();
7725            cacheKey = getCacheKey(null, false, projectId, cp);
7726            currentResource = m_monitor.getCachedResource(cacheKey);
7727            if ((currentResource == null) || !dbc.getProjectId().isNullUUID()) {
7728                currentResource = getVfsDriver(dbc).readFolder(dbc, projectId, cp);
7729                if (dbc.getProjectId().isNullUUID()) {
7730                    m_monitor.cacheResource(cacheKey, currentResource);
7731                }
7732            }
7733
7734            pathList.add(i, currentResource);
7735
7736            if (i < (folderCount - 1)) {
7737                currentResourceName = it.next();
7738            }
7739        }
7740
7741        // read the (optional) last file resource in the path /x.html
7742        if (lastResourceIsFile) {
7743            if (it.hasNext()) {
7744                // this will only be false if a resource in the
7745                // top level root folder (e.g. "/index.html") was requested
7746                currentResourceName = it.next();
7747            }
7748            currentPath.append(currentResourceName);
7749
7750            // read the file
7751            cp = currentPath.toString();
7752            cacheKey = getCacheKey(null, false, projectId, cp);
7753            currentResource = m_monitor.getCachedResource(cacheKey);
7754            if ((currentResource == null) || !dbc.getProjectId().isNullUUID()) {
7755                currentResource = getVfsDriver(dbc).readResource(dbc, projectId, cp, filter.includeDeleted());
7756                if (dbc.getProjectId().isNullUUID()) {
7757                    m_monitor.cacheResource(cacheKey, currentResource);
7758                }
7759            }
7760
7761            pathList.add(i, currentResource);
7762        }
7763
7764        return pathList;
7765    }
7766
7767    /**
7768     * Reads a project given the projects id.<p>
7769     *
7770     * @param dbc the current database context
7771     * @param id the id of the project
7772     *
7773     * @return the project read
7774     *
7775     * @throws CmsDataAccessException if something goes wrong
7776     */
7777    public CmsProject readProject(CmsDbContext dbc, CmsUUID id) throws CmsDataAccessException {
7778
7779        CmsProject project = null;
7780        project = m_monitor.getCachedProject(id.toString());
7781        if (project == null) {
7782            project = getProjectDriver(dbc).readProject(dbc, id);
7783            m_monitor.cacheProject(project);
7784        }
7785        return project;
7786    }
7787
7788    /**
7789     * Reads a project.<p>
7790     *
7791     * Important: Since a project name can be used multiple times, this is NOT the most efficient
7792     * way to read the project. This is only a convenience for front end developing.
7793     * Reading a project by name will return the first project with that name.
7794     * All core classes must use the id version {@link #readProject(CmsDbContext, CmsUUID)} to ensure the right project is read.<p>
7795     *
7796     * @param dbc the current database context
7797     * @param name the name of the project
7798     *
7799     * @return the project read
7800     *
7801     * @throws CmsException if something goes wrong
7802     */
7803    public CmsProject readProject(CmsDbContext dbc, String name) throws CmsException {
7804
7805        CmsProject project = null;
7806        project = m_monitor.getCachedProject(name);
7807        if (project == null) {
7808            project = getProjectDriver(dbc).readProject(dbc, name);
7809            m_monitor.cacheProject(project);
7810        }
7811        return project;
7812    }
7813
7814    /**
7815     * Returns the list of all resource names that define the "view" of the given project.<p>
7816     *
7817     * @param dbc the current database context
7818     * @param project the project to get the project resources for
7819     *
7820     * @return the list of all resources, as <code>{@link String}</code> objects
7821     *              that define the "view" of the given project.
7822     *
7823     * @throws CmsException if something goes wrong
7824     */
7825    public List<String> readProjectResources(CmsDbContext dbc, CmsProject project) throws CmsException {
7826
7827        return getProjectDriver(dbc).readProjectResources(dbc, project);
7828    }
7829
7830    /**
7831     * Reads all resources of a project that match a given state from the VFS.<p>
7832     *
7833     * Possible values for the <code>state</code> parameter are:<br>
7834     * <ul>
7835     * <li><code>{@link CmsResource#STATE_CHANGED}</code>: Read all "changed" resources in the project</li>
7836     * <li><code>{@link CmsResource#STATE_NEW}</code>: Read all "new" resources in the project</li>
7837     * <li><code>{@link CmsResource#STATE_DELETED}</code>: Read all "deleted" resources in the project</li>
7838     * <li><code>{@link CmsResource#STATE_KEEP}</code>: Read all resources either "changed", "new" or "deleted" in the project</li>
7839     * </ul><p>
7840     *
7841     * @param dbc the current database context
7842     * @param projectId the id of the project to read the file resources for
7843     * @param state the resource state to match
7844     *
7845     * @return a list of <code>{@link CmsResource}</code> objects matching the filter criteria
7846     *
7847     * @throws CmsException if something goes wrong
7848     *
7849     * @see CmsObject#readProjectView(CmsUUID, CmsResourceState)
7850     */
7851    public List<CmsResource> readProjectView(CmsDbContext dbc, CmsUUID projectId, CmsResourceState state)
7852    throws CmsException {
7853
7854        List<CmsResource> resources;
7855        if (state.isNew() || state.isChanged() || state.isDeleted()) {
7856            // get all resources form the database that match the selected state
7857            resources = getVfsDriver(dbc).readResources(dbc, projectId, state, CmsDriverManager.READMODE_MATCHSTATE);
7858        } else {
7859            // get all resources form the database that are somehow changed (i.e. not unchanged)
7860            resources = getVfsDriver(
7861                dbc).readResources(dbc, projectId, CmsResource.STATE_UNCHANGED, CmsDriverManager.READMODE_UNMATCHSTATE);
7862        }
7863
7864        // filter the permissions
7865        List<CmsResource> result = filterPermissions(dbc, resources, CmsResourceFilter.ALL);
7866        // sort the result
7867        Collections.sort(result);
7868        // set the full resource names
7869        return updateContextDates(dbc, result);
7870    }
7871
7872    /**
7873     * Reads a property definition.<p>
7874     *
7875     * If no property definition with the given name is found,
7876     * <code>null</code> is returned.<p>
7877     *
7878     * @param dbc the current database context
7879     * @param name the name of the property definition to read
7880     *
7881     * @return the property definition that was read
7882     *
7883     * @throws CmsException a CmsDbEntryNotFoundException is thrown if the property definition does not exist
7884     */
7885    public CmsPropertyDefinition readPropertyDefinition(CmsDbContext dbc, String name) throws CmsException {
7886
7887        return getVfsDriver(dbc).readPropertyDefinition(dbc, name, dbc.currentProject().getUuid());
7888    }
7889
7890    /**
7891     * Reads a property object from a resource specified by a property name.<p>
7892     *
7893     * Returns <code>{@link CmsProperty#getNullProperty()}</code> if the property is not found.<p>
7894     *
7895     * @param dbc the current database context
7896     * @param resource the resource where the property is read from
7897     * @param key the property key name
7898     * @param search if <code>true</code>, the property is searched on all parent folders of the resource.
7899     *      if it's not found attached directly to the resource.
7900     *
7901     * @return the required property, or <code>{@link CmsProperty#getNullProperty()}</code> if the property was not found
7902     *
7903     * @throws CmsException if something goes wrong
7904     */
7905    public CmsProperty readPropertyObject(CmsDbContext dbc, CmsResource resource, String key, boolean search)
7906    throws CmsException {
7907
7908        // NOTE: Do not call readPropertyObject(dbc, resource, key, search, null) for performance reasons
7909
7910        // use the list reading method to obtain all properties for the resource
7911        List<CmsProperty> properties = readPropertyObjects(dbc, resource, search);
7912
7913        int i = properties.indexOf(new CmsProperty(key, null, null));
7914        if (i >= 0) {
7915            // property has been found in the map
7916            CmsProperty result = properties.get(i);
7917            // ensure the result value is not frozen
7918            return result.cloneAsProperty();
7919        }
7920        return CmsProperty.getNullProperty();
7921
7922    }
7923
7924    /**
7925     * Reads a property object from a resource specified by a property name.<p>
7926     *
7927     * Returns <code>{@link CmsProperty#getNullProperty()}</code> if the property is not found.<p>
7928     *
7929     * @param dbc the current database context
7930     * @param resource the resource where the property is read from
7931     * @param key the property key name
7932     * @param search if <code>true</code>, the property is searched on all parent folders of the resource.
7933     *      if it's not found attached directly to the resource.
7934     * @param locale the locale for which the property should be read.
7935     *
7936     * @return the required property, or <code>{@link CmsProperty#getNullProperty()}</code> if the property was not found
7937     *
7938     * @throws CmsException if something goes wrong
7939     */
7940    public CmsProperty readPropertyObject(
7941        CmsDbContext dbc,
7942        CmsResource resource,
7943        String key,
7944        boolean search,
7945        Locale locale)
7946    throws CmsException {
7947
7948        // use the list reading method to obtain all properties for the resource
7949        List<CmsProperty> properties = readPropertyObjects(dbc, resource, search);
7950        // create a lookup property object and look this up in the result map
7951        CmsProperty result = null;
7952        // handle the case without locale separately to improve performance
7953        for (String localizedKey : CmsLocaleManager.getLocaleVariants(key, locale, true, false)) {
7954            int i = properties.indexOf(new CmsProperty(localizedKey, null, null));
7955            if (i >= 0) {
7956                // property has been found in the map
7957                result = properties.get(i);
7958                // ensure the result value is not frozen
7959                return result.cloneAsProperty();
7960            }
7961        }
7962        return CmsProperty.getNullProperty();
7963    }
7964
7965    /**
7966     * Reads all property objects mapped to a specified resource from the database.<p>
7967     *
7968     * All properties in the result List will be in frozen (read only) state, so you can't change the values.<p>
7969     *
7970     * Returns an empty list if no properties are found at all.<p>
7971     *
7972     * @param dbc the current database context
7973     * @param resource the resource where the properties are read from
7974     * @param search true, if the properties should be searched on all parent folders  if not found on the resource
7975     *
7976     * @return a list of CmsProperty objects containing the structure and/or resource value
7977     *
7978     * @throws CmsException if something goes wrong
7979     *
7980     * @see CmsObject#readPropertyObjects(String, boolean)
7981     */
7982    public List<CmsProperty> readPropertyObjects(CmsDbContext dbc, CmsResource resource, boolean search)
7983    throws CmsException {
7984
7985        // check if we have the result already cached
7986        CmsUUID projectId = getProjectIdForContext(dbc);
7987        String cacheKey = getCacheKey(CACHE_ALL_PROPERTIES, search, projectId, resource.getRootPath());
7988
7989        List<CmsProperty> properties = m_monitor.getCachedPropertyList(cacheKey);
7990
7991        if ((properties == null) || !dbc.getProjectId().isNullUUID()) {
7992            // result not cached, let's look it up in the DB
7993            if (search) {
7994                boolean cont;
7995                properties = new ArrayList<CmsProperty>();
7996                List<CmsProperty> parentProperties = null;
7997
7998                do {
7999                    try {
8000                        parentProperties = readPropertyObjects(dbc, resource, false);
8001
8002                        // make sure properties from lower folders "overwrite" properties from upper folders
8003                        parentProperties.removeAll(properties);
8004                        parentProperties.addAll(properties);
8005
8006                        properties.clear();
8007                        properties.addAll(parentProperties);
8008
8009                        cont = resource.getRootPath().length() > 1;
8010                    } catch (CmsSecurityException se) {
8011                        // a security exception (probably no read permission) we return the current result
8012                        cont = false;
8013                    }
8014                    if (cont) {
8015                        // no permission check on parent folder is required since we must have "read"
8016                        // permissions to read the child resource anyway
8017                        resource = readResource(
8018                            dbc,
8019                            CmsResource.getParentFolder(resource.getRootPath()),
8020                            CmsResourceFilter.ALL);
8021                    }
8022                } while (cont);
8023            } else {
8024                properties = getVfsDriver(dbc).readPropertyObjects(dbc, dbc.currentProject(), resource);
8025                //                for (CmsProperty prop : properties) {
8026                //                    prop.setOrigin(resource.getRootPath());
8027                //                }
8028            }
8029
8030            // set all properties in the result list as frozen
8031            CmsProperty.setFrozen(properties);
8032            if (dbc.getProjectId().isNullUUID()) {
8033                // store the result in the cache if needed
8034                m_monitor.cachePropertyList(cacheKey, properties);
8035            }
8036        }
8037
8038        return new ArrayList<CmsProperty>(properties);
8039    }
8040
8041    /**
8042     * Reads the resources that were published in a publish task for a given publish history ID.<p>
8043     *
8044     * @param dbc the current database context
8045     * @param publishHistoryId unique int ID to identify each publish task in the publish history
8046     *
8047     * @return a list of <code>{@link org.opencms.db.CmsPublishedResource}</code> objects
8048     *
8049     * @throws CmsException if something goes wrong
8050     */
8051    public List<CmsPublishedResource> readPublishedResources(CmsDbContext dbc, CmsUUID publishHistoryId)
8052    throws CmsException {
8053
8054        String cacheKey = publishHistoryId.toString();
8055        List<CmsPublishedResource> resourceList = m_monitor.getCachedPublishedResources(cacheKey);
8056        if ((resourceList == null) || !dbc.getProjectId().isNullUUID()) {
8057            resourceList = getProjectDriver(dbc).readPublishedResources(dbc, publishHistoryId);
8058            // store the result in the cache
8059            if (dbc.getProjectId().isNullUUID()) {
8060                m_monitor.cachePublishedResources(cacheKey, resourceList);
8061            }
8062        }
8063        return resourceList;
8064    }
8065
8066    /**
8067     * Reads a single publish job identified by its publish history id.<p>
8068     *
8069     * @param dbc the current database context
8070     * @param publishHistoryId unique id to identify the publish job in the publish history
8071     * @return an object of type <code>{@link CmsPublishJobInfoBean}</code>
8072     *
8073     * @throws CmsException if something goes wrong
8074     */
8075    public CmsPublishJobInfoBean readPublishJob(CmsDbContext dbc, CmsUUID publishHistoryId) throws CmsException {
8076
8077        return getProjectDriver(dbc).readPublishJob(dbc, publishHistoryId);
8078    }
8079
8080    /**
8081     * Reads all available publish jobs.<p>
8082     *
8083     * @param dbc the current database context
8084     * @param startTime the start of the time range for finish time
8085     * @param endTime the end of the time range for finish time
8086     * @return a list of objects of type <code>{@link CmsPublishJobInfoBean}</code>
8087     *
8088     * @throws CmsException if something goes wrong
8089     */
8090    public List<CmsPublishJobInfoBean> readPublishJobs(CmsDbContext dbc, long startTime, long endTime)
8091    throws CmsException {
8092
8093        return getProjectDriver(dbc).readPublishJobs(dbc, startTime, endTime);
8094    }
8095
8096    /**
8097     * Reads the publish list assigned to a publish job.<p>
8098     *
8099     * @param dbc the current database context
8100     * @param publishHistoryId the history id identifying the publish job
8101     * @return the assigned publish list
8102     * @throws CmsException if something goes wrong
8103     */
8104    public CmsPublishList readPublishList(CmsDbContext dbc, CmsUUID publishHistoryId) throws CmsException {
8105
8106        return getProjectDriver(dbc).readPublishList(dbc, publishHistoryId);
8107    }
8108
8109    /**
8110     * Reads the publish report assigned to a publish job.<p>
8111     *
8112     * @param dbc the current database context
8113     * @param publishHistoryId the history id identifying the publish job
8114     * @return the content of the assigned publish report
8115     * @throws CmsException if something goes wrong
8116     */
8117    public byte[] readPublishReportContents(CmsDbContext dbc, CmsUUID publishHistoryId) throws CmsException {
8118
8119        return getProjectDriver(dbc).readPublishReportContents(dbc, publishHistoryId);
8120    }
8121
8122    /**
8123     * Reads an historical resource entry for the given resource and with the given version number.<p>
8124     *
8125     * @param dbc the current db context
8126     * @param resource the resource to be read
8127     * @param version the version number to retrieve
8128     *
8129     * @return the resource that was read
8130     *
8131     * @throws CmsException if the resource could not be read for any reason
8132     *
8133     * @see CmsObject#restoreResourceVersion(CmsUUID, int)
8134     * @see CmsObject#readResource(CmsUUID, int)
8135     */
8136    public I_CmsHistoryResource readResource(CmsDbContext dbc, CmsResource resource, int version) throws CmsException {
8137
8138        Iterator<I_CmsHistoryResource> itVersions = getHistoryDriver(dbc).readAllAvailableVersions(
8139            dbc,
8140            resource.getStructureId()).iterator();
8141        while (itVersions.hasNext()) {
8142            I_CmsHistoryResource histRes = itVersions.next();
8143            if (histRes.getVersion() == version) {
8144                return histRes;
8145            }
8146        }
8147        throw new CmsVfsResourceNotFoundException(
8148            org.opencms.db.generic.Messages.get().container(
8149                org.opencms.db.generic.Messages.ERR_HISTORY_FILE_NOT_FOUND_1,
8150                resource.getStructureId()));
8151    }
8152
8153    /**
8154     * Reads a resource from the VFS, using the specified resource filter.<p>
8155     *
8156     * @param dbc the current database context
8157     * @param structureID the structure id of the resource to read
8158     * @param filter the resource filter to use while reading
8159     *
8160     * @return the resource that was read
8161     *
8162     * @throws CmsDataAccessException if something goes wrong
8163     *
8164     * @see CmsObject#readResource(CmsUUID, CmsResourceFilter)
8165     * @see CmsObject#readResource(CmsUUID)
8166     */
8167    public CmsResource readResource(CmsDbContext dbc, CmsUUID structureID, CmsResourceFilter filter)
8168    throws CmsDataAccessException {
8169
8170        CmsUUID projectId = getProjectIdForContext(dbc);
8171        // please note: the filter will be applied in the security manager later
8172        CmsResource resource = getVfsDriver(dbc).readResource(dbc, projectId, structureID, filter.includeDeleted());
8173
8174        // context dates need to be updated
8175        updateContextDates(dbc, resource);
8176
8177        // return the resource
8178        return resource;
8179    }
8180
8181    /**
8182     * Reads a resource from the VFS, using the specified resource filter.<p>
8183     *
8184     * @param dbc the current database context
8185     * @param resourcePath the name of the resource to read (full path)
8186     * @param filter the resource filter to use while reading
8187     *
8188     * @return the resource that was read
8189     *
8190     * @throws CmsDataAccessException if something goes wrong
8191     *
8192     * @see CmsObject#readResource(String, CmsResourceFilter)
8193     * @see CmsObject#readResource(String)
8194     * @see CmsObject#readFile(CmsResource)
8195     */
8196    public CmsResource readResource(CmsDbContext dbc, String resourcePath, CmsResourceFilter filter)
8197    throws CmsDataAccessException {
8198
8199        CmsUUID projectId = getProjectIdForContext(dbc);
8200        // please note: the filter will be applied in the security manager later
8201        CmsResource resource = getVfsDriver(dbc).readResource(dbc, projectId, resourcePath, filter.includeDeleted());
8202
8203        // context dates need to be updated
8204        updateContextDates(dbc, resource);
8205
8206        // return the resource
8207        return resource;
8208    }
8209
8210    /**
8211     * Reads all resources below the given path matching the filter criteria,
8212     * including the full tree below the path only in case the <code>readTree</code>
8213     * parameter is <code>true</code>.<p>
8214     *
8215     * @param dbc the current database context
8216     * @param parent the parent path to read the resources from
8217     * @param filter the filter
8218     * @param readTree <code>true</code> to read all subresources
8219     *
8220     * @return a list of <code>{@link CmsResource}</code> objects matching the filter criteria
8221     *
8222     * @throws CmsDataAccessException if the bare reading of the resources fails
8223     * @throws CmsException if security and permission checks for the resources read fail
8224     */
8225    public List<CmsResource> readResources(
8226        CmsDbContext dbc,
8227        CmsResource parent,
8228        CmsResourceFilter filter,
8229        boolean readTree)
8230    throws CmsException, CmsDataAccessException {
8231
8232        // try to get the sub resources from the cache
8233        String cacheKey = getCacheKey(
8234            new String[] {dbc.currentUser().getName(), filter.getCacheId(), readTree ? "+" : "-", parent.getRootPath()},
8235            dbc);
8236
8237        List<CmsResource> resourceList = m_monitor.getCachedResourceList(cacheKey);
8238        if ((resourceList == null) || !dbc.getProjectId().isNullUUID()) {
8239            // read the result from the database
8240            resourceList = getVfsDriver(dbc).readResourceTree(
8241                dbc,
8242                dbc.currentProject().getUuid(),
8243                (readTree ? parent.getRootPath() : parent.getStructureId().toString()),
8244                filter.getType(),
8245                filter.getState(),
8246                filter.getModifiedAfter(),
8247                filter.getModifiedBefore(),
8248                filter.getReleaseAfter(),
8249                filter.getReleaseBefore(),
8250                filter.getExpireAfter(),
8251                filter.getExpireBefore(),
8252                (readTree ? CmsDriverManager.READMODE_INCLUDE_TREE : CmsDriverManager.READMODE_EXCLUDE_TREE)
8253                    | (filter.excludeType() ? CmsDriverManager.READMODE_EXCLUDE_TYPE : 0)
8254                    | (filter.excludeState() ? CmsDriverManager.READMODE_EXCLUDE_STATE : 0)
8255                    | ((filter.getOnlyFolders() != null)
8256                    ? (filter.getOnlyFolders().booleanValue()
8257                    ? CmsDriverManager.READMODE_ONLY_FOLDERS
8258                    : CmsDriverManager.READMODE_ONLY_FILES)
8259                    : 0));
8260
8261            // HACK: do not take care of permissions if reading organizational units
8262            if (!parent.getRootPath().startsWith("/system/orgunits/")) {
8263                // apply permission filter
8264                resourceList = filterPermissions(dbc, resourceList, filter);
8265            }
8266            // store the result in the resourceList cache
8267            if (dbc.getProjectId().isNullUUID()) {
8268                m_monitor.cacheResourceList(cacheKey, resourceList);
8269            }
8270        }
8271        // we must always apply the result filter and update the context dates
8272        return updateContextDates(dbc, resourceList, filter);
8273    }
8274
8275    /**
8276     * Returns the resources that were visited by a user set in the filter.<p>
8277     *
8278     * @param dbc the database context
8279     * @param poolName the name of the database pool to use
8280     * @param filter the filter that is used to get the visited resources
8281     *
8282     * @return the resources that were visited by a user set in the filter
8283     *
8284     * @throws CmsException if something goes wrong
8285     */
8286    public List<CmsResource> readResourcesVisitedBy(CmsDbContext dbc, String poolName, CmsVisitedByFilter filter)
8287    throws CmsException {
8288
8289        List<CmsResource> result = getSubscriptionDriver().readResourcesVisitedBy(dbc, poolName, filter);
8290        result = filterPermissions(dbc, result, CmsResourceFilter.DEFAULT);
8291        return result;
8292    }
8293
8294    /**
8295     * Reads all resources that have a value (containing the given value string) set
8296     * for the specified property (definition) in the given path.<p>
8297     *
8298     * Both individual and shared properties of a resource are checked.<p>
8299     *
8300     * If the <code>value</code> parameter is <code>null</code>, all resources having the
8301     * given property set are returned.<p>
8302     *
8303     * @param dbc the current database context
8304     * @param folder the folder to get the resources with the property from
8305     * @param propertyDefinition the name of the property (definition) to check for
8306     * @param value the string to search in the value of the property
8307     * @param filter the resource filter to apply to the result set
8308     *
8309     * @return a list of all <code>{@link CmsResource}</code> objects
8310     *          that have a value set for the specified property.
8311     *
8312     * @throws CmsException if something goes wrong
8313     */
8314    public List<CmsResource> readResourcesWithProperty(
8315        CmsDbContext dbc,
8316        CmsResource folder,
8317        String propertyDefinition,
8318        String value,
8319        CmsResourceFilter filter)
8320    throws CmsException {
8321
8322        String cacheKey;
8323        if (value == null) {
8324            cacheKey = getCacheKey(
8325                new String[] {
8326                    dbc.currentUser().getName(),
8327                    folder.getRootPath(),
8328                    propertyDefinition,
8329                    filter.getCacheId()},
8330                dbc);
8331        } else {
8332            cacheKey = getCacheKey(
8333                new String[] {
8334                    dbc.currentUser().getName(),
8335                    folder.getRootPath(),
8336                    propertyDefinition,
8337                    value,
8338                    filter.getCacheId()},
8339                dbc);
8340        }
8341        List<CmsResource> resourceList = m_monitor.getCachedResourceList(cacheKey);
8342        if ((resourceList == null) || !dbc.getProjectId().isNullUUID()) {
8343
8344            CmsPropertyDefinition propDef = null;
8345            try {
8346                // first read the property definition
8347                propDef = readPropertyDefinition(dbc, propertyDefinition);
8348            } catch (CmsDbEntryNotFoundException e) {
8349                LOG.debug(e.getLocalizedMessage(), e);
8350            }
8351            if (propDef != null) {
8352                // now read the list of resources that have a value set for the property definition
8353                resourceList = getVfsDriver(dbc).readResourcesWithProperty(
8354                    dbc,
8355                    dbc.currentProject().getUuid(),
8356                    propDef.getId(),
8357                    folder.getRootPath(),
8358                    value);
8359                // apply permission filter
8360                resourceList = filterPermissions(dbc, resourceList, filter);
8361            } else {
8362                resourceList = new ArrayList<>();
8363            }
8364            // store the result in the resourceList cache
8365            if (dbc.getProjectId().isNullUUID()) {
8366                m_monitor.cacheResourceList(cacheKey, resourceList);
8367            }
8368        }
8369        // we must always apply the result filter and update the context dates
8370        return updateContextDates(dbc, resourceList, filter);
8371    }
8372
8373    /**
8374     * Returns the set of users that are responsible for a specific resource.<p>
8375     *
8376     * @param dbc the current database context
8377     * @param resource the resource to get the responsible users from
8378     *
8379     * @return the set of users that are responsible for a specific resource
8380     *
8381     * @throws CmsException if something goes wrong
8382     */
8383    public Set<I_CmsPrincipal> readResponsiblePrincipals(CmsDbContext dbc, CmsResource resource) throws CmsException {
8384
8385        Set<I_CmsPrincipal> result = new HashSet<I_CmsPrincipal>();
8386        Iterator<CmsAccessControlEntry> aces = getAccessControlEntries(dbc, resource, true).iterator();
8387        while (aces.hasNext()) {
8388            CmsAccessControlEntry ace = aces.next();
8389            if (ace.isResponsible()) {
8390                I_CmsPrincipal p = lookupPrincipal(dbc, ace.getPrincipal());
8391                if (p != null) {
8392                    result.add(p);
8393                }
8394            }
8395        }
8396        return result;
8397    }
8398
8399    /**
8400     * Returns the set of users that are responsible for a specific resource.<p>
8401     *
8402     * @param dbc the current database context
8403     * @param resource the resource to get the responsible users from
8404     *
8405     * @return the set of users that are responsible for a specific resource
8406     *
8407     * @throws CmsException if something goes wrong
8408     */
8409    public Set<CmsUser> readResponsibleUsers(CmsDbContext dbc, CmsResource resource) throws CmsException {
8410
8411        Set<CmsUser> result = new HashSet<CmsUser>();
8412        Iterator<I_CmsPrincipal> principals = readResponsiblePrincipals(dbc, resource).iterator();
8413        while (principals.hasNext()) {
8414            I_CmsPrincipal principal = principals.next();
8415            if (principal.isGroup()) {
8416                try {
8417                    result.addAll(getUsersOfGroup(dbc, principal.getName(), true, false, false));
8418                } catch (CmsException e) {
8419                    if (LOG.isInfoEnabled()) {
8420                        LOG.info(e.getLocalizedMessage(), e);
8421                    }
8422                }
8423            } else {
8424                result.add((CmsUser)principal);
8425            }
8426        }
8427        return result;
8428    }
8429
8430    /**
8431     * Returns a List of all siblings of the specified resource,
8432     * the specified resource being always part of the result set.<p>
8433     *
8434     * The result is a list of <code>{@link CmsResource}</code> objects.<p>
8435     *
8436     * @param dbc the current database context
8437     * @param resource the resource to read the siblings for
8438     * @param filter a filter object
8439     *
8440     * @return a list of <code>{@link CmsResource}</code> Objects that
8441     *          are siblings to the specified resource,
8442     *          including the specified resource itself
8443     *
8444     * @throws CmsException if something goes wrong
8445     */
8446    public List<CmsResource> readSiblings(CmsDbContext dbc, CmsResource resource, CmsResourceFilter filter)
8447    throws CmsException {
8448
8449        List<CmsResource> siblings = getVfsDriver(
8450            dbc).readSiblings(dbc, dbc.currentProject().getUuid(), resource, filter.includeDeleted());
8451
8452        // important: there is no permission check done on the returned list of siblings
8453        // this is because of possible issues with the "publish all siblings" option,
8454        // moreover the user has read permission for the content through
8455        // the selected sibling anyway
8456        return updateContextDates(dbc, siblings, filter);
8457    }
8458
8459    /**
8460     * Returns the parameters of a resource in the table of all published template resources.<p>
8461     *
8462     * @param dbc the current database context
8463     * @param rfsName the rfs name of the resource
8464     *
8465     * @return the parameter string of the requested resource
8466     *
8467     * @throws CmsException if something goes wrong
8468     */
8469    public String readStaticExportPublishedResourceParameters(CmsDbContext dbc, String rfsName) throws CmsException {
8470
8471        return getProjectDriver(dbc).readStaticExportPublishedResourceParameters(dbc, rfsName);
8472    }
8473
8474    /**
8475     * Returns a list of all template resources which must be processed during a static export.<p>
8476     *
8477     * @param dbc the current database context
8478     * @param parameterResources flag for reading resources with parameters (1) or without (0)
8479     * @param timestamp for reading the data from the db
8480     *
8481     * @return a list of template resources as <code>{@link String}</code> objects
8482     *
8483     * @throws CmsException if something goes wrong
8484     */
8485    public List<String> readStaticExportResources(CmsDbContext dbc, int parameterResources, long timestamp)
8486    throws CmsException {
8487
8488        return getProjectDriver(dbc).readStaticExportResources(dbc, parameterResources, timestamp);
8489    }
8490
8491    /**
8492     * Returns the subscribed history resources that were deleted.<p>
8493     *
8494     * @param dbc the database context
8495     * @param poolName the name of the database pool to use
8496     * @param user the user that subscribed to the resource
8497     * @param groups the groups to check subscribed resources for
8498     * @param parent the parent resource (folder) of the deleted resources, if <code>null</code> all deleted resources will be returned
8499     * @param includeSubFolders indicates if the sub folders of the specified folder path should be considered, too
8500     * @param deletedFrom the time stamp from which the resources should have been deleted
8501     *
8502     * @return the subscribed history resources that were deleted
8503     *
8504     * @throws CmsException if something goes wrong
8505     */
8506    public List<I_CmsHistoryResource> readSubscribedDeletedResources(
8507        CmsDbContext dbc,
8508        String poolName,
8509        CmsUser user,
8510        List<CmsGroup> groups,
8511        CmsResource parent,
8512        boolean includeSubFolders,
8513        long deletedFrom)
8514    throws CmsException {
8515
8516        List<I_CmsHistoryResource> result = getSubscriptionDriver().readSubscribedDeletedResources(
8517            dbc,
8518            poolName,
8519            user,
8520            groups,
8521            parent,
8522            includeSubFolders,
8523            deletedFrom);
8524
8525        return result;
8526    }
8527
8528    /**
8529     * Returns the resources that were subscribed by a user or group set in the filter.<p>
8530     *
8531     * @param dbc the database context
8532     * @param poolName the name of the database pool to use
8533     * @param filter the filter that is used to get the subscribed resources
8534     *
8535     * @return the resources that were subscribed by a user or group set in the filter
8536     *
8537     * @throws CmsException if something goes wrong
8538     */
8539    public List<CmsResource> readSubscribedResources(CmsDbContext dbc, String poolName, CmsSubscriptionFilter filter)
8540    throws CmsException {
8541
8542        List<CmsResource> result = getSubscriptionDriver().readSubscribedResources(dbc, poolName, filter);
8543
8544        result = filterPermissions(dbc, result, CmsResourceFilter.DEFAULT);
8545        return result;
8546    }
8547
8548    /**
8549     * Reads URL name mapping entries which match the given filter.<p>
8550     *
8551     * @param dbc the database context
8552     * @param online if true, read online URL name mappings, else offline ones
8553     * @param filter the filter for matching the URL name entries
8554     *
8555     * @return the list of URL name mapping entries which match the given filter
8556     *
8557     * @throws CmsDataAccessException if something goes wrong
8558     */
8559    public List<CmsUrlNameMappingEntry> readUrlNameMappingEntries(
8560        CmsDbContext dbc,
8561        boolean online,
8562        CmsUrlNameMappingFilter filter)
8563    throws CmsDataAccessException {
8564
8565        I_CmsVfsDriver vfsDriver = getVfsDriver(dbc);
8566        return vfsDriver.readUrlNameMappingEntries(dbc, online, filter);
8567    }
8568
8569    /**
8570     * Reads the URL name mappings matching the given filter.<p>
8571     *
8572     * @param dbc the DB context to use
8573     * @param filter the filter used to select the mapping entries
8574     * @return the entries matching the given filter
8575     *
8576     * @throws CmsDataAccessException if something goes wrong
8577     */
8578    public List<CmsUrlNameMappingEntry> readUrlNameMappings(CmsDbContext dbc, CmsUrlNameMappingFilter filter)
8579    throws CmsDataAccessException {
8580
8581        List<CmsUrlNameMappingEntry> entries = getVfsDriver(dbc).readUrlNameMappingEntries(
8582            dbc,
8583            dbc.currentProject().isOnlineProject(),
8584            filter);
8585        return entries;
8586    }
8587
8588    /**
8589     * Reads the newest URL names of a resource for all locales.<p>
8590     *
8591     * @param dbc the database context
8592     * @param id the resource's structure id
8593     *
8594     * @return the url names for the locales
8595     *
8596     * @throws CmsDataAccessException if the database operation failed
8597     */
8598    public List<String> readUrlNamesForAllLocales(CmsDbContext dbc, CmsUUID id) throws CmsDataAccessException {
8599
8600        List<String> result = new ArrayList<String>();
8601        List<CmsUrlNameMappingEntry> entries = getVfsDriver(dbc).readUrlNameMappingEntries(
8602            dbc,
8603            dbc.currentProject().isOnlineProject(),
8604            CmsUrlNameMappingFilter.ALL.filterStructureId(id));
8605        ArrayListMultimap<String, CmsUrlNameMappingEntry> entriesByLocale = ArrayListMultimap.create();
8606        for (CmsUrlNameMappingEntry entry : entries) {
8607            String localeKey = entry.getLocale();
8608            entriesByLocale.put(localeKey, entry);
8609        }
8610
8611        for (String localeKey : entriesByLocale.keySet()) {
8612            List<CmsUrlNameMappingEntry> entrs = entriesByLocale.get(localeKey);
8613            CmsUrlNameMappingEntry maxEntryForLocale = Collections.max(entrs, new UrlNameMappingComparator());
8614            result.add(maxEntryForLocale.getName());
8615        }
8616        return result;
8617    }
8618
8619    /**
8620     * Returns a user object based on the id of a user.<p>
8621     *
8622     * @param dbc the current database context
8623     * @param id the id of the user to read
8624     *
8625     * @return the user read
8626     *
8627     * @throws CmsException if something goes wrong
8628     */
8629    public CmsUser readUser(CmsDbContext dbc, CmsUUID id) throws CmsException {
8630
8631        CmsUser user = m_monitor.getCachedUser(id.toString());
8632        if (user == null) {
8633            user = getUserDriver(dbc).readUser(dbc, id);
8634            m_monitor.cacheUser(user);
8635        }
8636        // important: do not return the cached user object, but a clone to avoid unwanted changes on cached objects
8637        return user.clone();
8638    }
8639
8640    /**
8641     * Returns a user object.<p>
8642     *
8643     * @param dbc the current database context
8644     * @param username the name of the user that is to be read
8645     *
8646     * @return user read
8647     *
8648     * @throws CmsDataAccessException if operation was not successful
8649     */
8650    public CmsUser readUser(CmsDbContext dbc, String username) throws CmsDataAccessException {
8651
8652        CmsUser user = m_monitor.getCachedUser(username);
8653        if (user == null) {
8654            user = getUserDriver(dbc).readUser(dbc, username);
8655            m_monitor.cacheUser(user);
8656        }
8657        // important: do not return the cached user object, but a clone to avoid unwanted changes on cached objects
8658        return user.clone();
8659    }
8660
8661    /**
8662     * Returns a user object if the password for the user is correct.<p>
8663     *
8664     * If the user/pwd pair is not valid a <code>{@link CmsException}</code> is thrown.<p>
8665     *
8666     * @param dbc the current database context
8667     * @param username the username of the user that is to be read
8668     * @param password the password of the user that is to be read
8669     *
8670     * @return user read
8671     *
8672     * @throws CmsException if operation was not successful
8673     */
8674    public CmsUser readUser(CmsDbContext dbc, String username, String password) throws CmsException {
8675
8676        // don't read user from cache here because password may have changed
8677        CmsUser user = getUserDriver(dbc).readUser(dbc, username, password, null);
8678        m_monitor.cacheUser(user);
8679        return user;
8680    }
8681
8682    /**
8683     * Removes an access control entry for a given resource and principal.<p>
8684     *
8685     * @param dbc the current database context
8686     * @param resource the resource
8687     * @param principal the id of the principal to remove the the access control entry for
8688     *
8689     * @throws CmsException if something goes wrong
8690     */
8691    public void removeAccessControlEntry(CmsDbContext dbc, CmsResource resource, CmsUUID principal)
8692    throws CmsException {
8693
8694        // remove the ace
8695        getUserDriver(dbc).removeAccessControlEntry(dbc, dbc.currentProject(), resource.getResourceId(), principal);
8696
8697        // log it
8698        log(
8699            dbc,
8700            new CmsLogEntry(
8701                dbc,
8702                resource.getStructureId(),
8703                CmsLogEntryType.RESOURCE_PERMISSIONS,
8704                new String[] {resource.getRootPath()}),
8705            false);
8706
8707        // update the "last modified" information
8708        setDateLastModified(dbc, resource, resource.getDateLastModified());
8709
8710        // clear the cache
8711        m_monitor.clearAccessControlListCache();
8712
8713        // fire a resource modification event
8714        Map<String, Object> data = new HashMap<String, Object>(2);
8715        data.put(I_CmsEventListener.KEY_RESOURCE, resource);
8716        data.put(I_CmsEventListener.KEY_CHANGE, Integer.valueOf(CHANGED_ACCESSCONTROL));
8717        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_MODIFIED, data));
8718    }
8719
8720    /**
8721     * Removes a resource from the given organizational unit.<p>
8722     *
8723     * @param dbc the current db context
8724     * @param orgUnit the organizational unit to remove the resource from
8725     * @param resource the resource that is to be removed from the organizational unit
8726     *
8727     * @throws CmsException if something goes wrong
8728     *
8729     * @see org.opencms.security.CmsOrgUnitManager#addResourceToOrgUnit(CmsObject, String, String)
8730     * @see org.opencms.security.CmsOrgUnitManager#addResourceToOrgUnit(CmsObject, String, String)
8731     */
8732    public void removeResourceFromOrgUnit(CmsDbContext dbc, CmsOrganizationalUnit orgUnit, CmsResource resource)
8733    throws CmsException {
8734
8735        m_monitor.flushCache(CmsMemoryMonitor.CacheType.HAS_ROLE, CmsMemoryMonitor.CacheType.ROLE_LIST);
8736        getUserDriver(dbc).removeResourceFromOrganizationalUnit(dbc, orgUnit, resource);
8737    }
8738
8739    /**
8740     * Removes a resource from the current project of the user.<p>
8741     *
8742     * @param dbc the current database context
8743     * @param resource the resource to apply this operation to
8744     *
8745     * @throws CmsException if something goes wrong
8746     *
8747     * @see CmsObject#copyResourceToProject(String)
8748     * @see I_CmsResourceType#copyResourceToProject(CmsObject, CmsSecurityManager, CmsResource)
8749     */
8750    public void removeResourceFromProject(CmsDbContext dbc, CmsResource resource) throws CmsException {
8751
8752        // remove the resource to the project only if the resource is already in the project
8753        if (isInsideCurrentProject(dbc, resource.getRootPath())) {
8754            // check if there are already any subfolders of this resource
8755            I_CmsProjectDriver projectDriver = getProjectDriver(dbc);
8756            if (resource.isFolder()) {
8757                List<String> projectResources = projectDriver.readProjectResources(dbc, dbc.currentProject());
8758                for (int i = 0; i < projectResources.size(); i++) {
8759                    String resname = projectResources.get(i);
8760                    if (resname.startsWith(resource.getRootPath())) {
8761                        // delete the existing project resource first
8762                        projectDriver.deleteProjectResource(dbc, dbc.currentProject().getUuid(), resname);
8763                    }
8764                }
8765            }
8766            try {
8767                projectDriver.deleteProjectResource(dbc, dbc.currentProject().getUuid(), resource.getRootPath());
8768            } catch (CmsException exc) {
8769                // if the subfolder exists already - all is ok
8770            } finally {
8771                m_monitor.flushCache(CmsMemoryMonitor.CacheType.PROJECT_RESOURCES);
8772
8773                OpenCms.fireCmsEvent(
8774                    new CmsEvent(
8775                        I_CmsEventListener.EVENT_PROJECT_MODIFIED,
8776                        Collections.<String, Object> singletonMap("project", dbc.currentProject())));
8777            }
8778        }
8779    }
8780
8781    /**
8782     * Removes the given resource to the given user's publish list.<p>
8783     *
8784     * @param dbc the database context
8785     * @param userId the user's id
8786     * @param structureIds the collection of structure IDs to remove
8787     *
8788     * @throws CmsDataAccessException if something goes wrong
8789     */
8790    public void removeResourceFromUsersPubList(CmsDbContext dbc, CmsUUID userId, Collection<CmsUUID> structureIds)
8791    throws CmsDataAccessException {
8792
8793        for (CmsUUID structureId : structureIds) {
8794            CmsLogEntry entry = new CmsLogEntry(
8795                userId,
8796                System.currentTimeMillis(),
8797                structureId,
8798                CmsLogEntryType.RESOURCE_HIDDEN,
8799                new String[] {readResource(dbc, structureId, CmsResourceFilter.ALL).getRootPath()});
8800            log(dbc, entry, true);
8801        }
8802    }
8803
8804    /**
8805     * Removes a user from a group.<p>
8806     *
8807     * @param dbc the current database context
8808     * @param username the name of the user that is to be removed from the group
8809     * @param groupname the name of the group
8810     * @param readRoles if to read roles or groups
8811     *
8812     * @throws CmsException if operation was not successful
8813     * @throws CmsIllegalArgumentException if the given user was not member in the given group
8814     * @throws CmsDbEntryNotFoundException if the given group was not found
8815     * @throws CmsSecurityException if the given user was <b>read as 'null' from the database</b>
8816     *
8817     * @see #addUserToGroup(CmsDbContext, String, String, boolean)
8818     */
8819    public void removeUserFromGroup(CmsDbContext dbc, String username, String groupname, boolean readRoles)
8820    throws CmsException, CmsIllegalArgumentException, CmsDbEntryNotFoundException, CmsSecurityException {
8821
8822        CmsGroup group = readGroup(dbc, groupname);
8823        //check if group exists
8824        if (group == null) {
8825            // the group does not exists
8826            throw new CmsDbEntryNotFoundException(Messages.get().container(Messages.ERR_UNKNOWN_GROUP_1, groupname));
8827        }
8828        if (group.isVirtual() && !readRoles) {
8829            // if removing a user from a virtual role treat it as removing the user from the role
8830            removeUserFromGroup(dbc, username, CmsRole.valueOf(group).getGroupName(), true);
8831            return;
8832        }
8833        if (group.isVirtual()) {
8834            // this is an hack so to prevent a unlimited recursive calls
8835            readRoles = false;
8836        }
8837        if ((readRoles && !group.isRole()) || (!readRoles && group.isRole())) {
8838            // we want a role but we got a group, or the other way
8839            throw new CmsDbEntryNotFoundException(Messages.get().container(Messages.ERR_UNKNOWN_GROUP_1, groupname));
8840        }
8841
8842        boolean skipRemove = false;
8843        // test if this user is existing in the group
8844        if (!userInGroup(dbc, username, groupname, readRoles)) {
8845            if (readRoles) {
8846                // Sometimes users can end up with the default groups corresponding to roles (Administrators, Users) without the actual roles.
8847                // 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
8848                // want to throw an exception then, because it would prevent the code that actually removes the user from the group from running.
8849                LOG.warn(
8850                    "Trying to remove user from role that they are not a member of (user: "
8851                        + username
8852                        + ", group: "
8853                        + groupname
8854                        + ")");
8855                skipRemove = true;
8856            } else {
8857                // user is not in the group, throw exception
8858                throw new CmsIllegalArgumentException(
8859                    Messages.get().container(Messages.ERR_USER_NOT_IN_GROUP_2, username, groupname));
8860            }
8861        }
8862
8863        CmsUser user = readUser(dbc, username);
8864        //check if the user exists
8865        if (user == null) {
8866            // the user does not exists
8867            throw new CmsIllegalArgumentException(
8868                Messages.get().container(Messages.ERR_USER_NOT_IN_GROUP_2, username, groupname));
8869        }
8870
8871        if (readRoles) {
8872            CmsRole role = CmsRole.valueOf(group);
8873            // update virtual groups
8874            Iterator<CmsGroup> it = getVirtualGroupsForRole(dbc, role).iterator();
8875            while (it.hasNext()) {
8876                CmsGroup virtualGroup = it.next();
8877                if (userInGroup(dbc, username, virtualGroup.getName(), false)) {
8878                    // here we say readroles = true, to prevent an unlimited recursive calls
8879                    removeUserFromGroup(dbc, username, virtualGroup.getName(), true);
8880                }
8881            }
8882        }
8883        if (!skipRemove) {
8884            getUserDriver(dbc).deleteUserInGroup(dbc, user.getId(), group.getId());
8885        }
8886
8887        // flush relevant caches
8888        if (readRoles) {
8889            m_monitor.flushCache(CmsMemoryMonitor.CacheType.HAS_ROLE, CmsMemoryMonitor.CacheType.ROLE_LIST);
8890        }
8891        m_monitor.flushUserGroups(user.getId());
8892        m_monitor.flushCache(CmsMemoryMonitor.CacheType.USER_LIST);
8893
8894        if (!dbc.getProjectId().isNullUUID()) {
8895            // user modified event is not needed
8896            return;
8897        }
8898        // fire user modified event
8899        Map<String, Object> eventData = new HashMap<String, Object>();
8900        eventData.put(I_CmsEventListener.KEY_USER_ID, user.getId().toString());
8901        eventData.put(I_CmsEventListener.KEY_USER_NAME, user.getName());
8902        eventData.put(I_CmsEventListener.KEY_GROUP_ID, group.getId().toString());
8903        eventData.put(I_CmsEventListener.KEY_GROUP_NAME, group.getName());
8904        eventData.put(
8905            I_CmsEventListener.KEY_USER_ACTION,
8906            I_CmsEventListener.VALUE_USER_MODIFIED_ACTION_REMOVE_USER_FROM_GROUP);
8907        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_USER_MODIFIED, eventData));
8908
8909    }
8910
8911    /**
8912     * Repairs broken categories.<p>
8913     *
8914     * @param dbc the database context
8915     * @param projectId the project id
8916     * @param resource the resource to repair the categories for
8917     *
8918     * @throws CmsException if something goes wrong
8919     */
8920    public void repairCategories(CmsDbContext dbc, CmsUUID projectId, CmsResource resource) throws CmsException {
8921
8922        CmsObject cms = OpenCms.initCmsObject(new CmsObject(getSecurityManager(), dbc.getRequestContext()));
8923        cms.getRequestContext().setSiteRoot("");
8924        cms.getRequestContext().setCurrentProject(readProject(dbc, projectId));
8925        CmsCategoryService.getInstance().repairRelations(cms, resource);
8926    }
8927
8928    /**
8929     * Replaces the content, type and properties of a resource.<p>
8930     *
8931     * @param dbc the current database context
8932     * @param resource the name of the resource to apply this operation to
8933     * @param type the new type of the resource
8934     * @param content the new content of the resource
8935     * @param properties the new properties of the resource
8936     *
8937     * @throws CmsException if something goes wrong
8938     *
8939     * @see CmsObject#replaceResource(String, int, byte[], List)
8940     * @see I_CmsResourceType#replaceResource(CmsObject, CmsSecurityManager, CmsResource, int, byte[], List)
8941     */
8942    @SuppressWarnings("javadoc")
8943    public void replaceResource(
8944        CmsDbContext dbc,
8945        CmsResource resource,
8946        int type,
8947        byte[] content,
8948        List<CmsProperty> properties)
8949    throws CmsException {
8950
8951        // replace the existing with the new file content
8952        getVfsDriver(dbc).replaceResource(dbc, resource, content, type);
8953
8954        if ((properties != null) && !properties.isEmpty()) {
8955            // write the properties
8956            getVfsDriver(dbc).writePropertyObjects(dbc, dbc.currentProject(), resource, properties);
8957            m_monitor.flushCache(CmsMemoryMonitor.CacheType.PROPERTY, CmsMemoryMonitor.CacheType.PROPERTY_LIST);
8958        }
8959
8960        // update the resource state
8961        if (resource.getState().isUnchanged()) {
8962            resource.setState(CmsResource.STATE_CHANGED);
8963        }
8964        resource.setUserLastModified(dbc.currentUser().getId());
8965
8966        // log it
8967        log(
8968            dbc,
8969            new CmsLogEntry(
8970                dbc,
8971                resource.getStructureId(),
8972                CmsLogEntryType.RESOURCE_CONTENT_MODIFIED,
8973                new String[] {resource.getRootPath()}),
8974            false);
8975
8976        setDateLastModified(dbc, resource, System.currentTimeMillis());
8977
8978        getVfsDriver(dbc).writeResourceState(dbc, dbc.currentProject(), resource, UPDATE_RESOURCE, false);
8979
8980        deleteRelationsWithSiblings(dbc, resource);
8981
8982        // clear the cache
8983        m_monitor.clearResourceCache();
8984
8985        if ((properties != null) && !properties.isEmpty()) {
8986            // resource and properties were modified
8987            OpenCms.fireCmsEvent(
8988                new CmsEvent(
8989                    I_CmsEventListener.EVENT_RESOURCE_AND_PROPERTIES_MODIFIED,
8990                    Collections.<String, Object> singletonMap(I_CmsEventListener.KEY_RESOURCE, resource)));
8991        } else {
8992            // only the resource was modified
8993            Map<String, Object> data = new HashMap<String, Object>(2);
8994            data.put(I_CmsEventListener.KEY_RESOURCE, resource);
8995            data.put(I_CmsEventListener.KEY_CHANGE, Integer.valueOf(CHANGED_RESOURCE | CHANGED_CONTENT));
8996            OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_MODIFIED, data));
8997        }
8998    }
8999
9000    /**
9001     * Resets the password for a specified user.<p>
9002     *
9003     * @param dbc the current database context
9004     * @param username the name of the user
9005     * @param oldPassword the old password
9006     * @param secondFactor the second factor data used for 2FA
9007     * @param newPassword the new password
9008     *
9009     * @throws CmsException if the user data could not be read from the database
9010     * @throws CmsSecurityException if the specified username and old password could not be verified
9011     */
9012    public void resetPassword(
9013        CmsDbContext dbc,
9014        String username,
9015        String oldPassword,
9016        CmsSecondFactorInfo secondFactor,
9017        String newPassword)
9018    throws CmsException, CmsSecurityException {
9019
9020        if ((oldPassword != null) && (newPassword != null)) {
9021
9022            CmsUser user = null;
9023
9024            if (dbc.getRequestContext().getAttribute(CmsUserDriver.REQ_ATTR_DONT_DIGEST_PASSWORD) == null) {
9025                validatePassword(newPassword);
9026            }
9027
9028            // read the user as a system user to verify that the specified old password is correct
9029            try {
9030                user = getUserDriver(dbc).readUser(dbc, username, oldPassword, null);
9031            } catch (CmsDbEntryNotFoundException e) {
9032                throw new CmsDataAccessException(Messages.get().container(Messages.ERR_RESET_PASSWORD_1, username), e);
9033            }
9034
9035            if ((user == null) || user.isManaged()) {
9036                throw new CmsDataAccessException(Messages.get().container(Messages.ERR_RESET_PASSWORD_1, username));
9037            }
9038
9039            CmsTwoFactorAuthenticationHandler twoFactorHandler = OpenCms.getTwoFactorAuthenticationHandler();
9040            if (twoFactorHandler.needsTwoFactorAuthentication(user) && twoFactorHandler.hasSecondFactor(user)) {
9041                if (!twoFactorHandler.verifySecondFactor(user, secondFactor)) {
9042                    throw new CmsDataAccessException(
9043                        Messages.get().container(Messages.ERR_RESET_PASSWORD_1, username),
9044                        new RuntimeException("Verification code mismatch"));
9045                }
9046            }
9047
9048            getUserDriver(dbc).writePassword(dbc, username, oldPassword, newPassword);
9049            user.getAdditionalInfo().put(
9050                CmsUserSettings.ADDITIONAL_INFO_LAST_PASSWORD_CHANGE,
9051                "" + System.currentTimeMillis());
9052            user.deleteAdditionalInfo(CmsUserSettings.ADDITIONAL_INFO_PASSWORD_RESET);
9053            getUserDriver(dbc).writeUser(dbc, user);
9054
9055            if (!dbc.getProjectId().isNullUUID()) {
9056                // user modified event is not needed
9057                return;
9058            }
9059            // fire user modified event
9060            Map<String, Object> eventData = new HashMap<String, Object>();
9061            eventData.put(I_CmsEventListener.KEY_USER_ID, user.getId().toString());
9062            eventData.put(
9063                I_CmsEventListener.KEY_USER_ACTION,
9064                I_CmsEventListener.VALUE_USER_MODIFIED_ACTION_RESET_PASSWORD);
9065            eventData.put(
9066                I_CmsEventListener.KEY_USER_CHANGES,
9067                Integer.valueOf(CmsUser.FLAG_CORE_DATA | CmsUser.FLAG_CORE_DATA));
9068            OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_USER_MODIFIED, eventData));
9069
9070        } else if (CmsStringUtil.isEmpty(oldPassword)) {
9071            throw new CmsDataAccessException(Messages.get().container(Messages.ERR_PWD_OLD_MISSING_0));
9072        } else if (CmsStringUtil.isEmpty(newPassword)) {
9073            throw new CmsDataAccessException(Messages.get().container(Messages.ERR_PWD_NEW_MISSING_0));
9074        }
9075    }
9076
9077    /**
9078     * Restores a deleted resource identified by its structure id from the historical archive.<p>
9079     *
9080     * @param dbc the current database context
9081     * @param structureId the structure id of the resource to restore
9082     *
9083     * @throws CmsException if something goes wrong
9084     *
9085     * @see CmsObject#restoreDeletedResource(CmsUUID)
9086     */
9087    public CmsResource restoreDeletedResource(CmsDbContext dbc, CmsUUID structureId) throws CmsException {
9088
9089        // get the last version, which should be the deleted one
9090        int version = getHistoryDriver(dbc).readLastVersion(dbc, structureId);
9091        // get that version
9092        I_CmsHistoryResource histRes = getHistoryDriver(dbc).readResource(dbc, structureId, version);
9093
9094        // check the parent path
9095        CmsResource parent;
9096        try {
9097            // try to read the parent resource by id
9098            parent = getVfsDriver(dbc).readResource(dbc, dbc.currentProject().getUuid(), histRes.getParentId(), true);
9099        } catch (CmsVfsResourceNotFoundException e) {
9100            // if not found try to read the parent resource by name
9101            try {
9102                // try to read the parent resource by id
9103                parent = getVfsDriver(dbc).readResource(
9104                    dbc,
9105                    dbc.currentProject().getUuid(),
9106                    CmsResource.getParentFolder(histRes.getRootPath()),
9107                    true);
9108            } catch (CmsVfsResourceNotFoundException e1) {
9109                // if not found try to restore the parent resource
9110                restoreDeletedResource(dbc, histRes.getParentId());
9111                parent = readResource(dbc, histRes.getParentId(), CmsResourceFilter.IGNORE_EXPIRATION);
9112            }
9113        }
9114        // check write permissions
9115        m_securityManager.checkPermissions(
9116            dbc,
9117            parent,
9118            CmsPermissionSet.ACCESS_WRITE,
9119            false,
9120            CmsResourceFilter.IGNORE_EXPIRATION);
9121
9122        // check the name
9123        String path = parent.getRootPath();
9124        String resName = CmsResource.getName(histRes.getRootPath()); // name
9125        String ext = "";
9126        if (resName.charAt(resName.length() - 1) == '/') {
9127            resName = resName.substring(0, resName.length() - 1);
9128        } else {
9129            ext = CmsFileUtil.getExtension(resName); // extension
9130        }
9131        String nameWOExt = resName.substring(0, resName.length() - ext.length()); // name without extension
9132        for (int i = 1; true; i++) {
9133            try {
9134                readResource(dbc, path + resName, CmsResourceFilter.ALL);
9135                resName = nameWOExt + "_" + i + ext;
9136                // try the next resource name with following schema: path/name_{i}.ext
9137            } catch (CmsVfsResourceNotFoundException e) {
9138                // ok, we found a not used resource name
9139                break;
9140            }
9141        }
9142
9143        // check structure id
9144        CmsUUID id = structureId;
9145        if (getVfsDriver(dbc).validateStructureIdExists(dbc, dbc.currentProject().getUuid(), structureId)) {
9146            // should never happen, but if already exists create a new one
9147            id = new CmsUUID();
9148        }
9149
9150        byte[] contents = null;
9151        boolean isFolder = true;
9152
9153        // do we need the contents?
9154        if (histRes instanceof CmsFile) {
9155            contents = ((CmsFile)histRes).getContents();
9156            if ((contents == null) || (contents.length == 0)) {
9157                contents = getHistoryDriver(dbc).readContent(dbc, histRes.getResourceId(), histRes.getPublishTag());
9158            }
9159            isFolder = false;
9160        }
9161
9162        // now read the historical properties
9163        List<CmsProperty> properties = getHistoryDriver(dbc).readProperties(dbc, histRes);
9164
9165        // create the object to create
9166        CmsResource newResource = new CmsResource(
9167            id,
9168            histRes.getResourceId(),
9169            path + resName,
9170            histRes.getTypeId(),
9171            isFolder,
9172            histRes.getFlags(),
9173            dbc.currentProject().getUuid(),
9174            CmsResource.STATE_NEW,
9175            histRes.getDateCreated(),
9176            histRes.getUserCreated(),
9177            histRes.getDateLastModified(),
9178            dbc.currentUser().getId(),
9179            histRes.getDateReleased(),
9180            histRes.getDateExpired(),
9181            histRes.getSiblingCount(),
9182            histRes.getLength(),
9183            histRes.getDateContent(),
9184            histRes.getVersion());
9185
9186        // log it
9187        log(
9188            dbc,
9189            new CmsLogEntry(
9190                dbc,
9191                newResource.getStructureId(),
9192                CmsLogEntryType.RESOURCE_RESTORE_DELETED,
9193                new String[] {newResource.getRootPath()}),
9194            false);
9195
9196        // prevent the date last modified is set to the current time
9197        newResource.setDateLastModified(newResource.getDateLastModified());
9198        // restore the resource!
9199        CmsResource resource = createResource(dbc, path + resName, newResource, contents, properties, true);
9200        // set resource state to changed
9201        newResource.setState(CmsResource.STATE_CHANGED);
9202        getVfsDriver(dbc).writeResourceState(dbc, dbc.currentProject(), newResource, UPDATE_RESOURCE_STATE, false);
9203        newResource.setState(CmsResource.STATE_NEW);
9204        // fire the event
9205        Map<String, Object> data = new HashMap<String, Object>(2);
9206        data.put(I_CmsEventListener.KEY_RESOURCE, resource);
9207        data.put(I_CmsEventListener.KEY_CHANGE, Integer.valueOf(CHANGED_RESOURCE | CHANGED_CONTENT));
9208        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_MODIFIED, data));
9209        return newResource;
9210    }
9211
9212    /**
9213     * Restores a resource in the current project with a version from the historical archive.<p>
9214     *
9215     * @param dbc the current database context
9216     * @param resource the resource to restore from the archive
9217     * @param version the version number to restore from the archive
9218     *
9219     * @throws CmsException if something goes wrong
9220     *
9221     * @see CmsObject#restoreResourceVersion(CmsUUID, int)
9222     * @see I_CmsResourceType#restoreResource(CmsObject, CmsSecurityManager, CmsResource, int)
9223     */
9224    public CmsResource restoreResource(CmsDbContext dbc, CmsResource resource, int version) throws CmsException {
9225
9226        I_CmsHistoryResource historyResource = readResource(dbc, resource, version);
9227        CmsResourceState state = CmsResource.STATE_CHANGED;
9228        if (resource.getState().isNew()) {
9229            state = CmsResource.STATE_NEW;
9230        }
9231        int newVersion = resource.getVersion();
9232        if (resource.getState().isUnchanged()) {
9233            newVersion++;
9234        }
9235        CmsResource newResource = null;
9236        // is the resource a file?
9237        if (historyResource instanceof CmsFile) {
9238            // get the historical up flags
9239            int flags = historyResource.getFlags();
9240            if (resource.isLabeled()) {
9241                // set the flag for labeled links on the restored file
9242                flags |= CmsResource.FLAG_LABELED;
9243            }
9244            CmsFile newFile = new CmsFile(
9245                resource.getStructureId(),
9246                resource.getResourceId(),
9247                resource.getRootPath(),
9248                historyResource.getTypeId(),
9249                flags,
9250                dbc.currentProject().getUuid(),
9251                state,
9252                resource.getDateCreated(),
9253                historyResource.getUserCreated(),
9254                resource.getDateLastModified(),
9255                dbc.currentUser().getId(),
9256                historyResource.getDateReleased(),
9257                historyResource.getDateExpired(),
9258                resource.getSiblingCount(),
9259                historyResource.getLength(),
9260                historyResource.getDateContent(),
9261                newVersion,
9262                readFile(dbc, (CmsHistoryFile)historyResource).getContents());
9263
9264            // log it
9265            log(
9266                dbc,
9267                new CmsLogEntry(
9268                    dbc,
9269                    newFile.getStructureId(),
9270                    CmsLogEntryType.RESOURCE_HISTORY,
9271                    new String[] {newFile.getRootPath()}),
9272                false);
9273
9274            newResource = writeFile(dbc, newFile);
9275        } else {
9276            // it is a folder!
9277            newResource = new CmsFolder(
9278                resource.getStructureId(),
9279                resource.getResourceId(),
9280                resource.getRootPath(),
9281                historyResource.getTypeId(),
9282                historyResource.getFlags(),
9283                dbc.currentProject().getUuid(),
9284                state,
9285                resource.getDateCreated(),
9286                historyResource.getUserCreated(),
9287                resource.getDateLastModified(),
9288                dbc.currentUser().getId(),
9289                historyResource.getDateReleased(),
9290                historyResource.getDateExpired(),
9291                newVersion);
9292
9293            // log it
9294            log(
9295                dbc,
9296                new CmsLogEntry(
9297                    dbc,
9298                    newResource.getStructureId(),
9299                    CmsLogEntryType.RESOURCE_HISTORY,
9300                    new String[] {newResource.getRootPath()}),
9301                false);
9302
9303            writeResource(dbc, newResource);
9304        }
9305        if (newResource != null) {
9306            // now read the historical properties
9307            List<CmsProperty> historyProperties = getHistoryDriver(dbc).readProperties(dbc, historyResource);
9308            // remove all properties
9309            deleteAllProperties(dbc, newResource.getRootPath());
9310            // write them to the restored resource
9311            writePropertyObjects(dbc, newResource, historyProperties, false);
9312
9313            m_monitor.clearResourceCache();
9314        }
9315
9316        Map<String, Object> data = new HashMap<String, Object>(2);
9317        data.put(I_CmsEventListener.KEY_RESOURCE, resource);
9318        data.put(I_CmsEventListener.KEY_CHANGE, Integer.valueOf(CHANGED_RESOURCE | CHANGED_CONTENT));
9319        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_MODIFIED, data));
9320        return newResource;
9321    }
9322
9323    /**
9324     * Saves a list of aliases for the same structure id, replacing any aliases for the same structure id.<p>
9325     *
9326     * @param dbc the current database context
9327     * @param project the current project
9328     * @param structureId the structure id for which the aliases should be saved
9329     * @param aliases the list of aliases to save
9330     *
9331     * @throws CmsException if something goes wrong
9332     */
9333    public void saveAliases(CmsDbContext dbc, CmsProject project, CmsUUID structureId, List<CmsAlias> aliases)
9334    throws CmsException {
9335
9336        for (CmsAlias alias : aliases) {
9337            if (!structureId.equals(alias.getStructureId())) {
9338                throw new IllegalArgumentException("Aliases to replace must have the same structure id!");
9339            }
9340        }
9341        I_CmsVfsDriver vfsDriver = getVfsDriver(dbc);
9342        vfsDriver.deleteAliases(dbc, project, new CmsAliasFilter(null, null, structureId));
9343        for (CmsAlias alias : aliases) {
9344            String aliasPath = alias.getAliasPath();
9345            if (CmsAlias.ALIAS_PATTERN.matcher(aliasPath).matches()) {
9346                vfsDriver.insertAlias(dbc, project, alias);
9347            } else {
9348                LOG.error("Invalid alias path: " + aliasPath);
9349            }
9350        }
9351    }
9352
9353    /**
9354     * Replaces the complete list of rewrite aliases for a given site root.<p>
9355     *
9356     * @param dbc the current database context
9357     * @param siteRoot the site root for which the rewrite aliases should be replaced
9358     * @param newAliases the new aliases for the given site root
9359     * @throws CmsException if something goes wrong
9360     */
9361    public void saveRewriteAliases(CmsDbContext dbc, String siteRoot, List<CmsRewriteAlias> newAliases)
9362    throws CmsException {
9363
9364        CmsRewriteAliasFilter filter = new CmsRewriteAliasFilter().setSiteRoot(siteRoot);
9365        getVfsDriver(dbc).deleteRewriteAliases(dbc, filter);
9366        getVfsDriver(dbc).insertRewriteAliases(dbc, newAliases);
9367    }
9368
9369    /**
9370     * Searches for users which fit the given criteria.<p>
9371     *
9372     * @param dbc the database context
9373     * @param searchParams the search criteria
9374     *
9375     * @return the users which fit the search criteria
9376     *
9377     * @throws CmsDataAccessException if something goes wrong
9378     */
9379    public List<CmsUser> searchUsers(CmsDbContext dbc, CmsUserSearchParameters searchParams
9380
9381    ) throws CmsDataAccessException {
9382
9383        return getUserDriver(dbc).searchUsers(dbc, searchParams);
9384    }
9385
9386    /**
9387     * Changes the "expire" date of a resource.<p>
9388     *
9389     * @param dbc the current database context
9390     * @param resource the resource to touch
9391     * @param dateExpired the new expire date of the resource
9392     *
9393     * @throws CmsDataAccessException if something goes wrong
9394     *
9395     * @see CmsObject#setDateExpired(String, long, boolean)
9396     * @see I_CmsResourceType#setDateExpired(CmsObject, CmsSecurityManager, CmsResource, long, boolean)
9397     */
9398    public void setDateExpired(CmsDbContext dbc, CmsResource resource, long dateExpired) throws CmsDataAccessException {
9399
9400        resource.setDateExpired(dateExpired);
9401        if (resource.getState().isUnchanged()) {
9402            resource.setState(CmsResource.STATE_CHANGED);
9403        }
9404        getVfsDriver(dbc).writeResourceState(dbc, dbc.currentProject(), resource, UPDATE_STRUCTURE, false);
9405
9406        // modify the last modified project reference
9407        getVfsDriver(dbc).writeResourceState(dbc, dbc.currentProject(), resource, UPDATE_RESOURCE_PROJECT, false);
9408        // log
9409        log(
9410            dbc,
9411            new CmsLogEntry(
9412                dbc,
9413                resource.getStructureId(),
9414                CmsLogEntryType.RESOURCE_DATE_EXPIRED,
9415                new String[] {resource.getRootPath()}),
9416            false);
9417
9418        // clear the cache
9419        m_monitor.clearResourceCache();
9420
9421        // fire the event
9422        Map<String, Object> data = new HashMap<String, Object>(2);
9423        data.put(I_CmsEventListener.KEY_RESOURCE, resource);
9424        data.put(I_CmsEventListener.KEY_CHANGE, Integer.valueOf(CHANGED_TIMEFRAME));
9425        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_MODIFIED, data));
9426    }
9427
9428    /**
9429     * Changes the "last modified" timestamp of a resource.<p>
9430     *
9431     * @param dbc the current database context
9432     * @param resource the resource to touch
9433     * @param dateLastModified the new last modified date of the resource
9434     *
9435     * @throws CmsDataAccessException if something goes wrong
9436     *
9437     * @see CmsObject#setDateLastModified(String, long, boolean)
9438     * @see I_CmsResourceType#setDateLastModified(CmsObject, CmsSecurityManager, CmsResource, long, boolean)
9439     */
9440    public void setDateLastModified(CmsDbContext dbc, CmsResource resource, long dateLastModified)
9441    throws CmsDataAccessException {
9442
9443        // modify the last modification date
9444        resource.setDateLastModified(dateLastModified);
9445        if (resource.getState().isUnchanged()) {
9446            resource.setState(CmsResource.STATE_CHANGED);
9447        } else if (resource.getState().isNew() && (resource.getSiblingCount() > 1)) {
9448            // in case of new resources with siblings make sure the state is correct
9449            resource.setState(CmsResource.STATE_CHANGED);
9450        }
9451        resource.setUserLastModified(dbc.currentUser().getId());
9452        getVfsDriver(dbc).writeResourceState(dbc, dbc.currentProject(), resource, UPDATE_RESOURCE, false);
9453
9454        log(
9455            dbc,
9456            new CmsLogEntry(
9457                dbc,
9458                resource.getStructureId(),
9459                CmsLogEntryType.RESOURCE_TOUCHED,
9460                new String[] {resource.getRootPath()}),
9461            false);
9462
9463        // clear the cache
9464        m_monitor.clearResourceCache();
9465
9466        // fire the event
9467        Map<String, Object> data = new HashMap<String, Object>(2);
9468        data.put(I_CmsEventListener.KEY_RESOURCE, resource);
9469        data.put(I_CmsEventListener.KEY_CHANGE, Integer.valueOf(CHANGED_LASTMODIFIED));
9470        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_MODIFIED, data));
9471    }
9472
9473    /**
9474     * Changes the "release" date of a resource.<p>
9475     *
9476     * @param dbc the current database context
9477     * @param resource the resource to touch
9478     * @param dateReleased the new release date of the resource
9479     *
9480     * @throws CmsDataAccessException if something goes wrong
9481     *
9482     * @see CmsObject#setDateReleased(String, long, boolean)
9483     * @see I_CmsResourceType#setDateReleased(CmsObject, CmsSecurityManager, CmsResource, long, boolean)
9484     */
9485    public void setDateReleased(CmsDbContext dbc, CmsResource resource, long dateReleased)
9486    throws CmsDataAccessException {
9487
9488        // modify the last modification date
9489        resource.setDateReleased(dateReleased);
9490        if (resource.getState().isUnchanged()) {
9491            resource.setState(CmsResource.STATE_CHANGED);
9492        }
9493        getVfsDriver(dbc).writeResourceState(dbc, dbc.currentProject(), resource, UPDATE_STRUCTURE, false);
9494
9495        // modify the last modified project reference
9496        getVfsDriver(dbc).writeResourceState(dbc, dbc.currentProject(), resource, UPDATE_RESOURCE_PROJECT, false);
9497        // log it
9498        log(
9499            dbc,
9500            new CmsLogEntry(
9501                dbc,
9502                resource.getStructureId(),
9503                CmsLogEntryType.RESOURCE_DATE_RELEASED,
9504                new String[] {resource.getRootPath()}),
9505            false);
9506
9507        // clear the cache
9508        m_monitor.clearResourceCache();
9509
9510        // fire the event
9511        Map<String, Object> data = new HashMap<String, Object>(2);
9512        data.put(I_CmsEventListener.KEY_RESOURCE, resource);
9513        data.put(I_CmsEventListener.KEY_CHANGE, Integer.valueOf(CHANGED_TIMEFRAME));
9514        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_MODIFIED, data));
9515    }
9516
9517    /**
9518     * Sets a new parent group for an already existing group.<p>
9519     *
9520     * @param dbc the current database context
9521     * @param groupName the name of the group that should be written
9522     * @param parentGroupName the name of the parent group to set,
9523     *                      or <code>null</code> if the parent
9524     *                      group should be deleted.
9525     *
9526     * @throws CmsException if operation was not successful
9527     * @throws CmsDataAccessException if the group with <code>groupName</code> could not be read from VFS
9528     */
9529    public void setParentGroup(CmsDbContext dbc, String groupName, String parentGroupName)
9530    throws CmsException, CmsDataAccessException {
9531
9532        CmsGroup group = readGroup(dbc, groupName);
9533        CmsUUID parentGroupId = CmsUUID.getNullUUID();
9534
9535        // if the group exists, use its id, else set to unknown.
9536        if (parentGroupName != null) {
9537            parentGroupId = readGroup(dbc, parentGroupName).getId();
9538        }
9539
9540        group.setParentId(parentGroupId);
9541
9542        // write the changes to the cms
9543        writeGroup(dbc, group);
9544    }
9545
9546    /**
9547     * Sets the password for a user.<p>
9548     *
9549     * @param dbc the current database context
9550     * @param username the name of the user
9551     * @param newPassword the new password
9552     *
9553     * @throws CmsException if operation was not successful
9554     * @throws CmsIllegalArgumentException if the user with the <code>username</code> was not found
9555     */
9556    public void setPassword(CmsDbContext dbc, String username, String newPassword)
9557    throws CmsException, CmsIllegalArgumentException {
9558
9559        if (dbc.getRequestContext().getAttribute(CmsUserDriver.REQ_ATTR_DONT_DIGEST_PASSWORD) == null) {
9560            validatePassword(newPassword);
9561        }
9562
9563        // read the user as a system user to verify that the specified old password is correct
9564        CmsUser user = getUserDriver(dbc).readUser(dbc, username);
9565        // only continue if not found and read user from web might succeed
9566        getUserDriver(dbc).writePassword(dbc, username, null, newPassword);
9567        user.getAdditionalInfo().put(
9568            CmsUserSettings.ADDITIONAL_INFO_LAST_PASSWORD_CHANGE,
9569            "" + System.currentTimeMillis());
9570        getUserDriver(dbc).writeUser(dbc, user);
9571
9572        // fire user modified event
9573        Map<String, Object> eventData = new HashMap<String, Object>();
9574        eventData.put(I_CmsEventListener.KEY_USER_ID, user.getId().toString());
9575        eventData.put(I_CmsEventListener.KEY_USER_ACTION, I_CmsEventListener.VALUE_USER_MODIFIED_ACTION_RESET_PASSWORD);
9576        eventData.put(
9577            I_CmsEventListener.KEY_USER_CHANGES,
9578            Integer.valueOf(CmsUser.FLAG_CORE_DATA | CmsUser.FLAG_CORE_DATA));
9579        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_USER_MODIFIED, eventData));
9580    }
9581
9582    /**
9583     * Marks a subscribed resource as deleted.<p>
9584     *
9585     * @param dbc the database context
9586     * @param poolName the name of the database pool to use
9587     * @param resource the subscribed resource to mark as deleted
9588     *
9589     * @throws CmsException if something goes wrong
9590     */
9591    public void setSubscribedResourceAsDeleted(CmsDbContext dbc, String poolName, CmsResource resource)
9592    throws CmsException {
9593
9594        getSubscriptionDriver().setSubscribedResourceAsDeleted(dbc, poolName, resource);
9595    }
9596
9597    /**
9598     * Moves an user to the given organizational unit.<p>
9599     *
9600     * @param dbc the current db context
9601     * @param orgUnit the organizational unit to add the resource to
9602     * @param user the user that is to be moved to the organizational unit
9603     *
9604     * @throws CmsException if something goes wrong
9605     *
9606     * @see org.opencms.security.CmsOrgUnitManager#setUsersOrganizationalUnit(CmsObject, String, String)
9607     */
9608    public void setUsersOrganizationalUnit(CmsDbContext dbc, CmsOrganizationalUnit orgUnit, CmsUser user)
9609    throws CmsException {
9610
9611        if (!getGroupsOfUser(dbc, user.getName(), false).isEmpty()) {
9612            throw new CmsDbConsistencyException(
9613                Messages.get().container(Messages.ERR_ORGUNIT_MOVE_USER_2, orgUnit.getName(), user.getName()));
9614        }
9615
9616        // move the principal
9617        getUserDriver(dbc).setUsersOrganizationalUnit(dbc, orgUnit, user);
9618        // remove the principal from cache
9619        m_monitor.clearUserCache(user);
9620
9621        if (!dbc.getProjectId().isNullUUID()) {
9622            // user modified event is not needed
9623            return;
9624        }
9625        // fire user modified event
9626        Map<String, Object> eventData = new HashMap<String, Object>();
9627        eventData.put(I_CmsEventListener.KEY_USER_ID, user.getId().toString());
9628        eventData.put(I_CmsEventListener.KEY_OU_NAME, user.getOuFqn());
9629        eventData.put(I_CmsEventListener.KEY_USER_ACTION, I_CmsEventListener.VALUE_USER_MODIFIED_ACTION_SET_OU);
9630        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_USER_MODIFIED, eventData));
9631    }
9632
9633    /**
9634     * Subscribes the user or group to the resource.<p>
9635     *
9636     * @param dbc the database context
9637     * @param poolName the name of the database pool to use
9638     * @param principal the principal that subscribes to the resource
9639     * @param resource the resource to subscribe to
9640     *
9641     * @throws CmsException if something goes wrong
9642     */
9643    public void subscribeResourceFor(CmsDbContext dbc, String poolName, CmsPrincipal principal, CmsResource resource)
9644    throws CmsException {
9645
9646        getSubscriptionDriver().subscribeResourceFor(dbc, poolName, principal, resource);
9647    }
9648
9649    /**
9650     * Undelete the resource.<p>
9651     *
9652     * @param dbc the current database context
9653     * @param resource the name of the resource to apply this operation to
9654     *
9655     * @throws CmsException if something goes wrong
9656     *
9657     * @see CmsObject#undeleteResource(String, boolean)
9658     * @see I_CmsResourceType#undelete(CmsObject, CmsSecurityManager, CmsResource, boolean)
9659     */
9660    public void undelete(CmsDbContext dbc, CmsResource resource) throws CmsException {
9661
9662        if (!resource.getState().isDeleted()) {
9663            throw new CmsVfsException(
9664                Messages.get().container(
9665                    Messages.ERR_UNDELETE_FOR_RESOURCE_DELETED_1,
9666                    dbc.removeSiteRoot(resource.getRootPath())));
9667        }
9668
9669        // set the state to changed
9670        resource.setState(CmsResourceState.STATE_CHANGED);
9671        // perform the changes
9672        updateState(dbc, resource, false);
9673        // log it
9674        log(
9675            dbc,
9676            new CmsLogEntry(
9677                dbc,
9678                resource.getStructureId(),
9679                CmsLogEntryType.RESOURCE_UNDELETED,
9680                new String[] {resource.getRootPath()}),
9681            false);
9682        // clear the cache
9683        m_monitor.clearResourceCache();
9684
9685        // fire change event
9686        Map<String, Object> data = new HashMap<String, Object>(2);
9687        data.put(I_CmsEventListener.KEY_RESOURCE, resource);
9688        data.put(I_CmsEventListener.KEY_CHANGE, Integer.valueOf(CHANGED_RESOURCE));
9689        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_MODIFIED, data));
9690    }
9691
9692    /**
9693     * Undos all changes in the resource by restoring the version from the
9694     * online project to the current offline project.<p>
9695     *
9696     * @param dbc the current database context
9697     * @param resource the name of the resource to apply this operation to
9698     * @param mode the undo mode, one of the <code>{@link org.opencms.file.CmsResource.CmsResourceUndoMode}#UNDO_XXX</code> constants
9699     *      please note that the recursive flag is ignored at this level
9700     *
9701     * @throws CmsException if something goes wrong
9702     *
9703     * @see CmsObject#undoChanges(String, CmsResource.CmsResourceUndoMode)
9704     * @see I_CmsResourceType#undoChanges(CmsObject, CmsSecurityManager, CmsResource, CmsResource.CmsResourceUndoMode)
9705     */
9706    public void undoChanges(CmsDbContext dbc, CmsResource resource, CmsResource.CmsResourceUndoMode mode)
9707    throws CmsException {
9708
9709        if (resource.getState().isNew()) {
9710            // undo changes is impossible on a new resource
9711            throw new CmsVfsException(Messages.get().container(Messages.ERR_UNDO_CHANGES_FOR_RESOURCE_NEW_0));
9712        }
9713
9714        // we need this for later use
9715        CmsProject onlineProject = readProject(dbc, CmsProject.ONLINE_PROJECT_ID);
9716        // read the resource from the online project
9717        CmsResource onlineResource = getVfsDriver(
9718            dbc).readResource(dbc, CmsProject.ONLINE_PROJECT_ID, resource.getStructureId(), true);
9719
9720        CmsResource onlineResourceByPath = null;
9721        try {
9722            // this is needed to figure out if a moved resource overwrote a deleted one
9723            onlineResourceByPath = getVfsDriver(
9724                dbc).readResource(dbc, CmsProject.ONLINE_PROJECT_ID, resource.getRootPath(), true);
9725
9726            // force undo move operation if needed
9727            if (!mode.isUndoMove() && !onlineResourceByPath.getRootPath().equals(onlineResource.getRootPath())) {
9728                mode = mode.includeMove();
9729            }
9730        } catch (Exception e) {
9731            // ok
9732        }
9733
9734        boolean moved = !onlineResource.getRootPath().equals(resource.getRootPath());
9735        // undo move operation if required
9736        if (moved && mode.isUndoMove()) {
9737            moveResource(dbc, resource, onlineResource.getRootPath(), true);
9738            if ((onlineResourceByPath != null)
9739                && !onlineResourceByPath.getRootPath().equals(onlineResource.getRootPath())) {
9740                // was moved over deleted, so the deleted file has to be undone
9741                undoContentChanges(dbc, onlineProject, null, onlineResourceByPath, CmsResource.STATE_UNCHANGED, true);
9742            }
9743        }
9744        // undo content changes
9745        CmsResourceState newState = CmsResource.STATE_UNCHANGED;
9746        if (moved && !mode.isUndoMove()) {
9747            newState = CmsResource.STATE_CHANGED;
9748        }
9749        undoContentChanges(dbc, onlineProject, resource, onlineResource, newState, moved && mode.isUndoMove());
9750        // because undoContentChanges deletes the offline resource internally, we have
9751        // to write an entry to the log table to prevent the resource from appearing in the
9752        // user's publish list.
9753        log(
9754            dbc,
9755            new CmsLogEntry(
9756                dbc,
9757                resource.getStructureId(),
9758                CmsLogEntryType.RESOURCE_CHANGES_UNDONE,
9759                new String[] {resource.getRootPath()}),
9760            true);
9761
9762    }
9763
9764    /**
9765     * Unlocks all resources in the given project.<p>
9766     *
9767     * @param project the project to unlock the resources in
9768     */
9769    public void unlockProject(CmsProject project) {
9770
9771        // unlock all resources in the project
9772        m_lockManager.removeResourcesInProject(project.getUuid(), false);
9773        m_monitor.clearResourceCache();
9774        m_monitor.flushCache(CmsMemoryMonitor.CacheType.PROJECT, CmsMemoryMonitor.CacheType.PERMISSION);
9775    }
9776
9777    /**
9778     * Unlocks a resource.<p>
9779     *
9780     * @param dbc the current database context
9781     * @param resource the resource to unlock
9782     * @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
9783     * @param removeSystemLock <code>true</code>, if you also want to remove system locks
9784     *
9785     * @throws CmsException if something goes wrong
9786     *
9787     * @see CmsObject#unlockResource(String)
9788     * @see I_CmsResourceType#unlockResource(CmsObject, CmsSecurityManager, CmsResource)
9789     */
9790    public void unlockResource(CmsDbContext dbc, CmsResource resource, boolean force, boolean removeSystemLock)
9791    throws CmsException {
9792
9793        // update the resource cache
9794        m_monitor.clearResourceCache();
9795
9796        // now update lock status
9797        m_lockManager.removeResource(dbc, resource, force, removeSystemLock);
9798
9799        // we must also clear the permission cache
9800        m_monitor.flushCache(CmsMemoryMonitor.CacheType.PERMISSION);
9801
9802        // fire resource modification event
9803        Map<String, Object> data = new HashMap<String, Object>(2);
9804        data.put(I_CmsEventListener.KEY_RESOURCE, resource);
9805        data.put(I_CmsEventListener.KEY_CHANGE, Integer.valueOf(NOTHING_CHANGED));
9806        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_MODIFIED, data));
9807    }
9808
9809    /**
9810     * Unsubscribes all deleted resources that were deleted before the specified time stamp.<p>
9811     *
9812     * @param dbc the database context
9813     * @param poolName the name of the database pool to use
9814     * @param deletedTo the time stamp to which the resources have been deleted
9815     *
9816     * @throws CmsException if something goes wrong
9817     */
9818    public void unsubscribeAllDeletedResources(CmsDbContext dbc, String poolName, long deletedTo) throws CmsException {
9819
9820        getSubscriptionDriver().unsubscribeAllDeletedResources(dbc, poolName, deletedTo);
9821    }
9822
9823    /**
9824     * Unsubscribes the principal from all resources.<p>
9825     *
9826     * @param dbc the database context
9827     * @param poolName the name of the database pool to use
9828     * @param principal the principal that unsubscribes from all resources
9829     *
9830     * @throws CmsException if something goes wrong
9831     */
9832    public void unsubscribeAllResourcesFor(CmsDbContext dbc, String poolName, CmsPrincipal principal)
9833    throws CmsException {
9834
9835        getSubscriptionDriver().unsubscribeAllResourcesFor(dbc, poolName, principal);
9836
9837    }
9838
9839    /**
9840     * Unsubscribes the principal from the resource.<p>
9841     *
9842     * @param dbc the database context
9843     * @param poolName the name of the database pool to use
9844     * @param principal the principal that unsubscribes from the resource
9845     * @param resource the resource to unsubscribe from
9846     *
9847     * @throws CmsException if something goes wrong
9848     */
9849    public void unsubscribeResourceFor(CmsDbContext dbc, String poolName, CmsPrincipal principal, CmsResource resource)
9850    throws CmsException {
9851
9852        getSubscriptionDriver().unsubscribeResourceFor(dbc, poolName, principal, resource);
9853    }
9854
9855    /**
9856     * Unsubscribes all groups and users from the resource.<p>
9857     *
9858     * @param dbc the database context
9859     * @param poolName the name of the database pool to use
9860     * @param resource the resource to unsubscribe all groups and users from
9861     *
9862     * @throws CmsException if something goes wrong
9863     */
9864    public void unsubscribeResourceForAll(CmsDbContext dbc, String poolName, CmsResource resource) throws CmsException {
9865
9866        getSubscriptionDriver().unsubscribeResourceForAll(dbc, poolName, resource);
9867    }
9868
9869    /**
9870     * Update the export points.<p>
9871     *
9872     * All files and folders "inside" an export point are written.<p>
9873     *
9874     * @param dbc the current database context
9875     */
9876    public void updateExportPoints(CmsDbContext dbc) {
9877
9878        try {
9879            // read the export points and return immediately if there are no export points at all
9880            Set<CmsExportPoint> exportPoints = new HashSet<CmsExportPoint>();
9881            exportPoints.addAll(OpenCms.getExportPoints());
9882            exportPoints.addAll(OpenCms.getModuleManager().getExportPoints());
9883            if (exportPoints.size() == 0) {
9884                if (LOG.isWarnEnabled()) {
9885                    LOG.warn(Messages.get().getBundle().key(Messages.LOG_NO_EXPORT_POINTS_CONFIGURED_0));
9886                }
9887                return;
9888            }
9889
9890            // create the driver to write the export points
9891            I_CmsExportPointDriver exportPointDriver = OpenCms.getImportExportManager().createExportPointDriver(
9892                exportPoints);
9893
9894            // the export point hash table contains RFS export paths keyed by their internal VFS paths
9895            Iterator<String> i = exportPointDriver.getExportPointPaths().iterator();
9896            I_CmsVfsDriver vfsDriver = getVfsDriver(dbc);
9897            while (i.hasNext()) {
9898                String currentExportPoint = i.next();
9899
9900                // print some report messages
9901                if (LOG.isInfoEnabled()) {
9902                    LOG.info(Messages.get().getBundle().key(Messages.LOG_WRITE_EXPORT_POINT_1, currentExportPoint));
9903                }
9904
9905                try {
9906                    CmsResourceFilter filter = CmsResourceFilter.DEFAULT;
9907                    List<CmsResource> resources = vfsDriver.readResourceTree(
9908                        dbc,
9909                        CmsProject.ONLINE_PROJECT_ID,
9910                        currentExportPoint,
9911                        filter.getType(),
9912                        filter.getState(),
9913                        filter.getModifiedAfter(),
9914                        filter.getModifiedBefore(),
9915                        filter.getReleaseAfter(),
9916                        filter.getReleaseBefore(),
9917                        filter.getExpireAfter(),
9918                        filter.getExpireBefore(),
9919                        CmsDriverManager.READMODE_INCLUDE_TREE
9920                            | (filter.excludeType() ? CmsDriverManager.READMODE_EXCLUDE_TYPE : 0)
9921                            | (filter.excludeState() ? CmsDriverManager.READMODE_EXCLUDE_STATE : 0));
9922
9923                    Iterator<CmsResource> j = resources.iterator();
9924                    while (j.hasNext()) {
9925                        CmsResource currentResource = j.next();
9926
9927                        if (currentResource.isFolder()) {
9928                            // export the folder
9929                            exportPointDriver.createFolder(currentResource.getRootPath(), currentExportPoint);
9930                        } else {
9931                            // try to create the exportpoint folder
9932                            exportPointDriver.createFolder(currentExportPoint, currentExportPoint);
9933                            byte[] onlineContent = vfsDriver.readContent(
9934                                dbc,
9935                                CmsProject.ONLINE_PROJECT_ID,
9936                                currentResource.getResourceId());
9937                            // export the file content online
9938                            exportPointDriver.writeFile(
9939                                currentResource.getRootPath(),
9940                                currentExportPoint,
9941                                onlineContent);
9942                        }
9943                    }
9944                } catch (CmsException e) {
9945                    // there might exist export points without corresponding resources in the VFS
9946                    // -> ignore exceptions which are not "resource not found" exception quiet here
9947                    if (e instanceof CmsVfsResourceNotFoundException) {
9948                        if (LOG.isErrorEnabled()) {
9949                            LOG.error(Messages.get().getBundle().key(Messages.LOG_UPDATE_EXORT_POINTS_ERROR_0), e);
9950                        }
9951                    }
9952                }
9953            }
9954        } catch (Exception e) {
9955            if (LOG.isErrorEnabled()) {
9956                LOG.error(Messages.get().getBundle().key(Messages.LOG_UPDATE_EXORT_POINTS_ERROR_0), e);
9957            }
9958        }
9959    }
9960
9961    /**
9962     * Updates the last login date on the given user to the current time.<p>
9963     *
9964     * @param dbc the current database context
9965     * @param user the user to be updated
9966     *
9967     * @throws CmsException if operation was not successful
9968     */
9969    public void updateLastLoginDate(CmsDbContext dbc, CmsUser user) throws CmsException {
9970
9971        m_monitor.clearUserCache(user);
9972        // set the last login time to the current time
9973        user.setLastlogin(System.currentTimeMillis());
9974        dbc.setAttribute(ATTRIBUTE_LOGIN, user.getName());
9975        getUserDriver(dbc).writeUser(dbc, user);
9976        // update cache
9977        m_monitor.cacheUser(user);
9978
9979        // invalidate all user dependent caches
9980        m_monitor.flushCache(
9981            CmsMemoryMonitor.CacheType.ACL,
9982            CmsMemoryMonitor.CacheType.GROUP,
9983            CmsMemoryMonitor.CacheType.ORG_UNIT,
9984            CmsMemoryMonitor.CacheType.USER_LIST,
9985            CmsMemoryMonitor.CacheType.PERMISSION,
9986            CmsMemoryMonitor.CacheType.RESOURCE_LIST);
9987
9988        // fire user modified event
9989        Map<String, Object> eventData = new HashMap<String, Object>();
9990        eventData.put(I_CmsEventListener.KEY_USER_ID, user.getId().toString());
9991        eventData.put(I_CmsEventListener.KEY_USER_NAME, user.getName());
9992        eventData.put(I_CmsEventListener.KEY_USER_ACTION, I_CmsEventListener.VALUE_USER_MODIFIED_ACTION_WRITE_USER);
9993        eventData.put(I_CmsEventListener.KEY_USER_CHANGES, Integer.valueOf(CmsUser.FLAG_LAST_LOGIN));
9994        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_USER_MODIFIED, eventData));
9995    }
9996
9997    /**
9998     * Logs everything that has not been written to DB jet.<p>
9999     *
10000     * @param dbc the current db context
10001     *
10002     * @throws CmsDataAccessException if something goes wrong
10003     */
10004    public void updateLog(CmsDbContext dbc) throws CmsDataAccessException {
10005
10006        synchronized (m_publishListUpdateLock) {
10007
10008            if (m_log.isEmpty()) {
10009                return;
10010            }
10011
10012            List<CmsLogEntry> log = new ArrayList<CmsLogEntry>(m_log);
10013            m_log.clear();
10014            String logTableEnabledStr = (String)OpenCms.getRuntimeProperty(PARAM_LOG_TABLE_ENABLED);
10015            if (Boolean.parseBoolean(logTableEnabledStr)) { // defaults to 'false' if value not set
10016                m_projectDriver.log(dbc, log);
10017            }
10018            A_CmsLogPublishListConverter converter = null;
10019            switch (OpenCms.getPublishManager().getPublishListRemoveMode()) {
10020                case currentUser:
10021                    converter = new CmsLogPublishListConverterCurrentUser();
10022                    break;
10023                case allUsers:
10024                default:
10025                    converter = new CmsLogPublishListConverterAllUsers();
10026                    break;
10027            }
10028            for (CmsLogEntry entry : log) {
10029                converter.add(entry);
10030            }
10031            converter.writeChangesToDatabase(dbc, m_projectDriver);
10032        }
10033    }
10034
10035    /**
10036     * Updates/Creates the given relations for the given resource.<p>
10037     *
10038     * @param dbc the db context
10039     * @param resource the resource to update the relations for
10040     * @param links the links to consider for updating
10041     * @param updateSiblingState if true, sets the state of siblings whose relations have changed to 'changed' (unless they are new or deleted)
10042     *
10043     * @throws CmsException if something goes wrong
10044     *
10045     * @see CmsSecurityManager#updateRelationsForResource(CmsRequestContext, CmsResource, List)
10046     */
10047    public void updateRelationsForResource(
10048        CmsDbContext dbc,
10049        CmsResource resource,
10050        List<CmsLink> links,
10051        boolean updateSiblingState)
10052    throws CmsException {
10053
10054        if (links == null) {
10055            links = new ArrayList<>();
10056        }
10057
10058        // create new relation information
10059        I_CmsVfsDriver vfsDriver = getVfsDriver(dbc);
10060        Iterator<CmsLink> itLinks = links.iterator();
10061        Set<CmsRelation> relationsForOriginalResource = new HashSet<>();
10062        while (itLinks.hasNext()) {
10063            CmsLink link = itLinks.next();
10064            if (link.isInternal()) { // only update internal links
10065                if (CmsStringUtil.isEmptyOrWhitespaceOnly(link.getTarget())) {
10066                    // only an anchor
10067                    continue;
10068                }
10069                CmsUUID targetId = link.getStructureId();
10070                String destPath = link.getTarget();
10071
10072                if (targetId != null) {
10073                    // the link target may not be a VFS path even if the link id is a structure id,
10074                    // so if possible, we read the resource for the id and set the relation target to its
10075                    // real root path.
10076                    try {
10077                        CmsResource destRes = readResource(dbc, targetId, CmsResourceFilter.ALL);
10078                        destPath = destRes.getRootPath();
10079                    } catch (CmsVfsResourceNotFoundException e) {
10080                        // ignore
10081                    }
10082                }
10083
10084                CmsRelation originalRelation = new CmsRelation(
10085                    resource.getStructureId(),
10086                    resource.getRootPath(),
10087                    link.getStructureId(),
10088                    destPath,
10089                    link.getType());
10090                relationsForOriginalResource.add(originalRelation);
10091            }
10092        }
10093        List<CmsResource> siblings = resource.getSiblingCount() == 1
10094        ? Arrays.asList(resource)
10095        : readSiblings(dbc, resource, CmsResourceFilter.ALL);
10096
10097        for (CmsResource sibling : siblings) {
10098            // For each sibling, we determine which 'defined in content' relations it SHOULD have,
10099            // and only update the relations if that set differs from the ones it actually has.
10100            // If the updateSiblingState flag is set, then for siblings, we update the structure
10101            // state to changed (unless the state was 'deleted' or 'new').
10102            // This is so that even if the user later publishes only one sibling, the other sibling will still
10103            // show up as changed in the GUI, so the user can publish it separately (with its updated relations).
10104            Set<CmsRelation> relationsForSibling = relationsForOriginalResource.stream().map(
10105                relation -> new CmsRelation(
10106                    sibling.getStructureId(),
10107                    sibling.getRootPath(),
10108                    relation.getTargetId(),
10109                    relation.getTargetPath(),
10110                    relation.getType())).collect(Collectors.toSet());
10111            Set<CmsRelation> existingRelations = new HashSet<>(
10112                vfsDriver.readRelations(
10113                    dbc,
10114                    dbc.currentProject().getUuid(),
10115                    sibling,
10116                    CmsRelationFilter.TARGETS.filterDefinedInContent()));
10117            if (!existingRelations.equals(relationsForSibling)) {
10118                vfsDriver.deleteRelations(
10119                    dbc,
10120                    dbc.currentProject().getUuid(),
10121                    sibling,
10122                    CmsRelationFilter.TARGETS.filterDefinedInContent());
10123                for (CmsRelation relation : relationsForSibling) {
10124                    vfsDriver.createRelation(dbc, dbc.currentProject().getUuid(), relation);
10125                }
10126                if (!sibling.getState().isDeleted()
10127                    && !sibling.getState().isNew()
10128                    && (siblings.size() > 1)
10129                    && updateSiblingState) {
10130                    sibling.setState(CmsResource.STATE_CHANGED);
10131                    vfsDriver.writeResourceState(
10132                        dbc,
10133                        dbc.currentProject(),
10134                        sibling,
10135                        CmsDriverManager.UPDATE_STRUCTURE_STATE,
10136                        false);
10137                }
10138            }
10139        }
10140    }
10141
10142    /**
10143     * Returns <code>true</code> if a user is member of the given group.<p>
10144     *
10145     * @param dbc the current database context
10146     * @param username the name of the user to check
10147     * @param groupname the name of the group to check
10148     * @param readRoles if to read roles or groups
10149     *
10150     * @return <code>true</code>, if the user is in the group, <code>false</code> otherwise
10151     *
10152     * @throws CmsException if something goes wrong
10153     */
10154    public boolean userInGroup(CmsDbContext dbc, String username, String groupname, boolean readRoles)
10155    throws CmsException {
10156
10157        List<CmsGroup> groups = getGroupsOfUser(dbc, username, readRoles);
10158        for (int i = 0; i < groups.size(); i++) {
10159            CmsGroup group = groups.get(i);
10160            if (groupname.equals(group.getName()) || groupname.substring(1).equals(group.getName())) {
10161                return true;
10162            }
10163        }
10164        return false;
10165    }
10166
10167    /**
10168     * This method checks if a new password follows the rules for
10169     * new passwords, which are defined by a Class implementing the
10170     * <code>{@link org.opencms.security.I_CmsPasswordHandler}</code>
10171     * interface and configured in the opencms.properties file.<p>
10172     *
10173     * If this method throws no exception the password is valid.<p>
10174     *
10175     * @param password the new password that has to be checked
10176     *
10177     * @throws CmsSecurityException if the password is not valid
10178     */
10179    public void validatePassword(String password) throws CmsSecurityException {
10180
10181        OpenCms.getPasswordHandler().validatePassword(password);
10182    }
10183
10184    /**
10185     * Validates the relations for the given resources.<p>
10186     *
10187     * @param dbc the database context
10188     * @param publishList the resources to validate during publishing
10189     * @param report a report to write the messages to
10190     *
10191     * @return a map with lists of invalid links
10192     *          (<code>{@link org.opencms.relations.CmsRelation}}</code> objects)
10193     *          keyed by root paths
10194     *
10195     * @throws Exception if something goes wrong
10196     */
10197    public Map<String, List<CmsRelation>> validateRelations(
10198        CmsDbContext dbc,
10199        CmsPublishList publishList,
10200        I_CmsReport report)
10201    throws Exception {
10202
10203        return m_htmlLinkValidator.validateResources(dbc, publishList, report);
10204    }
10205
10206    /**
10207     * Writes an access control entries to a given resource.<p>
10208     *
10209     * @param dbc the current database context
10210     * @param resource the resource
10211     * @param ace the entry to write
10212     *
10213     * @throws CmsException if something goes wrong
10214     */
10215    public void writeAccessControlEntry(CmsDbContext dbc, CmsResource resource, CmsAccessControlEntry ace)
10216    throws CmsException {
10217
10218        // write the new ace
10219        getUserDriver(dbc).writeAccessControlEntry(dbc, dbc.currentProject(), ace);
10220
10221        // log it
10222        log(
10223            dbc,
10224            new CmsLogEntry(
10225                dbc,
10226                resource.getStructureId(),
10227                CmsLogEntryType.RESOURCE_PERMISSIONS,
10228                new String[] {resource.getRootPath()}),
10229            false);
10230
10231        // update the "last modified" information
10232        setDateLastModified(dbc, resource, resource.getDateLastModified());
10233
10234        // clear the cache
10235        m_monitor.clearAccessControlListCache();
10236
10237        // fire a resource modification event
10238        Map<String, Object> data = new HashMap<String, Object>(2);
10239        data.put(I_CmsEventListener.KEY_RESOURCE, resource);
10240        data.put(I_CmsEventListener.KEY_CHANGE, Integer.valueOf(CHANGED_ACCESSCONTROL));
10241        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_MODIFIED, data));
10242    }
10243
10244    /**
10245     * Writes all export points into the file system for the publish task
10246     * specified by trhe given publish history ID.<p>
10247     *
10248     * @param dbc the current database context
10249     * @param report an I_CmsReport instance to print output message, or null to write messages to the log file
10250     * @param publishHistoryId ID to identify the publish task in the publish history
10251     */
10252    public void writeExportPoints(CmsDbContext dbc, I_CmsReport report, CmsUUID publishHistoryId) {
10253
10254        boolean printReportHeaders = false;
10255        List<CmsPublishedResource> publishedResources = null;
10256        try {
10257            // read the "published resources" for the specified publish history ID
10258            publishedResources = getProjectDriver(dbc).readPublishedResources(dbc, publishHistoryId);
10259        } catch (CmsException e) {
10260            if (LOG.isErrorEnabled()) {
10261                LOG.error(
10262                    Messages.get().getBundle().key(Messages.ERR_READ_PUBLISHED_RESOURCES_FOR_ID_1, publishHistoryId),
10263                    e);
10264            }
10265        }
10266        if ((publishedResources == null) || publishedResources.isEmpty()) {
10267            if (LOG.isWarnEnabled()) {
10268                LOG.warn(Messages.get().getBundle().key(Messages.LOG_EMPTY_PUBLISH_HISTORY_1, publishHistoryId));
10269            }
10270            return;
10271        }
10272
10273        // read the export points and return immediately if there are no export points at all
10274        Set<CmsExportPoint> exportPoints = new HashSet<CmsExportPoint>();
10275        exportPoints.addAll(OpenCms.getExportPoints());
10276        exportPoints.addAll(OpenCms.getModuleManager().getExportPoints());
10277        if (exportPoints.size() == 0) {
10278            if (LOG.isWarnEnabled()) {
10279                LOG.warn(Messages.get().getBundle().key(Messages.LOG_NO_EXPORT_POINTS_CONFIGURED_0));
10280            }
10281            return;
10282        }
10283
10284        // create the driver to write the export points
10285        I_CmsExportPointDriver exportPointDriver = OpenCms.getImportExportManager().createExportPointDriver(
10286            exportPoints);
10287
10288        // the report may be null if the export point write was started by an event
10289        if (report == null) {
10290            if (dbc.getRequestContext() != null) {
10291                report = new CmsLogReport(dbc.getRequestContext().getLocale(), getClass());
10292            } else {
10293                report = new CmsLogReport(CmsLocaleManager.getDefaultLocale(), getClass());
10294            }
10295        }
10296
10297        // iterate over all published resources to export them
10298        I_CmsVfsDriver vfsDriver = getVfsDriver(dbc);
10299        Iterator<CmsPublishedResource> i = publishedResources.iterator();
10300        while (i.hasNext()) {
10301            CmsPublishedResource currentPublishedResource = i.next();
10302            String currentExportPoint = exportPointDriver.getExportPoint(currentPublishedResource.getRootPath());
10303
10304            if (currentExportPoint != null) {
10305                if (!printReportHeaders) {
10306                    report.println(
10307                        Messages.get().container(Messages.RPT_EXPORT_POINTS_WRITE_BEGIN_0),
10308                        I_CmsReport.FORMAT_HEADLINE);
10309                    printReportHeaders = true;
10310                }
10311
10312                // print report message
10313                if (currentPublishedResource.getState().isDeleted()) {
10314                    report.print(
10315                        Messages.get().container(Messages.RPT_EXPORT_POINTS_DELETE_0),
10316                        I_CmsReport.FORMAT_NOTE);
10317                } else {
10318                    report.print(Messages.get().container(Messages.RPT_EXPORT_POINTS_WRITE_0), I_CmsReport.FORMAT_NOTE);
10319                }
10320                report.print(
10321                    org.opencms.report.Messages.get().container(
10322                        org.opencms.report.Messages.RPT_ARGUMENT_1,
10323                        currentPublishedResource.getRootPath()));
10324                report.print(org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_DOTS_0));
10325
10326                if (currentPublishedResource.isFolder()) {
10327                    // export the folder
10328                    if (currentPublishedResource.getState().isDeleted()) {
10329                        exportPointDriver.deleteResource(currentPublishedResource.getRootPath(), currentExportPoint);
10330                    } else {
10331                        exportPointDriver.createFolder(currentPublishedResource.getRootPath(), currentExportPoint);
10332                    }
10333                    report.println(
10334                        org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_OK_0),
10335                        I_CmsReport.FORMAT_OK);
10336                } else {
10337                    // export the file
10338                    try {
10339                        if (currentPublishedResource.getState().isDeleted()) {
10340                            exportPointDriver.deleteResource(
10341                                currentPublishedResource.getRootPath(),
10342                                currentExportPoint);
10343                        } else {
10344                            // read the file content online
10345                            byte[] onlineContent = vfsDriver.readContent(
10346                                dbc,
10347                                CmsProject.ONLINE_PROJECT_ID,
10348                                currentPublishedResource.getResourceId());
10349                            exportPointDriver.writeFile(
10350                                currentPublishedResource.getRootPath(),
10351                                currentExportPoint,
10352                                onlineContent);
10353                        }
10354                        report.println(
10355                            org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_OK_0),
10356                            I_CmsReport.FORMAT_OK);
10357                    } catch (CmsException e) {
10358                        if (LOG.isErrorEnabled()) {
10359                            LOG.error(
10360                                Messages.get().getBundle().key(
10361                                    Messages.LOG_WRITE_EXPORT_POINT_ERROR_1,
10362                                    currentPublishedResource.getRootPath()),
10363                                e);
10364                        }
10365                        report.println(
10366                            org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_FAILED_0),
10367                            I_CmsReport.FORMAT_ERROR);
10368                    }
10369                }
10370            }
10371        }
10372        if (printReportHeaders) {
10373            report.println(
10374                Messages.get().container(Messages.RPT_EXPORT_POINTS_WRITE_END_0),
10375                I_CmsReport.FORMAT_HEADLINE);
10376        }
10377    }
10378
10379    /**
10380     * Writes a resource to the OpenCms VFS, including it's content.<p>
10381     *
10382     * Applies only to resources of type <code>{@link CmsFile}</code>
10383     * i.e. resources that have a binary content attached.<p>
10384     *
10385     * Certain resource types might apply content validation or transformation rules
10386     * before the resource is actually written to the VFS. The returned result
10387     * might therefore be a modified version from the provided original.<p>
10388     *
10389     * @param dbc the current database context
10390     * @param resource the resource to apply this operation to
10391     *
10392     * @return the written resource (may have been modified)
10393     *
10394     * @throws CmsException if something goes wrong
10395     *
10396     * @see CmsObject#writeFile(CmsFile)
10397     * @see I_CmsResourceType#writeFile(CmsObject, CmsSecurityManager, CmsFile)
10398     */
10399    public CmsFile writeFile(CmsDbContext dbc, CmsFile resource) throws CmsException {
10400
10401        resource.setUserLastModified(dbc.currentUser().getId());
10402        resource.setContents(resource.getContents()); // to be sure the content date is updated
10403
10404        getVfsDriver(dbc).writeResource(dbc, dbc.currentProject().getUuid(), resource, UPDATE_RESOURCE_STATE);
10405
10406        byte[] contents = resource.getContents();
10407        getVfsDriver(dbc).writeContent(dbc, resource.getResourceId(), contents);
10408        // log it
10409        log(
10410            dbc,
10411            new CmsLogEntry(
10412                dbc,
10413                resource.getStructureId(),
10414                CmsLogEntryType.RESOURCE_CONTENT_MODIFIED,
10415                new String[] {resource.getRootPath()}),
10416            false);
10417
10418        // read the file back from db
10419        resource = new CmsFile(readResource(dbc, resource.getStructureId(), CmsResourceFilter.ALL));
10420        resource.setContents(contents);
10421
10422        deleteRelationsWithSiblings(dbc, resource);
10423
10424        // update the cache
10425        m_monitor.clearResourceCache();
10426
10427        Map<String, Object> data = new HashMap<String, Object>(2);
10428        data.put(I_CmsEventListener.KEY_RESOURCE, resource);
10429        data.put(I_CmsEventListener.KEY_CHANGE, Integer.valueOf(CHANGED_CONTENT));
10430        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_MODIFIED, data));
10431
10432        return resource;
10433    }
10434
10435    /**
10436     * Writes an already existing group.<p>
10437     *
10438     * The group id has to be a valid OpenCms group id.<br>
10439     *
10440     * The group with the given id will be completely overridden
10441     * by the given data.<p>
10442     *
10443     * @param dbc the current database context
10444     * @param group the group that should be written
10445     *
10446     * @throws CmsException if operation was not successful
10447     */
10448    public void writeGroup(CmsDbContext dbc, CmsGroup group) throws CmsException {
10449
10450        CmsGroup oldGroup = readGroup(dbc, group.getName());
10451        m_monitor.uncacheGroup(oldGroup);
10452        getUserDriver(dbc).writeGroup(dbc, group);
10453        m_monitor.cacheGroup(group);
10454
10455        if (!dbc.getProjectId().isNullUUID()) {
10456            // group modified event is not needed
10457            return;
10458        }
10459        // fire group modified event
10460        Map<String, Object> eventData = new HashMap<String, Object>();
10461        eventData.put(I_CmsEventListener.KEY_GROUP_ID, group.getId().toString());
10462        eventData.put(I_CmsEventListener.KEY_GROUP_NAME, oldGroup.getName());
10463        eventData.put(I_CmsEventListener.KEY_USER_ACTION, I_CmsEventListener.VALUE_GROUP_MODIFIED_ACTION_WRITE);
10464        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_GROUP_MODIFIED, eventData));
10465    }
10466
10467    /**
10468     * Creates an historical entry of the current project.<p>
10469     *
10470     * @param dbc the current database context
10471     * @param publishTag the version
10472     * @param publishDate the date of publishing
10473     *
10474     * @throws CmsDataAccessException if operation was not successful
10475     */
10476    public void writeHistoryProject(CmsDbContext dbc, int publishTag, long publishDate) throws CmsDataAccessException {
10477
10478        getHistoryDriver(dbc).writeProject(dbc, publishTag, publishDate);
10479    }
10480
10481    /**
10482     * Writes the locks that are currently stored in-memory to the database to allow restoring them
10483     * in future server startups.<p>
10484     *
10485     * This overwrites the locks previously stored in the underlying database table.<p>
10486     *
10487     * @param dbc the current database context
10488     *
10489     * @throws CmsException if something goes wrong
10490     */
10491    public void writeLocks(CmsDbContext dbc) throws CmsException {
10492
10493        m_lockManager.writeLocks(dbc);
10494    }
10495
10496    /**
10497     * Writes an already existing organizational unit.<p>
10498     *
10499     * The organizational unit id has to be a valid OpenCms organizational unit id.<br>
10500     *
10501     * The organizational unit with the given id will be completely overridden
10502     * by the given data.<p>
10503     *
10504     * @param dbc the current db context
10505     * @param organizationalUnit the organizational unit that should be written
10506     *
10507     * @throws CmsException if operation was not successful
10508     *
10509     * @see org.opencms.security.CmsOrgUnitManager#writeOrganizationalUnit(CmsObject, CmsOrganizationalUnit)
10510     */
10511    public void writeOrganizationalUnit(CmsDbContext dbc, CmsOrganizationalUnit organizationalUnit)
10512    throws CmsException {
10513
10514        m_monitor.uncacheOrgUnit(organizationalUnit);
10515        getUserDriver(dbc).writeOrganizationalUnit(dbc, organizationalUnit);
10516
10517        // create a publish list for the 'virtual' publish event
10518        CmsResource ouRes = readResource(dbc, organizationalUnit.getId(), CmsResourceFilter.DEFAULT);
10519        CmsPublishList pl = new CmsPublishList(ouRes, false);
10520        pl.add(ouRes, false);
10521
10522        getProjectDriver(dbc).writePublishHistory(
10523            dbc,
10524            pl.getPublishHistoryId(),
10525            new CmsPublishedResource(ouRes, -1, CmsResourceState.STATE_NEW));
10526
10527        // fire the 'virtual' publish event
10528        Map<String, Object> eventData = new HashMap<String, Object>();
10529        eventData.put(I_CmsEventListener.KEY_PUBLISHID, pl.getPublishHistoryId().toString());
10530        eventData.put(I_CmsEventListener.KEY_PROJECTID, dbc.currentProject().getUuid());
10531        eventData.put(I_CmsEventListener.KEY_DBCONTEXT, dbc);
10532        CmsEvent afterPublishEvent = new CmsEvent(I_CmsEventListener.EVENT_PUBLISH_PROJECT, eventData);
10533        OpenCms.fireCmsEvent(afterPublishEvent);
10534
10535        m_monitor.cacheOrgUnit(organizationalUnit);
10536    }
10537
10538    /**
10539     * Writes an already existing project.<p>
10540     *
10541     * The project id has to be a valid OpenCms project id.<br>
10542     *
10543     * The project with the given id will be completely overridden
10544     * by the given data.<p>
10545     *
10546     * @param dbc the current database context
10547     * @param project the project that should be written
10548     *
10549     * @throws CmsException if operation was not successful
10550     */
10551    public void writeProject(CmsDbContext dbc, CmsProject project) throws CmsException {
10552
10553        m_monitor.uncacheProject(project);
10554        getProjectDriver(dbc).writeProject(dbc, project);
10555        m_monitor.cacheProject(project);
10556    }
10557
10558    /**
10559     * Writes a new project into the PROJECT_LASTMODIFIED field of a resource record.<p>
10560     *
10561     * @param dbc the current database context
10562     * @param resource the resource which should be modified
10563     * @param projectId the project id to write
10564     *
10565     * @throws CmsDataAccessException if the database access fails
10566     */
10567    public void writeProjectLastModified(CmsDbContext dbc, CmsResource resource, CmsUUID projectId)
10568    throws CmsDataAccessException {
10569
10570        I_CmsVfsDriver vfsDriver = getVfsDriver(dbc);
10571        vfsDriver.writeLastModifiedProjectId(dbc, dbc.currentProject(), projectId, resource);
10572    }
10573
10574    /**
10575     * Writes a property for a specified resource.<p>
10576     *
10577     * @param dbc the current database context
10578     * @param resource the resource to write the property for
10579     * @param property the property to write
10580     *
10581     * @throws CmsException if something goes wrong
10582     *
10583     * @see CmsObject#writePropertyObject(String, CmsProperty)
10584     * @see I_CmsResourceType#writePropertyObject(CmsObject, CmsSecurityManager, CmsResource, CmsProperty)
10585     */
10586    public void writePropertyObject(CmsDbContext dbc, CmsResource resource, CmsProperty property) throws CmsException {
10587
10588        try {
10589            if (property == CmsProperty.getNullProperty()) {
10590                // skip empty or null properties
10591                return;
10592            }
10593
10594            // test if and what state should be updated
10595            // 0: none, 1: structure, 2: resource
10596            int updateState = getUpdateState(dbc, resource, Collections.singletonList(property));
10597
10598            // write the property
10599            getVfsDriver(dbc).writePropertyObject(dbc, dbc.currentProject(), resource, property);
10600
10601            if (updateState > 0) {
10602                updateState(dbc, resource, updateState == 2);
10603            }
10604            // log it
10605            log(
10606                dbc,
10607                new CmsLogEntry(
10608                    dbc,
10609                    resource.getStructureId(),
10610                    CmsLogEntryType.RESOURCE_PROPERTIES,
10611                    new String[] {resource.getRootPath()}),
10612                false);
10613
10614        } finally {
10615            // update the driver manager cache
10616            m_monitor.clearResourceCache();
10617            m_monitor.flushCache(CmsMemoryMonitor.CacheType.PROPERTY, CmsMemoryMonitor.CacheType.PROPERTY_LIST);
10618
10619            // fire an event that a property of a resource has been modified
10620            Map<String, Object> data = new HashMap<String, Object>();
10621            data.put(I_CmsEventListener.KEY_RESOURCE, resource);
10622            data.put("property", property);
10623            OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_PROPERTY_MODIFIED, data));
10624        }
10625    }
10626
10627    /**
10628     * Writes a list of properties for a specified resource.<p>
10629     *
10630     * Code calling this method has to ensure that the no properties
10631     * <code>a, b</code> are contained in the specified list so that <code>a.equals(b)</code>,
10632     * otherwise an exception is thrown.<p>
10633     *
10634     * @param dbc the current database context
10635     * @param resource the resource to write the properties for
10636     * @param properties the list of properties to write
10637     * @param updateState if <code>true</code> the state of the resource will be updated
10638     *
10639     * @throws CmsException if something goes wrong
10640     *
10641     * @see CmsObject#writePropertyObjects(String, List)
10642     * @see I_CmsResourceType#writePropertyObjects(CmsObject, CmsSecurityManager, CmsResource, List)
10643     */
10644    public void writePropertyObjects(
10645        CmsDbContext dbc,
10646        CmsResource resource,
10647        List<CmsProperty> properties,
10648        boolean updateState)
10649    throws CmsException {
10650
10651        if ((properties == null) || (properties.size() == 0)) {
10652            // skip empty or null lists
10653            return;
10654        }
10655
10656        try {
10657            // the specified list must not contain two or more equal property objects
10658            for (int i = 0, n = properties.size(); i < n; i++) {
10659                Set<String> keyValidationSet = new HashSet<String>();
10660                CmsProperty property = properties.get(i);
10661                if (!keyValidationSet.contains(property.getName())) {
10662                    keyValidationSet.add(property.getName());
10663                } else {
10664                    throw new CmsVfsException(
10665                        Messages.get().container(Messages.ERR_VFS_INVALID_PROPERTY_LIST_1, property.getName()));
10666                }
10667            }
10668
10669            // test if and what state should be updated
10670            // 0: none, 1: structure, 2: resource
10671            int updateStateValue = 0;
10672            if (updateState) {
10673                updateStateValue = getUpdateState(dbc, resource, properties);
10674            }
10675            I_CmsVfsDriver vfsDriver = getVfsDriver(dbc);
10676            for (int i = 0; i < properties.size(); i++) {
10677                // write the property
10678                CmsProperty property = properties.get(i);
10679                vfsDriver.writePropertyObject(dbc, dbc.currentProject(), resource, property);
10680            }
10681
10682            if (updateStateValue > 0) {
10683                // update state
10684                updateState(dbc, resource, (updateStateValue == 2));
10685            }
10686
10687            if (updateState) {
10688                // log it
10689                log(
10690                    dbc,
10691                    new CmsLogEntry(
10692                        dbc,
10693                        resource.getStructureId(),
10694                        CmsLogEntryType.RESOURCE_PROPERTIES,
10695                        new String[] {resource.getRootPath()}),
10696                    false);
10697            }
10698        } finally {
10699            // update the driver manager cache
10700            m_monitor.clearResourceCache();
10701            m_monitor.flushCache(CmsMemoryMonitor.CacheType.PROPERTY, CmsMemoryMonitor.CacheType.PROPERTY_LIST);
10702
10703            // fire an event that the properties of a resource have been modified
10704            OpenCms.fireCmsEvent(
10705                new CmsEvent(
10706                    I_CmsEventListener.EVENT_RESOURCE_AND_PROPERTIES_MODIFIED,
10707                    Collections.<String, Object> singletonMap(I_CmsEventListener.KEY_RESOURCE, resource)));
10708        }
10709    }
10710
10711    /**
10712     * Updates a publish job.<p>
10713     *
10714     * @param dbc the current database context
10715     * @param publishJob the publish job to update
10716     *
10717     * @throws CmsException if something goes wrong
10718     */
10719    public void writePublishJob(CmsDbContext dbc, CmsPublishJobInfoBean publishJob) throws CmsException {
10720
10721        getProjectDriver(dbc).writePublishJob(dbc, publishJob);
10722    }
10723
10724    /**
10725     * Writes the publish report for a publish job.<p>
10726     *
10727     * @param dbc the current database context
10728     * @param publishJob the publish job
10729     * @throws CmsException if something goes wrong
10730     */
10731    public void writePublishReport(CmsDbContext dbc, CmsPublishJobInfoBean publishJob) throws CmsException {
10732
10733        CmsPublishReport report = (CmsPublishReport)publishJob.removePublishReport();
10734
10735        if (report != null) {
10736            getProjectDriver(dbc).writePublishReport(dbc, publishJob.getPublishHistoryId(), report.getContents());
10737        }
10738    }
10739
10740    /**
10741     * Writes a resource to the OpenCms VFS.<p>
10742     *
10743     * @param dbc the current database context
10744     * @param resource the resource to write
10745     *
10746     * @throws CmsException if something goes wrong
10747     */
10748    public void writeResource(CmsDbContext dbc, CmsResource resource) throws CmsException {
10749
10750        // access was granted - write the resource
10751        resource.setUserLastModified(dbc.currentUser().getId());
10752        CmsUUID projectId = ((dbc.getProjectId() == null) || dbc.getProjectId().isNullUUID())
10753        ? dbc.currentProject().getUuid()
10754        : dbc.getProjectId();
10755
10756        getVfsDriver(dbc).writeResource(dbc, projectId, resource, UPDATE_RESOURCE_STATE);
10757
10758        // make sure the written resource has the state correctly set
10759        if (resource.getState().isUnchanged()) {
10760            resource.setState(CmsResource.STATE_CHANGED);
10761        }
10762
10763        // delete in content relations if the new type is not parseable
10764        if (!(OpenCms.getResourceManager().getResourceType(resource.getTypeId()) instanceof I_CmsLinkParseable)) {
10765            deleteRelationsWithSiblings(dbc, resource);
10766        }
10767
10768        // update the cache
10769        m_monitor.clearResourceCache();
10770        Map<String, Object> data = new HashMap<String, Object>(2);
10771        data.put(I_CmsEventListener.KEY_RESOURCE, resource);
10772        data.put(I_CmsEventListener.KEY_CHANGE, Integer.valueOf(CHANGED_RESOURCE));
10773        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_MODIFIED, data));
10774    }
10775
10776    /**
10777     * Inserts an entry in the published resource table.<p>
10778     *
10779     * This is done during static export.<p>
10780     *
10781     * @param dbc the current database context
10782     * @param resourceName The name of the resource to be added to the static export
10783     * @param linkType the type of resource exported (0= non-parameter, 1=parameter)
10784     * @param linkParameter the parameters added to the resource
10785     * @param timestamp a time stamp for writing the data into the db
10786     *
10787     * @throws CmsException if something goes wrong
10788     */
10789    public void writeStaticExportPublishedResource(
10790        CmsDbContext dbc,
10791        String resourceName,
10792        int linkType,
10793        String linkParameter,
10794        long timestamp)
10795    throws CmsException {
10796
10797        getProjectDriver(dbc).writeStaticExportPublishedResource(dbc, resourceName, linkType, linkParameter, timestamp);
10798    }
10799
10800    /**
10801     * Adds a new url name mapping for a structure id.<p>
10802     *
10803     * Instead of taking the name directly, this method takes an iterator of strings
10804     * which generates candidate URL names on-the-fly. The first generated name which is
10805     * not already mapped to another structure id will be chosen for the new URL name mapping.
10806     *
10807     * @param dbc the current database context
10808     * @param nameSeq the sequence of URL name candidates
10809     * @param structureId the structure id to which the url name should be mapped
10810     * @param locale the locale for which the mapping should be written
10811     * @param replaceOnPublish name mappings for which this is set will replace all other mappings for the same resource on publishing
10812     *
10813     * @return the actual name which was mapped to the structure id
10814     *
10815     * @throws CmsDataAccessException if something goes wrong
10816     */
10817    public String writeUrlNameMapping(
10818        CmsDbContext dbc,
10819        Iterator<String> nameSeq,
10820        CmsUUID structureId,
10821        String locale,
10822        boolean replaceOnPublish)
10823    throws CmsDataAccessException {
10824
10825        String bestName = findBestNameForUrlNameMapping(dbc, nameSeq, structureId, locale);
10826        addOrReplaceUrlNameMapping(dbc, bestName, structureId, locale, replaceOnPublish);
10827        return bestName;
10828    }
10829
10830    /**
10831     * Updates the user information. <p>
10832     *
10833     * The user id has to be a valid OpenCms user id.<br>
10834     *
10835     * The user with the given id will be completely overridden
10836     * by the given data.<p>
10837     *
10838     * @param dbc the current database context
10839     * @param user the user to be updated
10840     *
10841     * @throws CmsException if operation was not successful
10842     */
10843    public void writeUser(CmsDbContext dbc, CmsUser user) throws CmsException {
10844
10845        CmsUser oldUser = readUser(dbc, user.getId());
10846        m_monitor.clearUserCache(oldUser);
10847        getUserDriver(dbc).writeUser(dbc, user);
10848        m_monitor.flushCache(CmsMemoryMonitor.CacheType.USER_LIST);
10849
10850        if (!dbc.getProjectId().isNullUUID()) {
10851            // user modified event is not needed
10852            return;
10853        }
10854        // fire user modified event
10855        Map<String, Object> eventData = new HashMap<String, Object>();
10856        eventData.put(I_CmsEventListener.KEY_USER_ID, user.getId().toString());
10857        eventData.put(I_CmsEventListener.KEY_USER_NAME, oldUser.getName());
10858        eventData.put(I_CmsEventListener.KEY_USER_ACTION, I_CmsEventListener.VALUE_USER_MODIFIED_ACTION_WRITE_USER);
10859        eventData.put(I_CmsEventListener.KEY_USER_CHANGES, Integer.valueOf(user.getChanges(oldUser)));
10860        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_USER_MODIFIED, eventData));
10861        OpenCms.getTwoFactorAuthenticationHandler().trackUserChange(dbc.getRequestContext(), oldUser, user);
10862    }
10863
10864    /**
10865     * Adds or replaces a new url name mapping in the offline project.<p>
10866     *
10867     * @param dbc the current database context
10868     * @param name the URL name of the mapping
10869     * @param structureId the structure id of the mapping
10870     * @param locale the locale of the mapping
10871     * @param replaceOnPublish if the mapping shoudl replace previous URL name mappings when published
10872     *
10873     * @throws CmsDataAccessException if something goes wrong
10874     */
10875    protected void addOrReplaceUrlNameMapping(
10876        CmsDbContext dbc,
10877        String name,
10878        CmsUUID structureId,
10879        String locale,
10880        boolean replaceOnPublish)
10881    throws CmsDataAccessException {
10882
10883        getVfsDriver(dbc).deleteUrlNameMappingEntries(
10884            dbc,
10885            false,
10886            CmsUrlNameMappingFilter.ALL.filterStructureId(structureId).filterLocale(locale).filterStates(
10887                CmsUrlNameMappingEntry.MAPPING_STATUS_NEW,
10888                CmsUrlNameMappingEntry.MAPPING_STATUS_REPLACE_ON_PUBLISH));
10889        CmsUrlNameMappingEntry newEntry = new CmsUrlNameMappingEntry(
10890            name,
10891            structureId,
10892            replaceOnPublish
10893            ? CmsUrlNameMappingEntry.MAPPING_STATUS_REPLACE_ON_PUBLISH
10894            : CmsUrlNameMappingEntry.MAPPING_STATUS_NEW,
10895            System.currentTimeMillis(),
10896            locale);
10897        getVfsDriver(dbc).addUrlNameMappingEntry(dbc, false, newEntry);
10898    }
10899
10900    /**
10901     * Converts a resource to a folder (if possible).<p>
10902     *
10903     * @param resource the resource to convert
10904     * @return the converted resource
10905     *
10906     * @throws CmsVfsResourceNotFoundException if the resource is not a folder
10907     */
10908    protected CmsFolder convertResourceToFolder(CmsResource resource) throws CmsVfsResourceNotFoundException {
10909
10910        if (resource.isFolder()) {
10911            return new CmsFolder(resource);
10912        }
10913
10914        throw new CmsVfsResourceNotFoundException(
10915            Messages.get().container(Messages.ERR_ACCESS_FILE_AS_FOLDER_1, resource.getRootPath()));
10916    }
10917
10918    /**
10919     * Helper method for creating a driver from configuration data.<p>
10920     *
10921     * @param dbc the db context
10922     * @param configManager the configuration manager
10923     * @param config the configuration
10924     * @param driverChainKey the configuration key under which the driver chain is stored
10925     * @param suffix the suffix to append to a driver chain entry to get the key for the driver class
10926     *
10927     * @return the newly created driver
10928     */
10929    protected Object createDriver(
10930        CmsDbContext dbc,
10931        CmsConfigurationManager configManager,
10932        CmsParameterConfiguration config,
10933        String driverChainKey,
10934        String suffix) {
10935
10936        // read the vfs driver class properties and initialize a new instance
10937        List<String> drivers = config.getList(driverChainKey);
10938        String driverKey = drivers.get(0) + suffix;
10939        String driverName = config.get(driverKey);
10940        drivers = (drivers.size() > 1) ? drivers.subList(1, drivers.size()) : null;
10941        if (driverName == null) {
10942            CmsLog.INIT.error(Messages.get().getBundle().key(Messages.INIT_DRIVER_FAILED_1, driverKey));
10943        }
10944        Object result = newDriverInstance(dbc, configManager, driverName, drivers);
10945        if ("true".equalsIgnoreCase(System.getProperty("opencms.profile.drivers"))) {
10946            result = wrapDriverInProfilingProxy(result);
10947        }
10948        return result;
10949    }
10950
10951    /**
10952     * Deletes all relations for the given resource and all its siblings.<p>
10953     *
10954     * @param dbc the current database context
10955     * @param resource the resource to delete the resource for
10956     *
10957     * @throws CmsException if something goes wrong
10958     */
10959    protected void deleteRelationsWithSiblings(CmsDbContext dbc, CmsResource resource) throws CmsException {
10960
10961        // get all siblings
10962        List<CmsResource> siblings;
10963        if (resource.getSiblingCount() > 1) {
10964            siblings = readSiblings(dbc, resource, CmsResourceFilter.ALL);
10965        } else {
10966            siblings = new ArrayList<CmsResource>();
10967            siblings.add(resource);
10968        }
10969        // clean the relations in content for all siblings
10970        I_CmsVfsDriver vfsDriver = getVfsDriver(dbc);
10971        Iterator<CmsResource> it = siblings.iterator();
10972        while (it.hasNext()) {
10973            CmsResource sibling = it.next();
10974            // clean the relation information for this sibling
10975            vfsDriver.deleteRelations(
10976                dbc,
10977                dbc.currentProject().getUuid(),
10978                sibling,
10979                CmsRelationFilter.TARGETS.filterDefinedInContent());
10980        }
10981    }
10982
10983    /**
10984     * Tries to add sub-resources of moved folders to the publish list and throws an exception if the publish list still does
10985     * not contain some  sub-resources of the moved folders.<p>
10986     *
10987     * @param cms the current CMS context
10988     * @param dbc the current database context
10989     * @param pubList the publish list
10990     * @throws CmsException if something goes wrong
10991     */
10992    protected void ensureSubResourcesOfMovedFoldersPublished(CmsObject cms, CmsDbContext dbc, CmsPublishList pubList)
10993    throws CmsException {
10994
10995        List<CmsResource> topMovedFolders = pubList.getTopMovedFolders(cms);
10996        Iterator<CmsResource> folderIt = topMovedFolders.iterator();
10997        while (folderIt.hasNext()) {
10998            CmsResource folder = folderIt.next();
10999            addSubResources(dbc, pubList, folder, resource -> !resource.getState().isNew());
11000        }
11001        List<CmsResource> missingSubResources = pubList.getMissingSubResources(cms, topMovedFolders);
11002        if (missingSubResources.isEmpty()) {
11003            return;
11004        }
11005
11006        StringBuffer pathBuffer = new StringBuffer();
11007
11008        for (CmsResource missing : missingSubResources) {
11009            pathBuffer.append(missing.getRootPath());
11010            pathBuffer.append(" ");
11011        }
11012        throw new CmsVfsException(
11013            Messages.get().container(Messages.RPT_CHILDREN_OF_MOVED_FOLDER_NOT_PUBLISHED_1, pathBuffer.toString()));
11014
11015    }
11016
11017    /**
11018     * Tries to find the best name for an URL name mapping for the given structure id.<p>
11019     *
11020     * @param dbc the database context
11021     * @param nameSeq the sequence of name candidates
11022     * @param structureId the structure id to which an URL name should be mapped
11023     * @param locale the locale for which the URL name should be mapped
11024     *
11025     * @return the selected URL name candidate
11026     *
11027     * @throws CmsDataAccessException if something goes wrong
11028     */
11029    protected String findBestNameForUrlNameMapping(
11030        CmsDbContext dbc,
11031        Iterator<String> nameSeq,
11032        CmsUUID structureId,
11033        String locale)
11034    throws CmsDataAccessException {
11035
11036        String newName;
11037        boolean alreadyInUse;
11038        do {
11039            newName = nameSeq.next();
11040            alreadyInUse = false;
11041            CmsUrlNameMappingFilter filter = CmsUrlNameMappingFilter.ALL.filterName(newName);
11042            List<CmsUrlNameMappingEntry> entriesWithSameName = getVfsDriver(dbc).readUrlNameMappingEntries(
11043                dbc,
11044                false,
11045                filter);
11046            for (CmsUrlNameMappingEntry entry : entriesWithSameName) {
11047                boolean sameId = entry.getStructureId().equals(structureId);
11048                if (!sameId) {
11049                    // name already used for other resource, or for different locale of the same resource
11050                    alreadyInUse = true;
11051                    break;
11052                }
11053            }
11054        } while (alreadyInUse);
11055        return newName;
11056    }
11057
11058    /**
11059     * Helper method for finding the 'best' URL name to use for a new URL name mapping.<p>
11060     *
11061     * Since the name given as a parameter may be already used, this method will try to append numeric suffixes
11062     * to the name to find a mapping name which is not used.<p>
11063     *
11064     * @param dbc the current database context
11065     * @param name the name of the mapping
11066     * @param structureId the structure id to which the name is mapped
11067     *
11068     * @return the best name which was found for the new mapping
11069     *
11070     * @throws CmsDataAccessException if something goes wrong
11071     */
11072    protected String findBestNameForUrlNameMapping(CmsDbContext dbc, String name, CmsUUID structureId)
11073    throws CmsDataAccessException {
11074
11075        List<CmsUrlNameMappingEntry> entriesStartingWithName = getVfsDriver(dbc).readUrlNameMappingEntries(
11076            dbc,
11077            false,
11078            CmsUrlNameMappingFilter.ALL.filterNamePattern(name + "%").filterRejectStructureId(structureId));
11079        Set<String> usedNames = new HashSet<String>();
11080        for (CmsUrlNameMappingEntry entry : entriesStartingWithName) {
11081            usedNames.add(entry.getName());
11082        }
11083        int counter = 0;
11084        String numberedName;
11085        do {
11086            numberedName = getNumberedName(name, counter);
11087            counter += 1;
11088        } while (usedNames.contains(numberedName));
11089        return numberedName;
11090    }
11091
11092    /**
11093     * Returns the lock manager instance.<p>
11094     *
11095     * @return the lock manager instance
11096     */
11097    protected CmsLockManager getLockManager() {
11098
11099        return m_lockManager;
11100    }
11101
11102    /**
11103     * Adds a numeric suffix to the end of a string, unless the number passed as a parameter is 0.<p>
11104     *
11105     * @param name the base name
11106     * @param number the number from which to form the suffix
11107     *
11108     * @return the concatenation of the base name and possibly the numeric suffix
11109     */
11110    protected String getNumberedName(String name, int number) {
11111
11112        if (number == 0) {
11113            return name;
11114        }
11115        PrintfFormat fmt = new PrintfFormat("%0.6d");
11116        return name + "_" + fmt.sprintf(number);
11117    }
11118
11119    /**
11120     * Resets the resources in a project to their online state.<p>
11121     *
11122     * @param dbc the database context
11123     * @param projectId the project id
11124     * @param modifiedFiles the modified files
11125     * @param modifiedFolders the modified folders
11126     * @throws CmsException if something goes wrong
11127     * @throws CmsSecurityException if we don't have the permissions
11128     * @throws CmsDataAccessException if something goes wrong with the database
11129     */
11130    protected void resetResourcesInProject(
11131        CmsDbContext dbc,
11132        CmsUUID projectId,
11133        List<CmsResource> modifiedFiles,
11134        List<CmsResource> modifiedFolders)
11135    throws CmsException, CmsSecurityException, CmsDataAccessException {
11136
11137        // all resources inside the project have to be be reset to their online state.
11138        // 1. step: delete all new files
11139        for (int i = 0; i < modifiedFiles.size(); i++) {
11140            CmsResource currentFile = modifiedFiles.get(i);
11141            if (currentFile.getState().isNew()) {
11142                CmsLock lock = getLock(dbc, currentFile);
11143                if (lock.isNullLock()) {
11144                    // lock the resource
11145                    lockResource(dbc, currentFile, CmsLockType.EXCLUSIVE);
11146                } else if (!lock.isOwnedBy(dbc.currentUser()) || !lock.isInProject(dbc.currentProject())) {
11147                    changeLock(dbc, currentFile, CmsLockType.EXCLUSIVE);
11148                }
11149                // delete the properties
11150                getVfsDriver(dbc).deletePropertyObjects(
11151                    dbc,
11152                    projectId,
11153                    currentFile,
11154                    CmsProperty.DELETE_OPTION_DELETE_STRUCTURE_AND_RESOURCE_VALUES);
11155                // delete the file
11156                getVfsDriver(dbc).removeFile(dbc, dbc.currentProject().getUuid(), currentFile);
11157                // remove the access control entries
11158                getUserDriver(dbc).removeAccessControlEntries(dbc, dbc.currentProject(), currentFile.getResourceId());
11159                // fire the corresponding event
11160                OpenCms.fireCmsEvent(
11161                    new CmsEvent(
11162                        I_CmsEventListener.EVENT_RESOURCE_AND_PROPERTIES_MODIFIED,
11163                        Collections.<String, Object> singletonMap(I_CmsEventListener.KEY_RESOURCE, currentFile)));
11164            }
11165        }
11166
11167        // 2. step: delete all new folders
11168        for (int i = 0; i < modifiedFolders.size(); i++) {
11169            CmsResource currentFolder = modifiedFolders.get(i);
11170            if (currentFolder.getState().isNew()) {
11171                // delete the properties
11172                getVfsDriver(dbc).deletePropertyObjects(
11173                    dbc,
11174                    projectId,
11175                    currentFolder,
11176                    CmsProperty.DELETE_OPTION_DELETE_STRUCTURE_AND_RESOURCE_VALUES);
11177                // delete the folder
11178                getVfsDriver(dbc).removeFolder(dbc, dbc.currentProject(), currentFolder);
11179                // remove the access control entries
11180                getUserDriver(dbc).removeAccessControlEntries(dbc, dbc.currentProject(), currentFolder.getResourceId());
11181                // fire the corresponding event
11182                OpenCms.fireCmsEvent(
11183                    new CmsEvent(
11184                        I_CmsEventListener.EVENT_RESOURCE_AND_PROPERTIES_MODIFIED,
11185                        Collections.<String, Object> singletonMap(I_CmsEventListener.KEY_RESOURCE, currentFolder)));
11186            }
11187        }
11188
11189        // 3. step: undo changes on all changed or deleted folders
11190        for (int i = 0; i < modifiedFolders.size(); i++) {
11191            CmsResource currentFolder = modifiedFolders.get(i);
11192            if ((currentFolder.getState().isChanged()) || (currentFolder.getState().isDeleted())) {
11193                CmsLock lock = getLock(dbc, currentFolder);
11194                if (lock.isNullLock()) {
11195                    // lock the resource
11196                    lockResource(dbc, currentFolder, CmsLockType.EXCLUSIVE);
11197                } else if (!lock.isOwnedBy(dbc.currentUser()) || !lock.isInProject(dbc.currentProject())) {
11198                    changeLock(dbc, currentFolder, CmsLockType.EXCLUSIVE);
11199                }
11200                // undo all changes in the folder
11201                undoChanges(dbc, currentFolder, CmsResource.UNDO_CONTENT);
11202                // fire the corresponding event
11203                OpenCms.fireCmsEvent(
11204                    new CmsEvent(
11205                        I_CmsEventListener.EVENT_RESOURCE_AND_PROPERTIES_MODIFIED,
11206                        Collections.<String, Object> singletonMap(I_CmsEventListener.KEY_RESOURCE, currentFolder)));
11207            }
11208        }
11209
11210        // 4. step: undo changes on all changed or deleted files
11211        for (int i = 0; i < modifiedFiles.size(); i++) {
11212            CmsResource currentFile = modifiedFiles.get(i);
11213            if (currentFile.getState().isChanged() || currentFile.getState().isDeleted()) {
11214                CmsLock lock = getLock(dbc, currentFile);
11215                if (lock.isNullLock()) {
11216                    // lock the resource
11217                    lockResource(dbc, currentFile, CmsLockType.EXCLUSIVE);
11218                } else if (!lock.isOwnedInProjectBy(dbc.currentUser(), dbc.currentProject())) {
11219                    if (lock.isLockableBy(dbc.currentUser())) {
11220                        changeLock(dbc, currentFile, CmsLockType.EXCLUSIVE);
11221                    }
11222                }
11223                // undo all changes in the file
11224                undoChanges(dbc, currentFile, CmsResource.UNDO_CONTENT);
11225                // fire the corresponding event
11226                OpenCms.fireCmsEvent(
11227                    new CmsEvent(
11228                        I_CmsEventListener.EVENT_RESOURCE_AND_PROPERTIES_MODIFIED,
11229                        Collections.<String, Object> singletonMap(I_CmsEventListener.KEY_RESOURCE, currentFile)));
11230            }
11231        }
11232    }
11233
11234    /**
11235     * Counts the total number of users which fit the given criteria.<p>
11236     *
11237     * @param dbc the database context
11238     * @param searchParams the user search criteria
11239     *
11240     * @return the total number of users matching the criteria
11241     *
11242     * @throws CmsDataAccessException if something goes wrong
11243     */
11244    long countUsers(CmsDbContext dbc, CmsUserSearchParameters searchParams) throws CmsDataAccessException {
11245
11246        return getUserDriver(dbc).countUsers(dbc, searchParams);
11247    }
11248
11249    /**
11250     * Adds a pool to the static pool map.<p>
11251     *
11252     * @param pool the pool to add
11253     */
11254    private void addPool(CmsDbPoolV11 pool) {
11255
11256        m_pools.put(pool.getPoolUrl(), pool);
11257    }
11258
11259    /**
11260     * Adds all sub-resources of the given resource to the publish list.<p>
11261     *
11262     * @param dbc the database context
11263     * @param publishList the publish list
11264     * @param directPublishResource the resource to get the sub-resources for
11265     * @param additionalFilter an additional test for resources to pass before they are added to the publish list
11266     *
11267     * @throws CmsDataAccessException if something goes wrong accessing the database
11268     */
11269    private void addSubResources(
11270        CmsDbContext dbc,
11271        CmsPublishList publishList,
11272        CmsResource directPublishResource,
11273        Predicate<CmsResource> additionalFilter)
11274    throws CmsDataAccessException {
11275
11276        int flags = CmsDriverManager.READMODE_INCLUDE_TREE | CmsDriverManager.READMODE_EXCLUDE_STATE;
11277        if (!directPublishResource.getState().isDeleted()) {
11278            // fix for org.opencms.file.TestPublishIssues#testPublishFolderWithDeletedFileFromOtherProject
11279            flags = flags | CmsDriverManager.READMODE_INCLUDE_PROJECT;
11280        }
11281
11282        // add all sub resources of the folder
11283        List<CmsResource> folderList = getVfsDriver(dbc).readResourceTree(
11284            dbc,
11285            dbc.currentProject().getUuid(),
11286            directPublishResource.getRootPath(),
11287            CmsDriverManager.READ_IGNORE_TYPE,
11288            CmsResource.STATE_UNCHANGED,
11289            CmsDriverManager.READ_IGNORE_TIME,
11290            CmsDriverManager.READ_IGNORE_TIME,
11291            CmsDriverManager.READ_IGNORE_TIME,
11292            CmsDriverManager.READ_IGNORE_TIME,
11293            CmsDriverManager.READ_IGNORE_TIME,
11294            CmsDriverManager.READ_IGNORE_TIME,
11295            flags | CmsDriverManager.READMODE_ONLY_FOLDERS);
11296
11297        publishList.addAll(
11298            filterResources(dbc, publishList, folderList).stream().filter(additionalFilter).collect(
11299                Collectors.toList()),
11300            true);
11301
11302        List<CmsResource> fileList = getVfsDriver(dbc).readResourceTree(
11303            dbc,
11304            dbc.currentProject().getUuid(),
11305            directPublishResource.getRootPath(),
11306            CmsDriverManager.READ_IGNORE_TYPE,
11307            CmsResource.STATE_UNCHANGED,
11308            CmsDriverManager.READ_IGNORE_TIME,
11309            CmsDriverManager.READ_IGNORE_TIME,
11310            CmsDriverManager.READ_IGNORE_TIME,
11311            CmsDriverManager.READ_IGNORE_TIME,
11312            CmsDriverManager.READ_IGNORE_TIME,
11313            CmsDriverManager.READ_IGNORE_TIME,
11314            flags | CmsDriverManager.READMODE_ONLY_FILES);
11315
11316        publishList.addAll(
11317            filterResources(dbc, publishList, fileList).stream().filter(additionalFilter).collect(Collectors.toList()),
11318            true);
11319    }
11320
11321    /**
11322     * Helper method to check whether we should bother with reading the group for a given role in a given OU.<p>
11323     *
11324     * 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.
11325     *
11326     * @param ou the OU
11327     * @param role the role
11328     * @return true if we should read the role in the OU
11329     */
11330    private boolean canReadRoleInOu(CmsOrganizationalUnit ou, CmsRole role) {
11331
11332        if (ou.hasFlagWebuser() && !role.getRoleName().equals(CmsRole.ACCOUNT_MANAGER.getRoleName())) {
11333            return false;
11334        }
11335        return true;
11336    }
11337
11338    /**
11339     * Checks the parent of a resource during publishing.<p>
11340     *
11341     * @param dbc the current database context
11342     * @param deletedFolders a list of deleted folders
11343     * @param res a resource to check the parent for
11344     *
11345     * @return <code>true</code> if the parent resource will be deleted during publishing
11346     */
11347    private boolean checkDeletedParentFolder(CmsDbContext dbc, List<CmsResource> deletedFolders, CmsResource res) {
11348
11349        String parentPath = CmsResource.getParentFolder(res.getRootPath());
11350
11351        if (parentPath == null) {
11352            // resource has no parent
11353            return false;
11354        }
11355
11356        CmsResource parent;
11357        try {
11358            parent = readResource(dbc, parentPath, CmsResourceFilter.ALL);
11359        } catch (Exception e) {
11360            // failure: if we cannot read the parent, we should not publish the resource
11361            return false;
11362        }
11363
11364        if (!parent.getState().isDeleted()) {
11365            // parent is not deleted
11366            return false;
11367        }
11368
11369        for (int j = 0; j < deletedFolders.size(); j++) {
11370            if ((deletedFolders.get(j)).getStructureId().equals(parent.getStructureId())) {
11371                // parent is deleted, and it will get published
11372                return true;
11373            }
11374        }
11375
11376        // parent is new, but it will not get published
11377        return false;
11378    }
11379
11380    /**
11381     * Checks that no one of the resources to be published has a 'new' parent (that has not been published yet).<p>
11382     *
11383     * @param dbc the db context
11384     * @param publishList the publish list to check
11385     *
11386     * @throws CmsVfsException if there is a resource to be published with a 'new' parent
11387     */
11388    private void checkParentFolders(CmsDbContext dbc, CmsPublishList publishList) throws CmsVfsException {
11389
11390        boolean directPublish = publishList.isDirectPublish();
11391        // if we direct publish a file, check if all parent folders are already published
11392        if (directPublish) {
11393            // first get the names of all parent folders
11394            Iterator<CmsResource> it = publishList.getDirectPublishResources().iterator();
11395            List<String> parentFolderNames = new ArrayList<String>();
11396            while (it.hasNext()) {
11397                CmsResource res = it.next();
11398                String parentFolderName = CmsResource.getParentFolder(res.getRootPath());
11399                if (parentFolderName != null) {
11400                    parentFolderNames.add(parentFolderName);
11401                }
11402            }
11403            // remove duplicate parent folder names
11404            parentFolderNames = CmsFileUtil.removeRedundancies(parentFolderNames);
11405            String parentFolderName = null;
11406            try {
11407                I_CmsVfsDriver vfsDriver = getVfsDriver(dbc);
11408                // now check all folders if they exist in the online project
11409                Iterator<String> parentIt = parentFolderNames.iterator();
11410                while (parentIt.hasNext()) {
11411                    parentFolderName = parentIt.next();
11412                    vfsDriver.readFolder(dbc, CmsProject.ONLINE_PROJECT_ID, parentFolderName);
11413                }
11414            } catch (CmsException e) {
11415                throw new CmsVfsException(
11416                    Messages.get().container(Messages.RPT_PARENT_FOLDER_NOT_PUBLISHED_1, parentFolderName));
11417            }
11418        }
11419    }
11420
11421    /**
11422     * Checks the parent of a resource during publishing.<p>
11423     *
11424     * @param dbc the current database context
11425     * @param folderList a list of folders
11426     * @param res a resource to check the parent for
11427     *
11428     * @return true if the resource should be published
11429     */
11430    private boolean checkParentResource(CmsDbContext dbc, List<CmsResource> folderList, CmsResource res) {
11431
11432        String parentPath = CmsResource.getParentFolder(res.getRootPath());
11433
11434        if (parentPath == null) {
11435            // resource has no parent
11436            return true;
11437        }
11438
11439        CmsResource parent;
11440        try {
11441            parent = readResource(dbc, parentPath, CmsResourceFilter.ALL);
11442        } catch (Exception e) {
11443            // failure: if we cannot read the parent, we should not publish the resource
11444            return false;
11445        }
11446
11447        if (!parent.getState().isNew()) {
11448            // parent is already published
11449            return true;
11450        }
11451
11452        for (int j = 0; j < folderList.size(); j++) {
11453            if (folderList.get(j).getStructureId().equals(parent.getStructureId())) {
11454                // parent is new, but it will get published
11455                return true;
11456            }
11457        }
11458
11459        // parent is new, but it will not get published
11460        return false;
11461    }
11462
11463    /**
11464     * Copies all relations from the source resource to the target resource.<p>
11465     *
11466     * @param dbc the database context
11467     * @param source the source
11468     * @param target the target
11469     *
11470     * @throws CmsException if something goes wrong
11471     */
11472    private void copyRelations(CmsDbContext dbc, CmsResource source, CmsResource target) throws CmsException {
11473
11474        // copy relations all relations
11475        CmsObject cms = new CmsObject(getSecurityManager(), dbc.getRequestContext());
11476        Iterator<CmsRelation> itRelations = getRelationsForResource(
11477            dbc,
11478            source,
11479            CmsRelationFilter.TARGETS.filterNotDefinedInContent()).iterator();
11480        while (itRelations.hasNext()) {
11481            CmsRelation relation = itRelations.next();
11482            if (relation.getType().getCopyBehavior() == CopyBehavior.copy) {
11483                try {
11484                    CmsResource relTarget = relation.getTarget(cms, CmsResourceFilter.ALL);
11485                    addRelationToResource(dbc, target, relTarget, relation.getType(), true);
11486                } catch (CmsVfsResourceNotFoundException e) {
11487                    // ignore this broken relation
11488                    if (LOG.isWarnEnabled()) {
11489                        LOG.warn(e.getLocalizedMessage(), e);
11490                    }
11491                }
11492            }
11493        }
11494        // repair categories
11495        repairCategories(dbc, getProjectIdForContext(dbc), target);
11496    }
11497
11498    /**
11499     * Filters the given list of resources, removes all resources where the current user
11500     * does not have READ permissions, plus the filter is applied.<p>
11501     *
11502     * @param dbc the current database context
11503     * @param resourceList a list of CmsResources
11504     * @param filter the resource filter to use
11505     *
11506     * @return the filtered list of resources
11507     *
11508     * @throws CmsException in case errors testing the permissions
11509     */
11510    private List<CmsResource> filterPermissions(
11511        CmsDbContext dbc,
11512        List<CmsResource> resourceList,
11513        CmsResourceFilter filter)
11514    throws CmsException {
11515
11516        if (filter.requireTimerange()) {
11517            // never check time range here - this must be done later in #updateContextDates(...)
11518            filter = filter.addExcludeTimerange();
11519        }
11520        ResourceListWithCacheability result = new ResourceListWithCacheability();
11521        boolean nocacheWasSet = false;
11522        if (null == dbc.getAttribute(ATTR_PERMISSION_NOCACHE)) {
11523            // The attribute will be used by the permission handler to tell us that lists containing the resource
11524            // should not be cached.
11525            dbc.setAttribute(ATTR_PERMISSION_NOCACHE, new boolean[] {false});
11526            // insurance against potential indirect recursive calls introduced by future code changes:
11527            // make sure we only remove the attribute later if we were the one who set it
11528            nocacheWasSet = true;
11529        }
11530        try {
11531            for (int i = 0; i < resourceList.size(); i++) {
11532                // check the permission of all resources
11533                CmsResource currentResource = resourceList.get(i);
11534                if (m_securityManager.hasPermissions(
11535                    dbc,
11536                    currentResource,
11537                    CmsPermissionSet.ACCESS_READ,
11538                    LockCheck.yes,
11539                    filter).isAllowed()) {
11540                    // only return resources where permission was granted
11541                    result.add(currentResource);
11542                }
11543            }
11544        } finally {
11545            if (nocacheWasSet) {
11546                boolean[] nocache = (boolean[])dbc.getAttribute(ATTR_PERMISSION_NOCACHE);
11547                if (nocache != null) {
11548                    dbc.removeAttribute(ATTR_PERMISSION_NOCACHE);
11549                    if (nocache[0]) {
11550                        result.setCacheable(false);
11551                    }
11552                }
11553            }
11554        }
11555        // return the result
11556        return result;
11557    }
11558
11559    /**
11560     * Returns a filtered list of resources for publishing.<p>
11561     * Contains all resources, which are not locked
11562     * and which have a parent folder that is already published or will be published, too.<p>
11563     *
11564     * @param dbc the current database context
11565     * @param publishList the filling publish list
11566     * @param resourceList the list of resources to filter
11567     *
11568     * @return a filtered list of resources
11569     */
11570    private List<CmsResource> filterResources(
11571        CmsDbContext dbc,
11572        CmsPublishList publishList,
11573        List<CmsResource> resourceList) {
11574
11575        List<CmsResource> result = new ArrayList<CmsResource>();
11576
11577        // local folder list for adding new publishing subfolders
11578        // this solves the {@link org.opencms.file.TestPublishIssues#testPublishScenarioD} problem.
11579        List<CmsResource> newFolderList = new ArrayList<CmsResource>(
11580            publishList == null ? resourceList : publishList.getFolderList());
11581
11582        for (int i = 0; i < resourceList.size(); i++) {
11583            CmsResource res = resourceList.get(i);
11584            try {
11585                CmsLock lock = getLock(dbc, res);
11586                if (lock.isPublish()) {
11587                    // if already enqueued
11588                    continue;
11589                }
11590                if (!lock.isLockableBy(dbc.currentUser())) {
11591                    // checks if there is a shared lock and if the resource is deleted
11592                    // this solves the {@link org.opencms.file.TestPublishIssues#testPublishScenarioE} problem.
11593                    if (lock.isShared() && (publishList != null)) {
11594                        if (!res.getState().isDeleted()
11595                            || !checkDeletedParentFolder(dbc, publishList.getDeletedFolderList(), res)) {
11596                            continue;
11597                        }
11598                    } else {
11599                        // don't add locked resources
11600                        continue;
11601                    }
11602                }
11603                if (!"/".equals(res.getRootPath()) && !checkParentResource(dbc, newFolderList, res)) {
11604                    continue;
11605                }
11606                // check permissions
11607                try {
11608                    m_securityManager.checkPermissions(
11609                        dbc,
11610                        res,
11611                        CmsPermissionSet.ACCESS_DIRECT_PUBLISH,
11612                        false,
11613                        CmsResourceFilter.ALL);
11614                } catch (CmsException e) {
11615                    // skip if not enough permissions
11616                    continue;
11617                }
11618                if (res.isFolder()) {
11619                    newFolderList.add(res);
11620                }
11621                result.add(res);
11622            } catch (Exception e) {
11623                // should never happen
11624                LOG.error(e.getLocalizedMessage(), e);
11625            }
11626        }
11627        return result;
11628    }
11629
11630    /**
11631     * Returns a filtered list of sibling resources for publishing.<p>
11632     *
11633     * Contains all siblings of the given resources, which are not locked
11634     * and which have a parent folder that is already published or will be published, too.<p>
11635     *
11636     * @param dbc the current database context
11637     * @param publishList the unfinished publish list
11638     * @param resourceList the list of siblings to filter
11639     *
11640     * @return a filtered list of sibling resources for publishing
11641     */
11642    private List<CmsResource> filterSiblings(
11643        CmsDbContext dbc,
11644        CmsPublishList publishList,
11645        Collection<CmsResource> resourceList) {
11646
11647        List<CmsResource> result = new ArrayList<CmsResource>();
11648
11649        // removed internal extendible folder list, since iterated (sibling) resources are files in any case, never folders
11650
11651        for (CmsResource res : resourceList) {
11652            try {
11653                CmsLock lock = getLock(dbc, res);
11654                if (lock.isPublish()) {
11655                    // if already enqueued
11656                    continue;
11657                }
11658                if (!lock.isLockableBy(dbc.currentUser())) {
11659                    // checks if there is a shared lock and if the resource is deleted
11660                    // this solves the {@link org.opencms.file.TestPublishIssues#testPublishScenarioE} problem.
11661                    if (lock.isShared() && (publishList != null)) {
11662                        if (!res.getState().isDeleted()
11663                            || !checkDeletedParentFolder(dbc, publishList.getDeletedFolderList(), res)) {
11664                            continue;
11665                        }
11666                    } else {
11667                        // don't add locked resources
11668                        continue;
11669                    }
11670                }
11671                if (!"/".equals(res.getRootPath()) && !checkParentResource(dbc, publishList.getFolderList(), res)) {
11672                    // don't add resources that have no parent in the online project
11673                    continue;
11674                }
11675                // check permissions
11676                try {
11677                    m_securityManager.checkPermissions(
11678                        dbc,
11679                        res,
11680                        CmsPermissionSet.ACCESS_DIRECT_PUBLISH,
11681                        false,
11682                        CmsResourceFilter.ALL);
11683                } catch (CmsException e) {
11684                    // skip if not enough permissions
11685                    continue;
11686                }
11687                result.add(res);
11688            } catch (Exception e) {
11689                // should never happen
11690                LOG.error(e.getLocalizedMessage(), e);
11691            }
11692        }
11693        return result;
11694    }
11695
11696    /**
11697     * Returns the access control list of a given resource.<p>
11698     *
11699     * @param dbc the current database context
11700     * @param resource the resource
11701     * @param forFolder should be true if resource is a folder
11702     * @param depth the depth to include non-inherited access entries, also
11703     * @param inheritedOnly flag indicates to collect inherited permissions only
11704     *
11705     * @return the access control list of the resource
11706     *
11707     * @throws CmsException if something goes wrong
11708     */
11709    private CmsAccessControlList getAccessControlList(
11710        CmsDbContext dbc,
11711        CmsResource resource,
11712        boolean inheritedOnly,
11713        boolean forFolder,
11714        int depth)
11715    throws CmsException {
11716
11717        String cacheKey = getCacheKey(
11718            new String[] {
11719                inheritedOnly ? "+" : "-",
11720                forFolder ? "+" : "-",
11721                Integer.toString(depth),
11722                resource.getStructureId().toString()},
11723            dbc);
11724
11725        CmsAccessControlList acl = m_monitor.getCachedACL(cacheKey);
11726
11727        // return the cached acl if already available
11728        if ((acl != null) && dbc.getProjectId().isNullUUID()) {
11729            return acl;
11730        }
11731
11732        List<CmsAccessControlEntry> aces = getUserDriver(dbc).readAccessControlEntries(
11733            dbc,
11734            dbc.currentProject(),
11735            resource.getResourceId(),
11736            (depth > 1) || ((depth > 0) && forFolder));
11737
11738        // sort the list of aces
11739        boolean overwriteAll = sortAceList(aces);
11740
11741        // if no 'overwrite all' ace was found
11742        if (!overwriteAll) {
11743            // get the acl of the parent
11744            CmsResource parentResource = null;
11745            try {
11746                // try to recurse over the id
11747                parentResource = getVfsDriver(dbc).readParentFolder(
11748                    dbc,
11749                    dbc.currentProject().getUuid(),
11750                    resource.getStructureId());
11751            } catch (CmsVfsResourceNotFoundException e) {
11752                // should never happen, but try with the path
11753                String parentPath = CmsResource.getParentFolder(resource.getRootPath());
11754                if (parentPath != null) {
11755                    parentResource = getVfsDriver(dbc).readFolder(dbc, dbc.currentProject().getUuid(), parentPath);
11756                }
11757            }
11758            if (parentResource != null) {
11759                acl = (CmsAccessControlList)getAccessControlList(
11760                    dbc,
11761                    parentResource,
11762                    inheritedOnly,
11763                    forFolder,
11764                    depth + 1).clone();
11765            }
11766        }
11767        if (acl == null) {
11768            acl = new CmsAccessControlList();
11769        }
11770
11771        Set<CmsUUID> exclusiveAccessPrincipals = new HashSet<>();
11772        if (!((depth == 0) && inheritedOnly)) {
11773            Iterator<CmsAccessControlEntry> itAces = aces.iterator();
11774            while (itAces.hasNext()) {
11775                CmsAccessControlEntry acEntry = itAces.next();
11776                if (depth > 0) {
11777                    acEntry.setFlags(CmsAccessControlEntry.ACCESS_FLAGS_INHERITED);
11778                }
11779                if ((depth == 0)
11780                    && resource.isFile()
11781                    && (0 != (acEntry.getFlags() & CmsAccessControlEntry.ACCESS_FLAGS_RESPONSIBLE))) {
11782
11783                    // 'responsible' flag is only interpreted as exclusive access if it's not inherited and set directly on a file
11784                    exclusiveAccessPrincipals.add(acEntry.getPrincipal());
11785                }
11786
11787                acl.add(acEntry);
11788
11789                // if the overwrite flag is set, reset the allowed permissions to the permissions of this entry
11790                // denied permissions are kept or extended
11791                if ((acEntry.getFlags() & CmsAccessControlEntry.ACCESS_FLAGS_OVERWRITE) > 0) {
11792                    acl.setAllowedPermissions(acEntry);
11793                }
11794            }
11795        }
11796        if (exclusiveAccessPrincipals.size() > 0) {
11797            acl.setExclusiveAccessPrincipals(exclusiveAccessPrincipals);
11798        }
11799
11800        if (dbc.getProjectId().isNullUUID()) {
11801            m_monitor.cacheACL(cacheKey, acl);
11802        }
11803        return acl;
11804    }
11805
11806    /**
11807     * Return a cache key build from the provided information.<p>
11808     *
11809     * @param prefix a prefix for the key
11810     * @param flag a boolean flag for the key (only used if prefix is not null)
11811     * @param projectId the project for which to generate the key
11812     * @param resource the resource for which to generate the key
11813     *
11814     * @return String a cache key build from the provided information
11815     */
11816    private String getCacheKey(String prefix, boolean flag, CmsUUID projectId, String resource) {
11817
11818        StringBuffer b = new StringBuffer(64);
11819        if (prefix != null) {
11820            b.append(prefix);
11821            b.append(flag ? '+' : '-');
11822        }
11823        b.append(CmsProject.isOnlineProject(projectId) ? '+' : '-');
11824        return b.append(resource).toString();
11825    }
11826
11827    /**
11828     * Return a cache key build from the provided information.<p>
11829     *
11830     * @param keys an array of keys to generate the cache key from
11831     * @param dbc the database context for which to generate the key
11832     *
11833     * @return String a cache key build from the provided information
11834     */
11835    private String getCacheKey(String[] keys, CmsDbContext dbc) {
11836
11837        if (!dbc.getProjectId().isNullUUID()) {
11838            return "";
11839        }
11840        StringBuffer b = new StringBuffer(64);
11841        int len = keys.length;
11842        if (len > 0) {
11843            for (int i = 0; i < len; i++) {
11844                b.append(keys[i]);
11845                b.append('_');
11846            }
11847        }
11848        if (dbc.currentProject().isOnlineProject()) {
11849            b.append("+");
11850        } else {
11851            b.append("-");
11852        }
11853        return b.toString();
11854    }
11855
11856    /**
11857     * Gets the correct driver interface to use for proxying a specific driver instance.<p>
11858     *
11859     * @param obj the driver instance
11860     * @return the interface to use for proxying
11861     */
11862    private Class<?> getDriverInterfaceForProxy(Object obj) {
11863
11864        for (Class<?> interfaceClass : new Class[] {
11865            I_CmsUserDriver.class,
11866            I_CmsVfsDriver.class,
11867            I_CmsProjectDriver.class,
11868            I_CmsHistoryDriver.class,
11869            I_CmsSubscriptionDriver.class}) {
11870            if (interfaceClass.isAssignableFrom(obj.getClass())) {
11871                return interfaceClass;
11872            }
11873        }
11874        return null;
11875    }
11876
11877    /**
11878     * Returns the correct project id.<p>
11879     *
11880     * @param dbc the database context
11881     *
11882     * @return the correct project id
11883     */
11884    private CmsUUID getProjectIdForContext(CmsDbContext dbc) {
11885
11886        CmsUUID projectId = dbc.getProjectId();
11887        if (projectId.isNullUUID()) {
11888            projectId = dbc.currentProject().getUuid();
11889        }
11890        return projectId;
11891    }
11892
11893    /**
11894     * Returns if and what state needs to be updated.<p>
11895     *
11896     * @param dbc the db context
11897     * @param resource the resource
11898     * @param properties the properties to check
11899     *
11900     * @return 0: none, 1: structure, 2: resource
11901     *
11902     * @throws CmsDataAccessException if something goes wrong
11903     */
11904    private int getUpdateState(CmsDbContext dbc, CmsResource resource, List<CmsProperty> properties)
11905    throws CmsDataAccessException {
11906
11907        int updateState = 0;
11908        I_CmsVfsDriver vfsDriver = getVfsDriver(dbc);
11909        Iterator<CmsProperty> it = properties.iterator();
11910        while (it.hasNext() && (updateState < 2)) {
11911            CmsProperty property = it.next();
11912
11913            // read existing property
11914            CmsProperty existingProperty = vfsDriver.readPropertyObject(
11915                dbc,
11916                property.getName(),
11917                dbc.currentProject(),
11918                resource);
11919
11920            // check the shared property
11921            if (property.getResourceValue() != null) {
11922                if (property.isDeleteResourceValue()) {
11923                    if (existingProperty.getResourceValue() != null) {
11924                        updateState = 2; // deleted
11925                    }
11926                } else {
11927                    if (existingProperty.getResourceValue() == null) {
11928                        updateState = 2; // created
11929                    } else {
11930                        if (!property.getResourceValue().equals(existingProperty.getResourceValue())) {
11931                            updateState = 2; // updated
11932                        }
11933                    }
11934                }
11935            }
11936            if (updateState == 0) {
11937                // check the individual property only if needed
11938                if (property.getStructureValue() != null) {
11939                    if (property.isDeleteStructureValue()) {
11940                        if (existingProperty.getStructureValue() != null) {
11941                            updateState = 1; // deleted
11942                        }
11943                    } else {
11944                        if (existingProperty.getStructureValue() == null) {
11945                            updateState = 1; // created
11946                        } else {
11947                            if (!property.getStructureValue().equals(existingProperty.getStructureValue())) {
11948                                updateState = 1; // updated
11949                            }
11950                        }
11951                    }
11952                }
11953            }
11954        }
11955        return updateState;
11956    }
11957
11958    /**
11959     * Returns all groups that are virtualizing the given role in the given ou.<p>
11960     *
11961     * @param dbc the database context
11962     * @param role the role
11963     *
11964     * @return all groups that are virtualizing the given role (or a child of it)
11965     *
11966     * @throws CmsException if something goes wrong
11967     */
11968    private List<CmsGroup> getVirtualGroupsForRole(CmsDbContext dbc, CmsRole role) throws CmsException {
11969
11970        Set<Integer> roleFlags = new HashSet<Integer>();
11971        // add role flag
11972        Integer flags = Integer.valueOf(role.getVirtualGroupFlags());
11973        roleFlags.add(flags);
11974        // collect all child role flags
11975        Iterator<CmsRole> itChildRoles = role.getChildren(true).iterator();
11976        while (itChildRoles.hasNext()) {
11977            CmsRole child = itChildRoles.next();
11978            flags = Integer.valueOf(child.getVirtualGroupFlags());
11979            roleFlags.add(flags);
11980        }
11981        // iterate all groups matching the flags
11982        List<CmsGroup> groups = new ArrayList<CmsGroup>();
11983        Iterator<CmsGroup> it = getGroups(dbc, readOrganizationalUnit(dbc, role.getOuFqn()), false, false).iterator();
11984        while (it.hasNext()) {
11985            CmsGroup group = it.next();
11986            if (group.isVirtual()) {
11987                CmsRole r = CmsRole.valueOf(group);
11988                if (roleFlags.contains(Integer.valueOf(r.getVirtualGroupFlags()))) {
11989                    groups.add(group);
11990                }
11991            }
11992        }
11993        return groups;
11994    }
11995
11996    /**
11997     * Returns a list of users in a group.<p>
11998     *
11999     * @param dbc the current database context
12000     * @param ouFqn the organizational unit to get the users from
12001     * @param groupname the name of the group to list users from
12002     * @param includeOtherOuUsers include users of other organizational units
12003     * @param directUsersOnly if set only the direct assigned users will be returned,
12004     *                        if not also indirect users, ie. members of parent roles,
12005     *                        this parameter only works with roles
12006     * @param readRoles if to read roles or groups
12007     *
12008     * @return all <code>{@link CmsUser}</code> objects in the group
12009     *
12010     * @throws CmsException if operation was not successful
12011     */
12012    private List<CmsUser> internalUsersOfGroup(
12013        CmsDbContext dbc,
12014        String ouFqn,
12015        String groupname,
12016        boolean includeOtherOuUsers,
12017        boolean directUsersOnly,
12018        boolean readRoles)
12019    throws CmsException {
12020
12021        CmsGroup group = readGroup(dbc, groupname); // check that the group really exists
12022        if ((group == null) || (!((!readRoles && !group.isRole()) || (readRoles && group.isRole())))) {
12023            throw new CmsDbEntryNotFoundException(Messages.get().container(Messages.ERR_UNKNOWN_GROUP_1, groupname));
12024        }
12025
12026        String prefix = "_" + includeOtherOuUsers + "_" + directUsersOnly + "_" + ouFqn;
12027        String cacheKey = m_keyGenerator.getCacheKeyForGroupUsers(prefix, dbc, group);
12028        List<CmsUser> allUsers = m_monitor.getCachedUserList(cacheKey);
12029        if (allUsers == null) {
12030            Set<CmsUser> users = new HashSet<CmsUser>(
12031                getUserDriver(dbc).readUsersOfGroup(dbc, groupname, includeOtherOuUsers));
12032            if (readRoles && !directUsersOnly) {
12033                CmsRole role = CmsRole.valueOf(group);
12034                if (role.getParentRole() != null) {
12035                    try {
12036                        String parentGroup = role.getParentRole().getGroupName();
12037                        readGroup(dbc, parentGroup);
12038                        // iterate the parent roles
12039                        users.addAll(
12040                            internalUsersOfGroup(
12041                                dbc,
12042                                ouFqn,
12043                                parentGroup,
12044                                includeOtherOuUsers,
12045                                directUsersOnly,
12046                                readRoles));
12047                    } catch (CmsDbEntryNotFoundException e) {
12048                        // ignore, this may happen while deleting an orgunit
12049                        if (LOG.isDebugEnabled()) {
12050                            LOG.debug(e.getLocalizedMessage(), e);
12051                        }
12052                    }
12053                }
12054                String parentOu = CmsOrganizationalUnit.getParentFqn(group.getOuFqn());
12055                if (parentOu != null) {
12056                    // iterate the parent ou's
12057                    users.addAll(
12058                        internalUsersOfGroup(
12059                            dbc,
12060                            ouFqn,
12061                            parentOu + group.getSimpleName(),
12062                            includeOtherOuUsers,
12063                            directUsersOnly,
12064                            readRoles));
12065                }
12066            } else if (!readRoles && !directUsersOnly) {
12067                List<CmsGroup> groups = getChildren(dbc, group, false);
12068                for (CmsGroup parentGroup : groups) {
12069                    try {
12070                        // iterate the parent groups
12071                        users.addAll(
12072                            internalUsersOfGroup(
12073                                dbc,
12074                                ouFqn,
12075                                parentGroup.getName(),
12076                                includeOtherOuUsers,
12077                                directUsersOnly,
12078                                readRoles));
12079                    } catch (CmsDbEntryNotFoundException e) {
12080                        // ignore, this may happen while deleting an orgunit
12081                        if (LOG.isDebugEnabled()) {
12082                            LOG.debug(e.getLocalizedMessage(), e);
12083                        }
12084                    }
12085                }
12086            }
12087            // filter users from other ous
12088            if (!includeOtherOuUsers) {
12089                Iterator<CmsUser> itUsers = users.iterator();
12090                while (itUsers.hasNext()) {
12091                    CmsUser user = itUsers.next();
12092                    if (!user.getOuFqn().equals(ouFqn)) {
12093                        itUsers.remove();
12094                    }
12095                }
12096            }
12097
12098            // make user list unmodifiable for caching
12099            allUsers = Collections.unmodifiableList(new ArrayList<CmsUser>(users));
12100            if (dbc.getProjectId().isNullUUID()) {
12101                m_monitor.cacheUserList(cacheKey, allUsers);
12102            }
12103        }
12104        return allUsers;
12105    }
12106
12107    /**
12108     * Reads all resources that are inside and changed in a specified project.<p>
12109     *
12110     * @param dbc the current database context
12111     * @param projectId the ID of the project
12112     * @param mode one of the {@link CmsReadChangedProjectResourceMode} constants
12113     *
12114     * @return a List with all resources inside the specified project
12115     *
12116     * @throws CmsException if something goes wrong
12117     */
12118    private List<CmsResource> readChangedResourcesInsideProject(
12119        CmsDbContext dbc,
12120        CmsUUID projectId,
12121        CmsReadChangedProjectResourceMode mode)
12122    throws CmsException {
12123
12124        String cacheKey = projectId + "_" + mode.toString();
12125        List<CmsResource> result = m_monitor.getCachedProjectResources(cacheKey);
12126        if (result != null) {
12127            return result;
12128        }
12129        List<String> projectResources = readProjectResources(dbc, readProject(dbc, projectId));
12130        result = new ArrayList<CmsResource>();
12131        String currentProjectResource = null;
12132        List<CmsResource> resources = new ArrayList<CmsResource>();
12133        CmsResource currentResource = null;
12134        CmsLock currentLock = null;
12135
12136        for (int i = 0; i < projectResources.size(); i++) {
12137            // read all resources that are inside the project by visiting each project resource
12138            currentProjectResource = projectResources.get(i);
12139
12140            try {
12141                currentResource = readResource(dbc, currentProjectResource, CmsResourceFilter.ALL);
12142
12143                if (currentResource.isFolder()) {
12144                    resources.addAll(readResources(dbc, currentResource, CmsResourceFilter.ALL, true));
12145                } else {
12146                    resources.add(currentResource);
12147                }
12148            } catch (CmsException e) {
12149                // the project resource probably doesn't exist (anymore)...
12150                if (!(e instanceof CmsVfsResourceNotFoundException)) {
12151                    throw e;
12152                }
12153            }
12154        }
12155
12156        for (int j = 0; j < resources.size(); j++) {
12157            currentResource = resources.get(j);
12158            currentLock = getLock(dbc, currentResource).getEditionLock();
12159
12160            if (!currentResource.getState().isUnchanged()) {
12161                if ((currentLock.isNullLock() && (currentResource.getProjectLastModified().equals(projectId)))
12162                    || (currentLock.isOwnedBy(dbc.currentUser()) && (currentLock.getProjectId().equals(projectId)))) {
12163                    // add only resources that are
12164                    // - inside the project,
12165                    // - changed in the project,
12166                    // - either unlocked, or locked for the current user in the project
12167                    if ((mode == RCPRM_FILES_AND_FOLDERS_MODE)
12168                        || (currentResource.isFolder() && (mode == RCPRM_FOLDERS_ONLY_MODE))
12169                        || (currentResource.isFile() && (mode == RCPRM_FILES_ONLY_MODE))) {
12170                        result.add(currentResource);
12171                    }
12172                }
12173            }
12174        }
12175
12176        resources.clear();
12177        resources = null;
12178
12179        m_monitor.cacheProjectResources(cacheKey, result);
12180        return result;
12181    }
12182
12183    /**
12184     * Sorts the given list of {@link CmsAccessControlEntry} objects.<p>
12185     *
12186     * The the 'all others' ace in first place, the 'overwrite all' ace in second.<p>
12187     *
12188     * @param aces the list of ACEs to sort
12189     *
12190     * @return <code>true</code> if the list contains the 'overwrite all' ace
12191     */
12192    private boolean sortAceList(List<CmsAccessControlEntry> aces) {
12193
12194        // sort the list of entries
12195        Collections.sort(aces, CmsAccessControlEntry.COMPARATOR_ACE);
12196        // after sorting just the first 2 positions come in question
12197        for (int i = 0; i < Math.min(aces.size(), 2); i++) {
12198            CmsAccessControlEntry acEntry = aces.get(i);
12199            if (acEntry.getPrincipal().equals(CmsAccessControlEntry.PRINCIPAL_OVERWRITE_ALL_ID)) {
12200                return true;
12201            }
12202        }
12203        return false;
12204    }
12205
12206    /**
12207     * All permissions and resources attributes of the principal
12208     * are transfered to a replacement principal.<p>
12209     *
12210     * @param dbc the current database context
12211     * @param project the current project
12212     * @param principalId the id of the principal to be replaced
12213     * @param replacementId the user to be transfered
12214     * @param withACEs flag to signal if the ACEs should also be transfered or just deleted
12215     *
12216     * @throws CmsException if operation was not successful
12217     */
12218    private void transferPrincipalResources(
12219        CmsDbContext dbc,
12220        CmsProject project,
12221        CmsUUID principalId,
12222        CmsUUID replacementId,
12223        boolean withACEs)
12224    throws CmsException {
12225
12226        // get all resources for the given user including resources associated by ACEs or attributes
12227        I_CmsUserDriver userDriver = getUserDriver(dbc);
12228        I_CmsVfsDriver vfsDriver = getVfsDriver(dbc);
12229        Set<CmsResource> resources = getResourcesForPrincipal(dbc, project, principalId, null, true);
12230        Iterator<CmsResource> it = resources.iterator();
12231        while (it.hasNext()) {
12232            CmsResource resource = it.next();
12233            // check resource attributes
12234            boolean attrModified = false;
12235            CmsUUID createdUser = null;
12236            if (resource.getUserCreated().equals(principalId)) {
12237                createdUser = replacementId;
12238                attrModified = true;
12239            }
12240            CmsUUID lastModUser = null;
12241            if (resource.getUserLastModified().equals(principalId)) {
12242                lastModUser = replacementId;
12243                attrModified = true;
12244            }
12245            if (attrModified) {
12246                vfsDriver.transferResource(dbc, project, resource, createdUser, lastModUser);
12247                // clear the cache
12248                m_monitor.clearResourceCache();
12249            }
12250            boolean aceModified = false;
12251            // check aces
12252            if (withACEs) {
12253                Iterator<CmsAccessControlEntry> itAces = userDriver.readAccessControlEntries(
12254                    dbc,
12255                    project,
12256                    resource.getResourceId(),
12257                    false).iterator();
12258                while (itAces.hasNext()) {
12259                    CmsAccessControlEntry ace = itAces.next();
12260                    if (ace.getPrincipal().equals(principalId)) {
12261                        CmsAccessControlEntry newAce = new CmsAccessControlEntry(
12262                            ace.getResource(),
12263                            replacementId,
12264                            ace.getAllowedPermissions(),
12265                            ace.getDeniedPermissions(),
12266                            ace.getFlags());
12267                        // write the new ace
12268                        userDriver.writeAccessControlEntry(dbc, project, newAce);
12269                        aceModified = true;
12270                    }
12271                }
12272                if (aceModified) {
12273                    // clear the cache
12274                    m_monitor.clearAccessControlListCache();
12275                }
12276            }
12277            if (attrModified || aceModified) {
12278                // fire the event
12279                Map<String, Object> data = new HashMap<String, Object>(2);
12280                data.put(I_CmsEventListener.KEY_RESOURCE, resource);
12281                data.put(
12282                    I_CmsEventListener.KEY_CHANGE,
12283                    Integer.valueOf(
12284                        ((attrModified) ? CHANGED_RESOURCE : 0) | ((aceModified) ? CHANGED_ACCESSCONTROL : 0)));
12285                OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_MODIFIED, data));
12286            }
12287        }
12288    }
12289
12290    /**
12291     * Undoes all content changes of a resource.<p>
12292     *
12293     * @param dbc the database context
12294     * @param onlineProject the online project
12295     * @param offlineResource the offline resource, or <code>null</code> if deleted
12296     * @param onlineResource the online resource
12297     * @param newState the new resource state
12298     * @param moveUndone is a move operation on the same resource has been made
12299     *
12300     * @throws CmsException if something goes wrong
12301     */
12302    private void undoContentChanges(
12303        CmsDbContext dbc,
12304        CmsProject onlineProject,
12305        CmsResource offlineResource,
12306        CmsResource onlineResource,
12307        CmsResourceState newState,
12308        boolean moveUndone)
12309    throws CmsException {
12310
12311        String path = ((moveUndone || (offlineResource == null))
12312        ? onlineResource.getRootPath()
12313        : offlineResource.getRootPath());
12314
12315        // change folder or file?
12316        I_CmsUserDriver userDriver = getUserDriver(dbc);
12317        I_CmsVfsDriver vfsDriver = getVfsDriver(dbc);
12318        if (onlineResource.isFolder()) {
12319            CmsFolder restoredFolder = new CmsFolder(
12320                onlineResource.getStructureId(),
12321                onlineResource.getResourceId(),
12322                path,
12323                onlineResource.getTypeId(),
12324                onlineResource.getFlags(),
12325                dbc.currentProject().getUuid(),
12326                newState,
12327                onlineResource.getDateCreated(),
12328                onlineResource.getUserCreated(),
12329                onlineResource.getDateLastModified(),
12330                onlineResource.getUserLastModified(),
12331                onlineResource.getDateReleased(),
12332                onlineResource.getDateExpired(),
12333                onlineResource.getVersion()); // version number does not matter since it will be computed later
12334
12335            // write the folder in the offline project
12336            // this sets a flag so that the folder date is not set to the current time
12337            restoredFolder.setDateLastModified(onlineResource.getDateLastModified());
12338
12339            // write the folder
12340            vfsDriver.writeResource(dbc, dbc.currentProject().getUuid(), restoredFolder, NOTHING_CHANGED);
12341
12342            // restore the properties from the online project
12343            vfsDriver.deletePropertyObjects(
12344                dbc,
12345                dbc.currentProject().getUuid(),
12346                restoredFolder,
12347                CmsProperty.DELETE_OPTION_DELETE_STRUCTURE_AND_RESOURCE_VALUES);
12348
12349            List<CmsProperty> propertyInfos = vfsDriver.readPropertyObjects(dbc, onlineProject, onlineResource);
12350            vfsDriver.writePropertyObjects(dbc, dbc.currentProject(), restoredFolder, propertyInfos);
12351
12352            // restore the access control entries from the online project
12353            userDriver.removeAccessControlEntries(dbc, dbc.currentProject(), onlineResource.getResourceId());
12354            ListIterator<CmsAccessControlEntry> aceList = userDriver.readAccessControlEntries(
12355                dbc,
12356                onlineProject,
12357                onlineResource.getResourceId(),
12358                false).listIterator();
12359
12360            while (aceList.hasNext()) {
12361                CmsAccessControlEntry ace = aceList.next();
12362                userDriver.createAccessControlEntry(
12363                    dbc,
12364                    dbc.currentProject(),
12365                    onlineResource.getResourceId(),
12366                    ace.getPrincipal(),
12367                    ace.getPermissions().getAllowedPermissions(),
12368                    ace.getPermissions().getDeniedPermissions(),
12369                    ace.getFlags());
12370            }
12371        } else {
12372            byte[] onlineContent = vfsDriver.readContent(
12373                dbc,
12374                CmsProject.ONLINE_PROJECT_ID,
12375                onlineResource.getResourceId());
12376
12377            CmsFile restoredFile = new CmsFile(
12378                onlineResource.getStructureId(),
12379                onlineResource.getResourceId(),
12380                path,
12381                onlineResource.getTypeId(),
12382                onlineResource.getFlags(),
12383                dbc.currentProject().getUuid(),
12384                newState,
12385                onlineResource.getDateCreated(),
12386                onlineResource.getUserCreated(),
12387                onlineResource.getDateLastModified(),
12388                onlineResource.getUserLastModified(),
12389                onlineResource.getDateReleased(),
12390                onlineResource.getDateExpired(),
12391                0,
12392                onlineResource.getLength(),
12393                onlineResource.getDateContent(),
12394                onlineResource.getVersion(), // version number does not matter since it will be computed later
12395                onlineContent);
12396
12397            // write the file in the offline project
12398            // this sets a flag so that the file date is not set to the current time
12399            restoredFile.setDateLastModified(onlineResource.getDateLastModified());
12400
12401            // collect the old properties
12402            List<CmsProperty> properties = vfsDriver.readPropertyObjects(dbc, onlineProject, onlineResource);
12403
12404            if (offlineResource != null) {
12405                // bug fix 1020: delete all properties (inclum_rejectStructureIdded shared),
12406                // shared properties will be recreated by the next call of #createResource(...)
12407                vfsDriver.deletePropertyObjects(
12408                    dbc,
12409                    dbc.currentProject().getUuid(),
12410                    onlineResource,
12411                    CmsProperty.DELETE_OPTION_DELETE_STRUCTURE_AND_RESOURCE_VALUES);
12412
12413                // implementation notes:
12414                // undo changes can become complex e.g. if a resource was deleted, and then
12415                // another resource was copied over the deleted file as a sibling
12416                // therefore we must "clean" delete the offline resource, and then create
12417                // an new resource with the create method
12418                // note that this does NOT apply to folders, since a folder cannot be replaced
12419                // like a resource anyway
12420                deleteResource(dbc, offlineResource, CmsResource.DELETE_PRESERVE_SIBLINGS);
12421            }
12422            CmsResource res = createResource(
12423                dbc,
12424                restoredFile.getRootPath(),
12425                restoredFile,
12426                restoredFile.getContents(),
12427                properties,
12428                false);
12429
12430            // copy the access control entries from the online project
12431            if (offlineResource != null) {
12432                userDriver.removeAccessControlEntries(dbc, dbc.currentProject(), onlineResource.getResourceId());
12433            }
12434            ListIterator<CmsAccessControlEntry> aceList = userDriver.readAccessControlEntries(
12435                dbc,
12436                onlineProject,
12437                onlineResource.getResourceId(),
12438                false).listIterator();
12439
12440            while (aceList.hasNext()) {
12441                CmsAccessControlEntry ace = aceList.next();
12442                userDriver.createAccessControlEntry(
12443                    dbc,
12444                    dbc.currentProject(),
12445                    res.getResourceId(),
12446                    ace.getPrincipal(),
12447                    ace.getPermissions().getAllowedPermissions(),
12448                    ace.getPermissions().getDeniedPermissions(),
12449                    ace.getFlags());
12450            }
12451
12452            vfsDriver.deleteUrlNameMappingEntries(
12453                dbc,
12454                false,
12455                CmsUrlNameMappingFilter.ALL.filterStructureId(res.getStructureId()).filterStates(
12456                    CmsUrlNameMappingEntry.MAPPING_STATUS_NEW,
12457                    CmsUrlNameMappingEntry.MAPPING_STATUS_REPLACE_ON_PUBLISH));
12458            // restore the state to unchanged
12459            res.setState(newState);
12460            m_vfsDriver.writeResourceState(dbc, dbc.currentProject(), res, UPDATE_ALL, false);
12461        }
12462
12463        // delete all offline relations
12464        if (offlineResource != null) {
12465            vfsDriver.deleteRelations(dbc, dbc.currentProject().getUuid(), offlineResource, CmsRelationFilter.TARGETS);
12466        }
12467        // get online relations
12468        List<CmsRelation> relations = vfsDriver.readRelations(
12469            dbc,
12470            CmsProject.ONLINE_PROJECT_ID,
12471            onlineResource,
12472            CmsRelationFilter.TARGETS);
12473        // write offline relations
12474        Iterator<CmsRelation> itRelations = relations.iterator();
12475        while (itRelations.hasNext()) {
12476            CmsRelation relation = itRelations.next();
12477            vfsDriver.createRelation(dbc, dbc.currentProject().getUuid(), relation);
12478        }
12479
12480        // update the cache
12481        m_monitor.clearResourceCache();
12482        m_monitor.flushCache(CmsMemoryMonitor.CacheType.PROPERTY, CmsMemoryMonitor.CacheType.PROPERTY_LIST);
12483
12484        if ((offlineResource == null) || offlineResource.getRootPath().equals(onlineResource.getRootPath())) {
12485            log(
12486                dbc,
12487                new CmsLogEntry(
12488                    dbc,
12489                    onlineResource.getStructureId(),
12490                    CmsLogEntryType.RESOURCE_RESTORED,
12491                    new String[] {onlineResource.getRootPath()}),
12492                false);
12493        } else {
12494            log(
12495                dbc,
12496                new CmsLogEntry(
12497                    dbc,
12498                    offlineResource.getStructureId(),
12499                    CmsLogEntryType.RESOURCE_MOVE_RESTORED,
12500                    new String[] {offlineResource.getRootPath(), onlineResource.getRootPath()}),
12501                false);
12502        }
12503        if (offlineResource != null) {
12504            OpenCms.fireCmsEvent(
12505                new CmsEvent(
12506                    I_CmsEventListener.EVENT_RESOURCE_AND_PROPERTIES_MODIFIED,
12507                    Collections.<String, Object> singletonMap(I_CmsEventListener.KEY_RESOURCE, offlineResource)));
12508        } else {
12509            OpenCms.fireCmsEvent(
12510                new CmsEvent(
12511                    I_CmsEventListener.EVENT_RESOURCE_AND_PROPERTIES_MODIFIED,
12512                    Collections.<String, Object> singletonMap(I_CmsEventListener.KEY_RESOURCE, onlineResource)));
12513        }
12514    }
12515
12516    /**
12517     * Updates the current users context dates with the given resource.<p>
12518     *
12519     * This checks the date information of the resource based on
12520     * {@link CmsResource#getDateLastModified()} as well as
12521     * {@link CmsResource#getDateReleased()} and {@link CmsResource#getDateExpired()}.
12522     * The current users request context is updated with the the "latest" dates found.<p>
12523     *
12524     * This is required in order to ensure proper setting of <code>"last-modified"</code> http headers
12525     * and also for expiration of cached elements in the Flex cache.
12526     * Consider the following use case: Page A is generated from resources x, y and z.
12527     * If either x, y or z has an expiration / release date set, then page A must expire at a certain point
12528     * in time. This is ensured by the context date check here.<p>
12529     *
12530     * @param dbc the current database context
12531     * @param resource the resource to get the date information from
12532     */
12533    private void updateContextDates(CmsDbContext dbc, CmsResource resource) {
12534
12535        CmsFlexRequestContextInfo info = dbc.getFlexRequestContextInfo();
12536        if (info != null) {
12537            info.updateFromResource(resource);
12538        }
12539    }
12540
12541    /**
12542     * Updates the current users context dates with each {@link CmsResource} object in the given list.<p>
12543     *
12544     * The given input list is returned unmodified.<p>
12545     *
12546     * Please see {@link #updateContextDates(CmsDbContext, CmsResource)} for an explanation of what this method does.<p>
12547     *
12548     * @param dbc the current database context
12549     * @param resourceList a list of {@link CmsResource} objects
12550     *
12551     * @return the original list of CmsResources with the full resource name set
12552     */
12553    private List<CmsResource> updateContextDates(CmsDbContext dbc, List<CmsResource> resourceList) {
12554
12555        CmsFlexRequestContextInfo info = dbc.getFlexRequestContextInfo();
12556        if (info != null) {
12557            for (int i = 0; i < resourceList.size(); i++) {
12558                CmsResource resource = resourceList.get(i);
12559                info.updateFromResource(resource);
12560            }
12561        }
12562        return resourceList;
12563    }
12564
12565    /**
12566     * Returns a List of {@link CmsResource} objects generated when applying the given filter to the given list,
12567     * also updates the current users context dates with each {@link CmsResource} object in the given list,
12568     * also applies the selected resource filter to all resources in the list and returns the remaining resources.<p>
12569     *
12570     * Please see {@link #updateContextDates(CmsDbContext, CmsResource)} for an explanation of what this method does.<p>
12571     *
12572     * @param dbc the current database context
12573     * @param resourceList a list of {@link CmsResource} objects
12574     * @param filter the resource filter to use
12575     *
12576     * @return a List of {@link CmsResource} objects generated when applying the given filter to the given list
12577     */
12578    private List<CmsResource> updateContextDates(
12579        CmsDbContext dbc,
12580        List<CmsResource> resourceList,
12581        CmsResourceFilter filter) {
12582
12583        if (CmsResourceFilter.ALL == filter) {
12584            // if there is no filter required, then use the simpler method that does not apply the filter
12585            return new ArrayList<CmsResource>(updateContextDates(dbc, resourceList));
12586        }
12587
12588        CmsFlexRequestContextInfo info = dbc.getFlexRequestContextInfo();
12589        List<CmsResource> result = new ArrayList<CmsResource>(resourceList.size());
12590        for (int i = 0; i < resourceList.size(); i++) {
12591            CmsResource resource = resourceList.get(i);
12592            if (filter.isValid(dbc.getRequestContext(), resource)) {
12593                result.add(resource);
12594            }
12595            // must also include "invalid" resources for the update of context dates
12596            // since a resource may be invalid because of release / expiration date
12597            if (info != null) {
12598                info.updateFromResource(resource);
12599            }
12600        }
12601        return result;
12602    }
12603
12604    /**
12605     * Updates the state of a resource, depending on the <code>resourceState</code> parameter.<p>
12606     *
12607     * @param dbc the db context
12608     * @param resource the resource
12609     * @param resourceState if <code>true</code> the resource state will be updated, if not just the structure state.
12610     *
12611     * @throws CmsDataAccessException if something goes wrong
12612     */
12613    private void updateState(CmsDbContext dbc, CmsResource resource, boolean resourceState)
12614    throws CmsDataAccessException {
12615
12616        CmsUUID projectId = ((dbc.getProjectId() == null) || dbc.getProjectId().isNullUUID())
12617        ? dbc.currentProject().getUuid()
12618        : dbc.getProjectId();
12619        resource.setUserLastModified(dbc.currentUser().getId());
12620        if (resourceState) {
12621            // update the whole resource state
12622            getVfsDriver(dbc).writeResource(dbc, projectId, resource, UPDATE_RESOURCE_STATE);
12623        } else {
12624            // update the structure state
12625            getVfsDriver(dbc).writeResource(dbc, projectId, resource, UPDATE_STRUCTURE_STATE);
12626        }
12627    }
12628
12629    /**
12630     * Wraps a driver object with a dynamic proxy that counts method calls and their durations.<p>
12631     *
12632     * @param newDriverInstance the driver instance to wrap
12633     * @return the proxy
12634     */
12635    private Object wrapDriverInProfilingProxy(Object newDriverInstance) {
12636
12637        Class<?> cls = getDriverInterfaceForProxy(newDriverInstance);
12638        if (cls == null) {
12639            return newDriverInstance;
12640        }
12641        return Proxy.newProxyInstance(
12642            Thread.currentThread().getContextClassLoader(),
12643            new Class[] {cls},
12644            new CmsProfilingInvocationHandler(newDriverInstance, CmsDefaultProfilingHandler.INSTANCE));
12645    }
12646
12647}