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