001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com)
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 *
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015 * Lesser General Public License for more details.
016 *
017 * For further information about Alkacon Software GmbH & Co. KG, please see the
018 * company website: http://www.alkacon.com
019 *
020 * For further information about OpenCms, please see the
021 * project website: http://www.opencms.org
022 *
023 * You should have received a copy of the GNU Lesser General Public
024 * License along with this library; if not, write to the Free Software
025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
026 */
027
028package org.opencms.db;
029
030import org.opencms.ade.publish.CmsTooManyPublishResourcesException;
031import org.opencms.configuration.CmsConfigurationManager;
032import org.opencms.configuration.CmsParameterConfiguration;
033import org.opencms.configuration.CmsSystemConfiguration;
034import org.opencms.db.generic.CmsPublishHistoryCleanupFilter;
035import org.opencms.db.generic.CmsUserDriver;
036import org.opencms.db.log.CmsLogEntry;
037import org.opencms.db.log.CmsLogEntryType;
038import org.opencms.db.log.CmsLogFilter;
039import org.opencms.db.timing.CmsDefaultProfilingHandler;
040import org.opencms.db.timing.CmsProfilingInvocationHandler;
041import org.opencms.db.urlname.CmsUrlNameMappingEntry;
042import org.opencms.db.urlname.CmsUrlNameMappingFilter;
043import org.opencms.db.userpublishlist.A_CmsLogPublishListConverter;
044import org.opencms.db.userpublishlist.CmsLogPublishListConverterAllUsers;
045import org.opencms.db.userpublishlist.CmsLogPublishListConverterCurrentUser;
046import org.opencms.file.CmsDataAccessException;
047import org.opencms.file.CmsFile;
048import org.opencms.file.CmsFolder;
049import org.opencms.file.CmsGroup;
050import org.opencms.file.CmsObject;
051import org.opencms.file.CmsProject;
052import org.opencms.file.CmsProperty;
053import org.opencms.file.CmsPropertyDefinition;
054import org.opencms.file.CmsRequestContext;
055import org.opencms.file.CmsResource;
056import org.opencms.file.CmsResourceFilter;
057import org.opencms.file.CmsUser;
058import org.opencms.file.CmsUserSearchParameters;
059import org.opencms.file.CmsVfsException;
060import org.opencms.file.CmsVfsResourceAlreadyExistsException;
061import org.opencms.file.CmsVfsResourceNotFoundException;
062import org.opencms.file.I_CmsResource;
063import org.opencms.file.history.CmsHistoryFile;
064import org.opencms.file.history.CmsHistoryFolder;
065import org.opencms.file.history.CmsHistoryPrincipal;
066import org.opencms.file.history.CmsHistoryProject;
067import org.opencms.file.history.I_CmsHistoryResource;
068import org.opencms.file.types.CmsResourceTypeFolder;
069import org.opencms.file.types.CmsResourceTypeJsp;
070import org.opencms.file.types.I_CmsResourceType;
071import org.opencms.flex.CmsFlexRequestContextInfo;
072import org.opencms.gwt.shared.alias.CmsAliasImportResult;
073import org.opencms.gwt.shared.alias.CmsAliasImportStatus;
074import org.opencms.gwt.shared.alias.CmsAliasMode;
075import org.opencms.i18n.CmsLocaleManager;
076import org.opencms.i18n.CmsMessageContainer;
077import org.opencms.jsp.CmsJspNavBuilder;
078import org.opencms.lock.CmsLock;
079import org.opencms.lock.CmsLockException;
080import org.opencms.lock.CmsLockFilter;
081import org.opencms.lock.CmsLockManager;
082import org.opencms.lock.CmsLockType;
083import org.opencms.main.CmsEvent;
084import org.opencms.main.CmsException;
085import org.opencms.main.CmsIllegalArgumentException;
086import org.opencms.main.CmsIllegalStateException;
087import org.opencms.main.CmsInitException;
088import org.opencms.main.CmsLog;
089import org.opencms.main.CmsMultiException;
090import org.opencms.main.I_CmsEventListener;
091import org.opencms.main.OpenCms;
092import org.opencms.module.CmsModule;
093import org.opencms.monitor.CmsMemoryMonitor;
094import org.opencms.monitor.CmsMemoryMonitor.CacheType;
095import org.opencms.publish.CmsPublishEngine;
096import org.opencms.publish.CmsPublishJobInfoBean;
097import org.opencms.publish.CmsPublishReport;
098import org.opencms.relations.CmsCategoryService;
099import org.opencms.relations.CmsLink;
100import org.opencms.relations.CmsRelation;
101import org.opencms.relations.CmsRelationFilter;
102import org.opencms.relations.CmsRelationSystemValidator;
103import org.opencms.relations.CmsRelationType;
104import org.opencms.relations.CmsRelationType.CopyBehavior;
105import org.opencms.relations.I_CmsLinkParseable;
106import org.opencms.report.CmsLogReport;
107import org.opencms.report.I_CmsReport;
108import org.opencms.security.CmsAccessControlEntry;
109import org.opencms.security.CmsAccessControlList;
110import org.opencms.security.CmsAuthentificationException;
111import org.opencms.security.CmsOrganizationalUnit;
112import org.opencms.security.CmsPasswordEncryptionException;
113import org.opencms.security.CmsPermissionSet;
114import org.opencms.security.CmsPermissionSetCustom;
115import org.opencms.security.CmsPrincipal;
116import org.opencms.security.CmsRole;
117import org.opencms.security.CmsSecurityException;
118import org.opencms.security.I_CmsPermissionHandler;
119import org.opencms.security.I_CmsPermissionHandler.LockCheck;
120import org.opencms.security.I_CmsPrincipal;
121import org.opencms.security.twofactor.CmsSecondFactorInfo;
122import org.opencms.security.twofactor.CmsSecondFactorSetupException;
123import org.opencms.security.twofactor.CmsTwoFactorAuthenticationHandler;
124import org.opencms.site.CmsSiteMatcher;
125import org.opencms.util.CmsFileUtil;
126import org.opencms.util.CmsPath;
127import org.opencms.util.CmsStringUtil;
128import org.opencms.util.CmsUUID;
129import org.opencms.util.PrintfFormat;
130import org.opencms.workflow.CmsDefaultWorkflowManager;
131import org.opencms.workplace.threads.A_CmsProgressThread;
132
133import java.lang.reflect.Proxy;
134import java.util.ArrayList;
135import java.util.Collection;
136import java.util.Collections;
137import java.util.Comparator;
138import java.util.Date;
139import java.util.HashMap;
140import java.util.HashSet;
141import java.util.Iterator;
142import java.util.List;
143import java.util.ListIterator;
144import java.util.Locale;
145import java.util.Map;
146import java.util.Map.Entry;
147import java.util.Set;
148import java.util.TreeSet;
149import java.util.concurrent.ConcurrentMap;
150import java.util.concurrent.ExecutionException;
151import java.util.function.Predicate;
152import java.util.function.Supplier;
153import java.util.regex.Pattern;
154import java.util.regex.PatternSyntaxException;
155import java.util.stream.Collectors;
156
157import org.apache.commons.logging.Log;
158
159import com.google.common.collect.ArrayListMultimap;
160import com.google.common.collect.Maps;
161import com.google.common.collect.Multimap;
162
163/**
164 * The OpenCms driver manager.<p>
165 *
166 * @since 6.0.0
167 */
168public final class CmsDriverManager implements I_CmsEventListener {
169
170    /**
171     * Enum for distinguishing between login modes.
172     */
173    public static enum LoginUserMode {
174        /** 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). */
175        checkOnly,
176
177        /** Normal login process. */
178        standard
179    }
180
181    /**
182     * Resource list which additionally knows whether it should be cacheable in the resource list cache or not.
183     */
184    public static class ResourceListWithCacheability extends ArrayList<CmsResource> {
185
186        /** Serial version id. */
187        private static final long serialVersionUID = 1L;
188
189        /** True if the list should be cacheable. */
190        private boolean m_cacheable = true;
191
192        /**
193         * Creates a new instance.
194         */
195        public ResourceListWithCacheability() {
196
197            super();
198        }
199
200        /**
201         * Creates a new instance.
202         * @param initialCapacity the initial capacity
203         */
204        public ResourceListWithCacheability(int initialCapacity) {
205
206            super(initialCapacity);
207        }
208
209        /**
210         * Returns true if the resource list is cacheable.
211         *
212         * @return true if the list is cacheable
213         */
214        public boolean isCacheable() {
215
216            return m_cacheable;
217        }
218
219        /**
220         * Enables/disables cacheability for the resource list.
221         * @param cacheable true if the list should be cacheable
222         */
223        public void setCacheable(boolean cacheable) {
224
225            m_cacheable = cacheable;
226        }
227
228    }
229
230    /**
231     * Special key class for caching the resource OU data with a Guava LoadingCache.<p>
232     *
233     * In principle, the actual cache key is just the current project, but because of how cache loaders work,
234     * the key must contain everything that varies between calls and is required to load the value. So we also store the DB context
235     * for use by the cache loader. The project (offline/online) must still be stored, because the DB context gets invalidated
236     * eventually, i.e. its project id gets nulled.
237     */
238    public static class ResourceOUCacheKey {
239
240        /** The actual cache key. */
241        private String m_actualKey;
242
243        /** The DB context. */
244        private CmsDbContext m_dbc;
245
246        /** The driver manager to use. */
247        private CmsDriverManager m_driverManager;
248
249        /**
250         * Creates a new instance.
251         *
252         * @param driverManager the driver manager to use
253         * @param dbc the current DB context
254         */
255        public ResourceOUCacheKey(CmsDriverManager driverManager, CmsDbContext dbc) {
256
257            m_dbc = dbc;
258            m_driverManager = driverManager;
259            m_actualKey = CmsProject.ONLINE_PROJECT_ID.equals(dbc.currentProject().getId()) ? "ONLINE" : "OFFLINE";
260        }
261
262        /**
263         * @see java.lang.Object#equals(java.lang.Object)
264         */
265        @Override
266        public boolean equals(Object obj) {
267
268            return (obj instanceof ResourceOUCacheKey)
269                && ((ResourceOUCacheKey)obj).getActualKey().equals(getActualKey());
270        }
271
272        /**
273         * Gets the stored DB context.<p>
274         *
275         * Note that the DB contex returned by this may have been invalidated!
276         *
277         * @return the stored DB context
278         */
279        public CmsDbContext getDbContext() {
280
281            return m_dbc;
282        }
283
284        /**
285         * Gets the current driver manager.
286         *
287         * @return the driver manager to use
288         **/
289        public CmsDriverManager getDriverManager() {
290
291            return m_driverManager;
292        }
293
294        /**
295         * @see java.lang.Object#hashCode()
296         */
297        @Override
298        public int hashCode() {
299
300            return getActualKey().hashCode();
301        }
302
303        /**
304         * Gets the actual key data.
305         *
306         * @return the actual key data
307         */
308        private String getActualKey() {
309
310            return m_actualKey;
311        }
312
313    }
314
315    /**
316     * Helper class used to store information about resources assigned to OUs in a cache.
317     */
318    public static class ResourceOUMap {
319
320        /** Multimap from the paths of resources to the OUs to which they are assigned as OU resources. */
321        private Multimap<CmsPath, CmsOrganizationalUnit> m_ousByAssignedResourcePaths = ArrayListMultimap.create();
322
323        /** The organizational units, with their UUIDs as keys. */
324        private Map<CmsUUID, CmsOrganizationalUnit> m_ousById = new HashMap<>();
325
326        /**
327         * Gets the list of organizational units to which a given root path belongs, according to the cached
328         * OU resource assignments.
329         *
330         * @param rootPath the root path
331         * @return the organizational units to which the path belongs
332         */
333        public List<CmsOrganizationalUnit> getResourceOrgUnits(String rootPath) {
334
335            Set<CmsOrganizationalUnit> result = new HashSet<>();
336            String currentPath = rootPath;
337            while (currentPath != null) {
338                result.addAll(m_ousByAssignedResourcePaths.get(new CmsPath(currentPath)));
339                currentPath = CmsResource.getParentFolder(currentPath);
340            }
341            return new ArrayList<>(result);
342        }
343
344        /**
345         * Reads the OU resource data from the VFS and initializes this instance with it.
346         *
347         * @param driverManager the driver manager to use
348         * @param dbc the current DB context
349         * @throws CmsException if something goes wrong
350         */
351        public void init(CmsDriverManager driverManager, CmsDbContext dbc) throws CmsException {
352
353            List<CmsRelation> relations = driverManager.getRelationsForResource(
354                dbc,
355                null,
356                CmsRelationFilter.ALL.filterType(CmsRelationType.OU_RESOURCE));
357            CmsOrganizationalUnit root = driverManager.readOrganizationalUnit(dbc, "");
358            List<CmsOrganizationalUnit> children = driverManager.getOrganizationalUnits(dbc, root, true);
359
360            Set<CmsOrganizationalUnit> ous = new HashSet<>();
361            ous.add(root);
362            ous.addAll(children);
363            init(relations, ous);
364
365        }
366
367        /**
368         * Initializes the OU resource data.
369         *
370         * @param ouRelations the current list of OU relations
371         * @param ous the current list of OUs
372         */
373        public void init(Collection<CmsRelation> ouRelations, Collection<CmsOrganizationalUnit> ous) {
374
375            m_ousById.clear();
376            m_ousByAssignedResourcePaths.clear();
377            for (CmsOrganizationalUnit ou : ous) {
378                m_ousById.put(ou.getId(), ou);
379            }
380            for (CmsRelation rel : ouRelations) {
381                CmsOrganizationalUnit ou = m_ousById.get(rel.getSourceId());
382                if (ou != null) {
383                    m_ousByAssignedResourcePaths.put(new CmsPath(rel.getTargetPath()), ou);
384                }
385            }
386        }
387    }
388
389    /**
390     * The comparator used for comparing url name mapping entries by date.<p>
391     */
392    class UrlNameMappingComparator implements Comparator<CmsUrlNameMappingEntry> {
393
394        /**
395         * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
396         */
397        public int compare(CmsUrlNameMappingEntry o1, CmsUrlNameMappingEntry o2) {
398
399            long date1 = o1.getDateChanged();
400            long date2 = o2.getDateChanged();
401            if (date1 < date2) {
402                return -1;
403            }
404            if (date1 > date2) {
405                return +1;
406            }
407            return 0;
408        }
409    }
410
411    /**
412     * Enumeration class for the mode parameter in the
413     * {@link CmsDriverManager#readChangedResourcesInsideProject(CmsDbContext, CmsUUID, CmsReadChangedProjectResourceMode)}
414     * method.<p>
415     */
416    private static class CmsReadChangedProjectResourceMode {
417
418        /**
419         * Default constructor.<p>
420         */
421        protected CmsReadChangedProjectResourceMode() {
422
423            // noop
424        }
425    }
426
427    /** Request context attribute used to override the time used for time-based exclusive access checks. */
428    public static final String ATTR_EXCLUSIVE_ACCESS_CLOCK = "ATTR_EXCLUSIVE_ACCESS_CLOCK";
429
430    /** Attribute for signaling to the user driver that a specific OU should be initialized by fillDefaults. */
431    public static final String ATTR_INIT_OU = "INIT_OU";
432
433    /** DB context attribute used to communicate information about resource cacheability between various methods. */
434    public static final String ATTR_PERMISSION_NOCACHE = "ATTR_PERMISSION_NOCACHE";
435
436    /** Attribute login. */
437    public static final String ATTRIBUTE_LOGIN = "A_LOGIN";
438
439    /** Cache key for all properties. */
440    public static final String CACHE_ALL_PROPERTIES = "_CAP_";
441
442    /**
443     * Values indicating changes of a resource,
444     * ordered according to the scope of the change.
445     */
446    /** Value to indicate a change in access control entries of a resource. */
447    public static final int CHANGED_ACCESSCONTROL = 1;
448
449    /** Value to indicate a content change. */
450    public static final int CHANGED_CONTENT = 16;
451
452    /** Value to indicate a change in the lastmodified settings of a resource. */
453    public static final int CHANGED_LASTMODIFIED = 4;
454
455    /** Value to indicate a project change. */
456    public static final int CHANGED_PROJECT = 32;
457
458    /** Value to indicate a change in the resource data. */
459    public static final int CHANGED_RESOURCE = 8;
460
461    /** Value to indicate a change in the availability timeframe. */
462    public static final int CHANGED_TIMEFRAME = 2;
463
464    /** "cache" string in the configuration-file. */
465    public static final String CONFIGURATION_CACHE = "cache";
466
467    /** "db" string in the configuration-file. */
468    public static final String CONFIGURATION_DB = "db";
469
470    /** "driver.history" string in the configuration-file. */
471    public static final String CONFIGURATION_HISTORY = "driver.history";
472
473    /** "driver.project" string in the configuration-file. */
474    public static final String CONFIGURATION_PROJECT = "driver.project";
475
476    /** "subscription.vfs" string in the configuration file. */
477    public static final String CONFIGURATION_SUBSCRIPTION = "driver.subscription";
478
479    /** "driver.user" string in the configuration-file. */
480    public static final String CONFIGURATION_USER = "driver.user";
481
482    /** "driver.vfs" string in the configuration-file. */
483    public static final String CONFIGURATION_VFS = "driver.vfs";
484
485    /** DBC attribute key needed to fix publishing behavior involving siblings. */
486    public static final String KEY_CHANGED_AND_DELETED = "changedAndDeleted";
487
488    /** The vfs path of the loast and found folder. */
489    public static final String LOST_AND_FOUND_FOLDER = "/system/lost-found";
490
491    /** The maximum length of a VFS resource path. */
492    public static final int MAX_VFS_RESOURCE_PATH_LENGTH = 512;
493
494    /** Key for indicating no changes. */
495    public static final int NOTHING_CHANGED = 0;
496
497    /** Name of the configuration parameter to enable/disable logging to the CMS_LOG table. */
498    public static final String PARAM_LOG_TABLE_ENABLED = "log.table.enabled";
499
500    /** Indicates to ignore the resource path when matching resources. */
501    public static final String READ_IGNORE_PARENT = null;
502
503    /** Indicates to ignore the time value. */
504    public static final long READ_IGNORE_TIME = 0L;
505
506    /** Indicates to ignore the resource type when matching resources. */
507    public static final int READ_IGNORE_TYPE = -1;
508
509    /** Indicates to match resources NOT having the given state. */
510    public static final int READMODE_EXCLUDE_STATE = 8;
511
512    /** Indicates to match immediate children only. */
513    public static final int READMODE_EXCLUDE_TREE = 1;
514
515    /** Indicates to match resources NOT having the given type. */
516    public static final int READMODE_EXCLUDE_TYPE = 4;
517
518    /** Mode for reading project resources from the db. */
519    public static final int READMODE_IGNORESTATE = 0;
520
521    /** Indicates to match resources in given project only. */
522    public static final int READMODE_INCLUDE_PROJECT = 2;
523
524    /** Indicates to match all successors. */
525    public static final int READMODE_INCLUDE_TREE = 0;
526
527    /** Mode for reading project resources from the db. */
528    public static final int READMODE_MATCHSTATE = 1;
529
530    /** Indicates if only file resources should be read. */
531    public static final int READMODE_ONLY_FILES = 128;
532
533    /** Indicates if only folder resources should be read. */
534    public static final int READMODE_ONLY_FOLDERS = 64;
535
536    /** Mode for reading project resources from the db. */
537    public static final int READMODE_UNMATCHSTATE = 2;
538
539    /** Flag that can be used to disable the resource OU caching if necessary. */
540    public static boolean resourceOrgUnitCachingEnabled = true;
541
542    /** Prefix char for temporary files in the VFS. */
543    public static final String TEMP_FILE_PREFIX = "~";
544
545    /** Key to indicate complete update. */
546    public static final int UPDATE_ALL = 3;
547
548    /** Key to indicate update of resource record. */
549    public static final int UPDATE_RESOURCE = 4;
550
551    /** Key to indicate update of last modified project reference. */
552    public static final int UPDATE_RESOURCE_PROJECT = 6;
553
554    /** Key to indicate update of resource state. */
555    public static final int UPDATE_RESOURCE_STATE = 1;
556
557    /** Key to indicate update of resource state including the content date. */
558    public static final int UPDATE_RESOURCE_STATE_CONTENT = 7;
559
560    /** Key to indicate update of structure record. */
561    public static final int UPDATE_STRUCTURE = 5;
562
563    /** Key to indicate update of structure state. */
564    public static final int UPDATE_STRUCTURE_STATE = 2;
565
566    /** Map of pools defined in opencms.properties. */
567    protected static ConcurrentMap<String, CmsDbPoolV11> m_pools = Maps.newConcurrentMap();
568
569    /** The log object for this class. */
570    private static final Log LOG = CmsLog.getLog(CmsDriverManager.class);
571
572    /** Constant mode parameter to read all files and folders in the {@link #readChangedResourcesInsideProject(CmsDbContext, CmsUUID, CmsReadChangedProjectResourceMode)}} method. */
573    private static final CmsReadChangedProjectResourceMode RCPRM_FILES_AND_FOLDERS_MODE = new CmsReadChangedProjectResourceMode();
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_ONLY_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_FOLDERS_ONLY_MODE = new CmsReadChangedProjectResourceMode();
580
581    /** The history driver. */
582    private I_CmsHistoryDriver m_historyDriver;
583
584    /** The HTML link validator. */
585    private CmsRelationSystemValidator m_htmlLinkValidator;
586
587    /** The class used for cache key generation. */
588    private I_CmsCacheKey m_keyGenerator;
589
590    /** The lock manager. */
591    private CmsLockManager m_lockManager;
592
593    /** The log entry cache. */
594    private List<CmsLogEntry> m_log = new ArrayList<CmsLogEntry>();
595
596    /** Local reference to the memory monitor to avoid multiple lookups through the OpenCms singleton. */
597    private CmsMemoryMonitor m_monitor;
598
599    /** The project driver. */
600    private I_CmsProjectDriver m_projectDriver;
601
602    /** The the configuration read from the <code>opencms.properties</code> file. */
603    private CmsParameterConfiguration m_propertyConfiguration;
604
605    /** the publish engine. */
606    private CmsPublishEngine m_publishEngine;
607
608    /** Object used for synchronizing updates to the user publish list. */
609    private Object m_publishListUpdateLock = new Object();
610
611    /** The security manager (for access checks). */
612    private CmsSecurityManager m_securityManager;
613
614    /** The sql manager. */
615    private CmsSqlManager m_sqlManager;
616
617    /** The subscription driver. */
618    private I_CmsSubscriptionDriver m_subscriptionDriver;
619
620    /** The user driver. */
621    private I_CmsUserDriver m_userDriver;
622
623    /** The VFS driver. */
624    private I_CmsVfsDriver m_vfsDriver;
625
626    /**
627     * Private constructor, initializes some required member variables.<p>
628     */
629    private CmsDriverManager() {
630
631        // intentionally left blank
632    }
633
634    /**
635     * Reads the required configurations from the opencms.properties file and creates
636     * the various drivers to access the cms resources.<p>
637     *
638     * The initialization process of the driver manager and its drivers is split into
639     * the following phases:
640     * <ul>
641     * <li>the database pool configuration is read</li>
642     * <li>a plain and empty driver manager instance is created</li>
643     * <li>an instance of each driver is created</li>
644     * <li>the driver manager is passed to each driver during initialization</li>
645     * <li>finally, the driver instances are passed to the driver manager during initialization</li>
646     * </ul>
647     *
648     * @param configurationManager the configuration manager
649     * @param securityManager the security manager
650     * @param runtimeInfoFactory the initialized OpenCms runtime info factory
651     * @param publishEngine the publish engine
652     *
653     * @return CmsDriverManager the instantiated driver manager
654     * @throws CmsInitException if the driver manager couldn't be instantiated
655     */
656    public static CmsDriverManager newInstance(
657        CmsConfigurationManager configurationManager,
658        CmsSecurityManager securityManager,
659        I_CmsDbContextFactory runtimeInfoFactory,
660        CmsPublishEngine publishEngine)
661    throws CmsInitException {
662
663        // read the opencms.properties from the configuration
664        CmsParameterConfiguration config = configurationManager.getConfiguration();
665
666        CmsDriverManager driverManager = null;
667        try {
668            // create a driver manager instance
669            driverManager = new CmsDriverManager();
670            if (CmsLog.INIT.isInfoEnabled()) {
671                CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_DRIVER_MANAGER_START_PHASE1_0));
672            }
673            if (runtimeInfoFactory == null) {
674                throw new CmsInitException(
675                    org.opencms.main.Messages.get().container(org.opencms.main.Messages.ERR_CRITICAL_NO_DB_CONTEXT_0));
676            }
677        } catch (Exception exc) {
678            CmsMessageContainer message = Messages.get().container(Messages.LOG_ERR_DRIVER_MANAGER_START_0);
679            if (LOG.isFatalEnabled()) {
680                LOG.fatal(message.key(), exc);
681            }
682            throw new CmsInitException(message, exc);
683        }
684
685        // store the configuration
686        driverManager.m_propertyConfiguration = config;
687
688        // set the security manager
689        driverManager.m_securityManager = securityManager;
690
691        // set the lock manager
692        driverManager.m_lockManager = new CmsLockManager(driverManager);
693
694        // create and set the sql manager
695        driverManager.m_sqlManager = new CmsSqlManager(driverManager);
696
697        // set the publish engine
698        driverManager.m_publishEngine = publishEngine;
699
700        if (CmsLog.INIT.isInfoEnabled()) {
701            CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_DRIVER_MANAGER_START_PHASE2_0));
702        }
703
704        // read the pool names to initialize
705        List<String> driverPoolNames = config.getList(CmsDriverManager.CONFIGURATION_DB + ".pools");
706        if (CmsLog.INIT.isInfoEnabled()) {
707            String names = "";
708            for (String name : driverPoolNames) {
709                names += name + " ";
710            }
711            CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_DRIVER_MANAGER_START_POOLS_1, names));
712        }
713
714        // initialize each pool
715        for (String name : driverPoolNames) {
716            driverManager.newPoolInstance(config, name);
717        }
718
719        // initialize the runtime info factory with the generated driver manager
720        runtimeInfoFactory.initialize(driverManager);
721
722        if (CmsLog.INIT.isInfoEnabled()) {
723            CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_DRIVER_MANAGER_START_PHASE3_0));
724        }
725
726        // store the access objects
727        CmsDbContext dbc = runtimeInfoFactory.getDbContext();
728        driverManager.m_vfsDriver = (I_CmsVfsDriver)driverManager.createDriver(
729            dbc,
730            configurationManager,
731            config,
732            CONFIGURATION_VFS,
733            ".vfs.driver");
734        dbc.clear();
735
736        dbc = runtimeInfoFactory.getDbContext();
737        driverManager.m_userDriver = (I_CmsUserDriver)driverManager.createDriver(
738            dbc,
739            configurationManager,
740            config,
741            CONFIGURATION_USER,
742            ".user.driver");
743        dbc.clear();
744
745        dbc = runtimeInfoFactory.getDbContext();
746        driverManager.m_projectDriver = (I_CmsProjectDriver)driverManager.createDriver(
747            dbc,
748            configurationManager,
749            config,
750            CONFIGURATION_PROJECT,
751            ".project.driver");
752        dbc.clear();
753
754        dbc = runtimeInfoFactory.getDbContext();
755        driverManager.m_historyDriver = (I_CmsHistoryDriver)driverManager.createDriver(
756            dbc,
757            configurationManager,
758            config,
759            CONFIGURATION_HISTORY,
760            ".history.driver");
761        dbc.clear();
762
763        dbc = runtimeInfoFactory.getDbContext();
764        try {
765            // we wrap this in a try-catch because otherwise it would fail during the update
766            // process, since the subscription driver configuration does not exist at that point.
767            driverManager.m_subscriptionDriver = (I_CmsSubscriptionDriver)driverManager.createDriver(
768                dbc,
769                configurationManager,
770                config,
771                CONFIGURATION_SUBSCRIPTION,
772                ".subscription.driver");
773        } catch (IndexOutOfBoundsException npe) {
774            LOG.warn("Could not instantiate subscription driver!");
775            LOG.warn(npe.getLocalizedMessage(), npe);
776        }
777        dbc.clear();
778
779        // register the driver manager for required events
780        org.opencms.main.OpenCms.addCmsEventListener(
781            driverManager,
782            new int[] {
783                I_CmsEventListener.EVENT_UPDATE_EXPORTS,
784                I_CmsEventListener.EVENT_CLEAR_CACHES,
785                I_CmsEventListener.EVENT_CLEAR_PRINCIPAL_CACHES,
786                I_CmsEventListener.EVENT_USER_MODIFIED,
787                I_CmsEventListener.EVENT_PUBLISH_PROJECT});
788
789        // return the configured driver manager
790        return driverManager;
791    }
792
793    /**
794     * Adds an alias entry.<p>
795     *
796     * @param dbc the database context
797     * @param project the current project
798     * @param alias the alias to add
799     *
800     * @throws CmsException if something goes wrong
801     */
802    public void addAlias(CmsDbContext dbc, CmsProject project, CmsAlias alias) throws CmsException {
803
804        I_CmsVfsDriver vfsDriver = getVfsDriver(dbc);
805        vfsDriver.insertAlias(dbc, project, alias);
806    }
807
808    /**
809     * Adds a new relation to the given resource.<p>
810     *
811     * @param dbc the database context
812     * @param resource the resource to add the relation to
813     * @param target the target of the relation
814     * @param type the type of the relation
815     * @param importCase if importing relations
816     *
817     * @throws CmsException if something goes wrong
818     */
819    public void addRelationToResource(
820        CmsDbContext dbc,
821        CmsResource resource,
822        CmsResource target,
823        CmsRelationType type,
824        boolean importCase)
825    throws CmsException {
826
827        if (type.isDefinedInContent()) {
828            throw new CmsIllegalArgumentException(
829                Messages.get().container(
830                    Messages.ERR_ADD_RELATION_IN_CONTENT_3,
831                    dbc.removeSiteRoot(resource.getRootPath()),
832                    dbc.removeSiteRoot(target.getRootPath()),
833                    type.getLocalizedName(dbc.getRequestContext().getLocale())));
834        }
835        CmsRelation relation = new CmsRelation(resource, target, type);
836        getVfsDriver(dbc).createRelation(dbc, dbc.currentProject().getUuid(), relation);
837        if (importCase) {
838            // fire the reindexing event, since - if offline indexing is not stopped,
839            // the content could be indexed without relations already and thus miss categories.
840            Map<String, Object> data = new HashMap<String, Object>(2);
841            data.put(I_CmsEventListener.KEY_PROJECTID, dbc.currentProject().getId());
842            data.put(I_CmsEventListener.KEY_RESOURCES, Collections.singletonList(resource));
843            I_CmsReport report = null;
844            if (dbc.getRequestContext() != null) {
845                report = new CmsLogReport(dbc.getRequestContext().getLocale(), getClass());
846            } else {
847                report = new CmsLogReport(CmsLocaleManager.getDefaultLocale(), getClass());
848            }
849            data.put(I_CmsEventListener.KEY_REPORT, report);
850            data.put(I_CmsEventListener.KEY_REINDEX_RELATED, Boolean.TRUE);
851            OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_REINDEX_OFFLINE, data));
852        } else {
853            // log it
854            log(
855                dbc,
856                new CmsLogEntry(
857                    dbc,
858                    resource.getStructureId(),
859                    CmsLogEntryType.RESOURCE_ADD_RELATION,
860                    new String[] {relation.getSourcePath(), relation.getTargetPath()}),
861                false);
862            // touch the resource
863            setDateLastModified(dbc, resource, System.currentTimeMillis());
864        }
865    }
866
867    /**
868     * Adds a resource to the given organizational unit.<p>
869     *
870     * @param dbc the current db context
871     * @param orgUnit the organizational unit to add the resource to
872     * @param resource the resource that is to be added to the organizational unit
873     *
874     * @throws CmsException if something goes wrong
875     *
876     * @see org.opencms.security.CmsOrgUnitManager#addResourceToOrgUnit(CmsObject, String, String)
877     * @see org.opencms.security.CmsOrgUnitManager#addResourceToOrgUnit(CmsObject, String, String)
878     */
879    public void addResourceToOrgUnit(CmsDbContext dbc, CmsOrganizationalUnit orgUnit, CmsResource resource)
880    throws CmsException {
881
882        m_monitor.flushCache(CmsMemoryMonitor.CacheType.HAS_ROLE, CmsMemoryMonitor.CacheType.ROLE_LIST);
883        getUserDriver(dbc).addResourceToOrganizationalUnit(dbc, orgUnit, resource);
884    }
885
886    /**
887     * Adds a user to a group.<p>
888     *
889     * @param dbc the current database context
890     * @param username the name of the user that is to be added to the group
891     * @param groupname the name of the group
892     * @param readRoles if reading roles or groups
893     *
894     * @throws CmsException if operation was not successful
895     * @throws CmsDbEntryNotFoundException if the given user or the given group was not found
896     *
897     * @see #removeUserFromGroup(CmsDbContext, String, String, boolean)
898     */
899    public void addUserToGroup(CmsDbContext dbc, String username, String groupname, boolean readRoles)
900    throws CmsException, CmsDbEntryNotFoundException {
901
902        //check if group exists
903        CmsGroup group = readGroup(dbc, groupname);
904        if (group == null) {
905            // the group does not exists
906            throw new CmsDbEntryNotFoundException(Messages.get().container(Messages.ERR_UNKNOWN_GROUP_1, groupname));
907        }
908        if (group.isVirtual() && !readRoles) {
909            String roleName = CmsRole.valueOf(group).getGroupName();
910            if (!userInGroup(dbc, username, roleName, true)) {
911                addUserToGroup(dbc, username, roleName, true);
912                return;
913            }
914        }
915        if (group.isVirtual()) {
916            // this is an hack to prevent unlimited recursive calls
917            readRoles = false;
918        }
919        if ((readRoles && !group.isRole()) || (!readRoles && group.isRole())) {
920            // we want a role but we got a group, or the other way
921            throw new CmsDbEntryNotFoundException(Messages.get().container(Messages.ERR_UNKNOWN_GROUP_1, groupname));
922        }
923        if (userInGroup(dbc, username, groupname, readRoles)) {
924            // the user is already member of the group
925            return;
926        }
927        //check if the user exists
928        CmsUser user = readUser(dbc, username);
929        if (user == null) {
930            // the user does not exists
931            throw new CmsDbEntryNotFoundException(Messages.get().container(Messages.ERR_UNKNOWN_USER_1, username));
932        }
933
934        // if adding an user to a role
935        if (readRoles) {
936            CmsRole role = CmsRole.valueOf(group);
937            // a role can only be set if the user has the given role
938            m_securityManager.checkRole(dbc, role);
939            // now we check if we already have the role
940            if (m_securityManager.hasRole(dbc, user, role)) {
941                // do nothing
942                return;
943            }
944            // and now we need to remove all possible child-roles
945            List<CmsRole> children = role.getChildren(true);
946            Iterator<CmsGroup> itUserGroups = getGroupsOfUser(
947                dbc,
948                username,
949                group.getOuFqn(),
950                true,
951                true,
952                true,
953                dbc.getRequestContext().getRemoteAddress()).iterator();
954            while (itUserGroups.hasNext()) {
955                CmsGroup roleGroup = itUserGroups.next();
956                if (children.contains(CmsRole.valueOf(roleGroup))) {
957                    // remove only child roles
958                    removeUserFromGroup(dbc, username, roleGroup.getName(), true);
959                }
960            }
961            // update virtual groups
962            Iterator<CmsGroup> it = getVirtualGroupsForRole(dbc, role).iterator();
963            while (it.hasNext()) {
964                CmsGroup virtualGroup = it.next();
965                // here we say readroles = true, to prevent an unlimited recursive calls
966                addUserToGroup(dbc, username, virtualGroup.getName(), true);
967            }
968        }
969
970        //add this user to the group
971        getUserDriver(dbc).createUserInGroup(dbc, user.getId(), group.getId());
972
973        // flush the cache
974        if (readRoles) {
975            m_monitor.flushCache(CmsMemoryMonitor.CacheType.HAS_ROLE, CmsMemoryMonitor.CacheType.ROLE_LIST);
976        }
977        m_monitor.flushUserGroups(user.getId());
978        m_monitor.flushCache(CmsMemoryMonitor.CacheType.USER_LIST);
979
980        if (!dbc.getProjectId().isNullUUID() && !CmsProject.ONLINE_PROJECT_ID.equals(dbc.getProjectId())) {
981            // user modified event is not needed
982            return;
983        }
984        // fire user modified event
985        Map<String, Object> eventData = new HashMap<String, Object>();
986        eventData.put(I_CmsEventListener.KEY_USER_ID, user.getId().toString());
987        eventData.put(I_CmsEventListener.KEY_USER_NAME, user.getName());
988        eventData.put(I_CmsEventListener.KEY_USER_ID, user.getId().toString());
989        eventData.put(I_CmsEventListener.KEY_GROUP_NAME, group.getName());
990        eventData.put(
991            I_CmsEventListener.KEY_USER_ACTION,
992            I_CmsEventListener.VALUE_USER_MODIFIED_ACTION_ADD_USER_TO_GROUP);
993        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_USER_MODIFIED, eventData));
994    }
995
996    /**
997     * Changes the lock of a resource to the current user,
998     * that is "steals" the lock from another user.<p>
999     *
1000     * @param dbc the current database context
1001     * @param resource the resource to change the lock for
1002     * @param lockType the new lock type to set
1003     *
1004     * @throws CmsException if something goes wrong
1005     * @throws CmsSecurityException if something goes wrong
1006     *
1007     *
1008     * @see CmsObject#changeLock(String)
1009     * @see I_CmsResourceType#changeLock(CmsObject, CmsSecurityManager, CmsResource)
1010     *
1011     * @see CmsSecurityManager#hasPermissions(CmsRequestContext, CmsResource, CmsPermissionSet, boolean, CmsResourceFilter)
1012     */
1013    public void changeLock(CmsDbContext dbc, CmsResource resource, CmsLockType lockType)
1014    throws CmsException, CmsSecurityException {
1015
1016        // get the current lock
1017        CmsLock currentLock = getLock(dbc, resource);
1018        // check if the resource is locked at all
1019        if (currentLock.getEditionLock().isUnlocked() && currentLock.getSystemLock().isUnlocked()) {
1020            throw new CmsLockException(
1021                Messages.get().container(
1022                    Messages.ERR_CHANGE_LOCK_UNLOCKED_RESOURCE_1,
1023                    dbc.getRequestContext().getSitePath(resource)));
1024        } else if ((lockType == CmsLockType.EXCLUSIVE)
1025            && currentLock.isExclusiveOwnedInProjectBy(dbc.currentUser(), dbc.currentProject())) {
1026            // the current lock requires no change
1027            return;
1028        }
1029
1030        // duplicate logic from CmsSecurityManager#hasPermissions() because lock state can't be ignored
1031        // if another user has locked the file, the current user can never get WRITE permissions with the default check
1032        int denied = 0;
1033
1034        // check if the current user is vfs manager
1035        boolean canIgnorePermissions = m_securityManager.hasRoleForResource(
1036            dbc,
1037            dbc.currentUser(),
1038            CmsRole.VFS_MANAGER,
1039            resource);
1040        // if the resource type is jsp
1041        // write is only allowed for developers
1042        if (!canIgnorePermissions && (CmsResourceTypeJsp.isJsp(resource))) {
1043            if (!m_securityManager.hasRoleForResource(dbc, dbc.currentUser(), CmsRole.VFS_MANAGER, resource)) {
1044                denied |= CmsPermissionSet.PERMISSION_WRITE;
1045            }
1046        }
1047        CmsPermissionSetCustom permissions;
1048        if (canIgnorePermissions) {
1049            // if the current user is administrator, anything is allowed
1050            permissions = new CmsPermissionSetCustom(~0);
1051        } else {
1052            // otherwise, get the permissions from the access control list
1053            permissions = getPermissions(dbc, resource, dbc.currentUser());
1054        }
1055        // revoke the denied permissions
1056        permissions.denyPermissions(denied);
1057        // now check if write permission is granted
1058        if ((CmsPermissionSet.ACCESS_WRITE.getPermissions()
1059            & permissions.getPermissions()) != CmsPermissionSet.ACCESS_WRITE.getPermissions()) {
1060            // check failed, throw exception
1061            m_securityManager.checkPermissions(
1062                dbc.getRequestContext(),
1063                resource,
1064                CmsPermissionSet.ACCESS_WRITE,
1065                I_CmsPermissionHandler.PERM_DENIED);
1066        }
1067        // if we got here write permission is granted on the target
1068
1069        // remove the old lock
1070        m_lockManager.removeResource(dbc, resource, true, lockType.isSystem());
1071        // apply the new lock
1072        lockResource(dbc, resource, lockType);
1073    }
1074
1075    /**
1076     * Returns a list with all sub resources of a given folder that have set the given property,
1077     * matching the current property's value with the given old value and replacing it by a given new value.<p>
1078     *
1079     * @param dbc the current database context
1080     * @param resource the resource on which property definition values are changed
1081     * @param propertyDefinition the name of the propertydefinition to change the value
1082     * @param oldValue the old value of the propertydefinition
1083     * @param newValue the new value of the propertydefinition
1084     * @param recursive if true, change the property value on the resource and recursively all property values on
1085     *                     sub-resources (only for folders)
1086     * @return a list with the <code>{@link CmsResource}</code>'s where the property value has been changed
1087     *
1088     * @throws CmsVfsException for now only when the search for the oldvalue failed.
1089     * @throws CmsException if operation was not successful
1090     */
1091    public List<CmsResource> changeResourcesInFolderWithProperty(
1092        CmsDbContext dbc,
1093        CmsResource resource,
1094        String propertyDefinition,
1095        String oldValue,
1096        String newValue,
1097        boolean recursive)
1098    throws CmsVfsException, CmsException {
1099
1100        CmsResourceFilter filter = CmsResourceFilter.IGNORE_EXPIRATION;
1101        // collect the resources to look up
1102        List<CmsResource> resources = new ArrayList<CmsResource>();
1103        if (recursive) {
1104            // read the files in the folder
1105            resources = readResourcesWithProperty(dbc, resource, propertyDefinition, null, filter);
1106            // add the folder itself
1107            resources.add(resource);
1108        } else {
1109            resources.add(resource);
1110        }
1111
1112        Pattern oldPattern;
1113        try {
1114            // remove the place holder if available
1115            String tmpOldValue = oldValue;
1116            if (tmpOldValue.contains(CmsStringUtil.PLACEHOLDER_START)
1117                && tmpOldValue.contains(CmsStringUtil.PLACEHOLDER_END)) {
1118                tmpOldValue = tmpOldValue.replace(CmsStringUtil.PLACEHOLDER_START, "");
1119                tmpOldValue = tmpOldValue.replace(CmsStringUtil.PLACEHOLDER_END, "");
1120            }
1121            // compile regular expression pattern
1122            oldPattern = Pattern.compile(tmpOldValue);
1123        } catch (PatternSyntaxException e) {
1124            throw new CmsVfsException(
1125                Messages.get().container(
1126                    Messages.ERR_CHANGE_RESOURCES_IN_FOLDER_WITH_PROP_4,
1127                    new Object[] {propertyDefinition, oldValue, newValue, resource.getRootPath()}),
1128                e);
1129        }
1130
1131        List<CmsResource> changedResources = new ArrayList<CmsResource>(resources.size());
1132        // create permission set and filter to check each resource
1133        CmsPermissionSet perm = CmsPermissionSet.ACCESS_WRITE;
1134        for (int i = 0; i < resources.size(); i++) {
1135            // loop through found resources and check property values
1136            CmsResource res = resources.get(i);
1137            // check resource state and permissions
1138            try {
1139                m_securityManager.checkPermissions(dbc, res, perm, true, filter);
1140            } catch (Exception e) {
1141                // resource is deleted or not writable for current user
1142                continue;
1143            }
1144            CmsProperty property = readPropertyObject(dbc, res, propertyDefinition, false);
1145            String propertyValue = property.getValue();
1146            boolean changed = false;
1147            if ((propertyValue != null) && oldPattern.matcher(propertyValue).matches()) {
1148                // apply the place holder content
1149                String tmpNewValue = CmsStringUtil.transformValues(oldValue, newValue, propertyValue);
1150                // change structure value
1151                property.setStructureValue(tmpNewValue);
1152                changed = true;
1153            }
1154            if (changed) {
1155                // write property object if something has changed
1156                writePropertyObject(dbc, res, property);
1157                changedResources.add(res);
1158            }
1159        }
1160        return changedResources;
1161    }
1162
1163    /**
1164     * Changes the resource flags of a resource.<p>
1165     *
1166     * The resource flags are used to indicate various "special" conditions
1167     * for a resource. Most notably, the "internal only" setting which signals
1168     * that a resource can not be directly requested with it's URL.<p>
1169     *
1170     * @param dbc the current database context
1171     * @param resource the resource to change the flags for
1172     * @param flags the new resource flags for this resource
1173     *
1174     * @throws CmsException if something goes wrong
1175     *
1176     * @see CmsObject#chflags(String, int)
1177     * @see I_CmsResourceType#chflags(CmsObject, CmsSecurityManager, CmsResource, int)
1178     */
1179    public void chflags(CmsDbContext dbc, CmsResource resource, int flags) throws CmsException {
1180
1181        // must operate on a clone to ensure resource is not modified in case permissions are not granted
1182        CmsResource clone = (CmsResource)resource.clone();
1183        clone.setFlags(flags);
1184        // log it
1185        log(
1186            dbc,
1187            new CmsLogEntry(
1188                dbc,
1189                resource.getStructureId(),
1190                CmsLogEntryType.RESOURCE_FLAGS,
1191                new String[] {resource.getRootPath()}),
1192            false);
1193        // write it
1194        writeResource(dbc, clone);
1195    }
1196
1197    /**
1198     * Changes the resource type of a resource.<p>
1199     *
1200     * OpenCms handles resources according to the resource type,
1201     * not the file suffix. This is e.g. why a JSP in OpenCms can have the
1202     * suffix ".html" instead of ".jsp" only. Changing the resource type
1203     * makes sense e.g. if you want to make a plain text file a JSP resource,
1204     * or a binary file an image, etc.<p>
1205     *
1206     * @param dbc the current database context
1207     * @param resource the resource to change the type for
1208     * @param type the new resource type for this resource
1209     *
1210     * @throws CmsException if something goes wrong
1211     *
1212     * @see CmsObject#chtype(String, int)
1213     * @see I_CmsResourceType#chtype(CmsObject, CmsSecurityManager, CmsResource, int)
1214     */
1215    @SuppressWarnings({"javadoc", "deprecation"})
1216    public void chtype(CmsDbContext dbc, CmsResource resource, int type) throws CmsException {
1217
1218        // must operate on a clone to ensure resource is not modified in case permissions are not granted
1219        CmsResource clone = (CmsResource)resource.clone();
1220        I_CmsResourceType newType = OpenCms.getResourceManager().getResourceType(type);
1221        clone.setType(newType.getTypeId());
1222        // log it
1223        log(
1224            dbc,
1225            new CmsLogEntry(
1226                dbc,
1227                resource.getStructureId(),
1228                CmsLogEntryType.RESOURCE_TYPE,
1229                new String[] {resource.getRootPath()}),
1230            false);
1231        // write it
1232        writeResource(dbc, clone);
1233    }
1234
1235    /**
1236     * Cleans up the publish history entries according to the given filter.
1237     *
1238     * @param dbc the database context
1239     * @param filter the filter
1240     * @return the number of cleaned up rows
1241     * @throws CmsDataAccessException if something goes wrong
1242     */
1243    public int cleanupPublishHistory(CmsDbContext dbc, CmsPublishHistoryCleanupFilter filter)
1244    throws CmsDataAccessException {
1245
1246        int result = m_projectDriver.cleanupPublishHistory(dbc, filter);
1247        if (filter.getMode() == CmsPublishHistoryCleanupFilter.Mode.single) {
1248            OpenCms.getMemoryMonitor().cachePublishedResources(filter.getHistoryId().toString(), null);
1249        } else {
1250            OpenCms.getMemoryMonitor().flushCache(CmsMemoryMonitor.CacheType.PUBLISHED_RESOURCES);
1251        }
1252        return result;
1253    }
1254
1255    /**
1256     * @see org.opencms.main.I_CmsEventListener#cmsEvent(org.opencms.main.CmsEvent)
1257     */
1258    public void cmsEvent(CmsEvent event) {
1259
1260        if (LOG.isDebugEnabled()) {
1261            LOG.debug(Messages.get().getBundle().key(Messages.LOG_CMS_EVENT_1, Integer.valueOf(event.getType())));
1262        }
1263
1264        I_CmsReport report;
1265        CmsDbContext dbc;
1266
1267        switch (event.getType()) {
1268
1269            case I_CmsEventListener.EVENT_UPDATE_EXPORTS:
1270                dbc = (CmsDbContext)event.getData().get(I_CmsEventListener.KEY_DBCONTEXT);
1271                updateExportPoints(dbc);
1272                break;
1273
1274            case I_CmsEventListener.EVENT_PUBLISH_PROJECT:
1275                CmsUUID publishHistoryId = new CmsUUID((String)event.getData().get(I_CmsEventListener.KEY_PUBLISHID));
1276                report = (I_CmsReport)event.getData().get(I_CmsEventListener.KEY_REPORT);
1277                dbc = (CmsDbContext)event.getData().get(I_CmsEventListener.KEY_DBCONTEXT);
1278                m_monitor.clearCacheForPublishing();
1279                writeExportPoints(dbc, report, publishHistoryId);
1280                break;
1281
1282            case I_CmsEventListener.EVENT_CLEAR_CACHES:
1283                m_monitor.clearCache();
1284                break;
1285            case I_CmsEventListener.EVENT_CLEAR_PRINCIPAL_CACHES:
1286                m_monitor.clearPrincipalsCache();
1287                break;
1288            case I_CmsEventListener.EVENT_USER_MODIFIED:
1289                String action = (String)event.getData().get(I_CmsEventListener.KEY_USER_ACTION);
1290                m_monitor.flushCache(
1291                    CacheType.USER,
1292                    CacheType.GROUP,
1293                    CacheType.ORG_UNIT,
1294                    CacheType.ACL,
1295                    CacheType.PERMISSION,
1296                    CacheType.USER_LIST);
1297                if (I_CmsEventListener.VALUE_USER_MODIFIED_ACTION_ADD_USER_TO_GROUP.equals(action)
1298                    || I_CmsEventListener.VALUE_USER_MODIFIED_ACTION_REMOVE_USER_FROM_GROUP.equals(action)
1299                    || I_CmsEventListener.VALUE_USER_MODIFIED_ACTION_SET_OU.equals(action)) {
1300
1301                    Object userIdObj = event.getData().get(I_CmsEventListener.KEY_USER_ID);
1302                    if (userIdObj != null) {
1303                        CmsUUID userId = null;
1304                        if (userIdObj instanceof CmsUUID) {
1305                            userId = (CmsUUID)userIdObj;
1306                        } else if (userIdObj instanceof String) {
1307                            try {
1308                                userId = new CmsUUID(userIdObj.toString());
1309                            } catch (Exception e) {
1310                                LOG.error(e.getLocalizedMessage(), e);
1311                            }
1312                        }
1313                        if (userId != null) {
1314                            m_monitor.flushUserGroups(userId);
1315                        }
1316                    } else {
1317                        m_monitor.flushCache(CacheType.USERGROUPS);
1318                    }
1319                    m_monitor.flushCache(CacheType.HAS_ROLE, CacheType.ROLE_LIST);
1320                }
1321                break;
1322            default:
1323                // noop
1324        }
1325    }
1326
1327    /**
1328     * Copies the access control entries of a given resource to a destination resource.<p>
1329     *
1330     * Already existing access control entries of the destination resource are removed.<p>
1331     *
1332     * @param dbc the current database context
1333     * @param source the resource to copy the access control entries from
1334     * @param destination the resource to which the access control entries are copied
1335     * @param updateLastModifiedInfo if true, user and date "last modified" information on the target resource will be updated
1336     *
1337     * @throws CmsException if something goes wrong
1338     */
1339    public void copyAccessControlEntries(
1340        CmsDbContext dbc,
1341        CmsResource source,
1342        CmsResource destination,
1343        boolean updateLastModifiedInfo)
1344    throws CmsException {
1345
1346        // get the entries to copy
1347        ListIterator<CmsAccessControlEntry> aceList = getUserDriver(
1348            dbc).readAccessControlEntries(dbc, dbc.currentProject(), source.getResourceId(), false).listIterator();
1349
1350        // remove the current entries from the destination
1351        getUserDriver(dbc).removeAccessControlEntries(dbc, dbc.currentProject(), destination.getResourceId());
1352
1353        // now write the new entries
1354        while (aceList.hasNext()) {
1355            CmsAccessControlEntry ace = aceList.next();
1356            getUserDriver(dbc).createAccessControlEntry(
1357                dbc,
1358                dbc.currentProject(),
1359                destination.getResourceId(),
1360                ace.getPrincipal(),
1361                ace.getPermissions().getAllowedPermissions(),
1362                ace.getPermissions().getDeniedPermissions(),
1363                ace.getFlags());
1364        }
1365
1366        // log it
1367        log(
1368            dbc,
1369            new CmsLogEntry(
1370                dbc,
1371                destination.getStructureId(),
1372                CmsLogEntryType.RESOURCE_PERMISSIONS,
1373                new String[] {destination.getRootPath()}),
1374            false);
1375
1376        // update the "last modified" information
1377        if (updateLastModifiedInfo) {
1378            setDateLastModified(dbc, destination, destination.getDateLastModified());
1379        }
1380
1381        // clear the cache
1382        m_monitor.clearAccessControlListCache();
1383
1384        // fire a resource modification event
1385        Map<String, Object> data = new HashMap<String, Object>(2);
1386        data.put(I_CmsEventListener.KEY_RESOURCE, destination);
1387        data.put(I_CmsEventListener.KEY_CHANGE, Integer.valueOf(CHANGED_ACCESSCONTROL));
1388        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_MODIFIED, data));
1389    }
1390
1391    /**
1392     * Copies a resource.<p>
1393     *
1394     * You must ensure that the destination path is an absolute, valid and
1395     * existing VFS path. Relative paths from the source are currently not supported.<p>
1396     *
1397     * In case the target resource already exists, it is overwritten with the
1398     * source resource.<p>
1399     *
1400     * The <code>siblingMode</code> parameter controls how to handle siblings
1401     * during the copy operation.
1402     * Possible values for this parameter are:
1403     * <ul>
1404     * <li><code>{@link org.opencms.file.CmsResource#COPY_AS_NEW}</code></li>
1405     * <li><code>{@link org.opencms.file.CmsResource#COPY_AS_SIBLING}</code></li>
1406     * <li><code>{@link org.opencms.file.CmsResource#COPY_PRESERVE_SIBLING}</code></li>
1407     * </ul><p>
1408     *
1409     * @param dbc the current database context
1410     * @param source the resource to copy
1411     * @param destination the name of the copy destination with complete path
1412     * @param siblingMode indicates how to handle siblings during copy
1413     *
1414     * @throws CmsException if something goes wrong
1415     * @throws CmsIllegalArgumentException if the <code>source</code> argument is <code>null</code>
1416     *
1417     * @see CmsObject#copyResource(String, String, CmsResource.CmsResourceCopyMode)
1418     * @see I_CmsResourceType#copyResource(CmsObject, CmsSecurityManager, CmsResource, String, CmsResource.CmsResourceCopyMode)
1419     */
1420    public void copyResource(
1421        CmsDbContext dbc,
1422        CmsResource source,
1423        String destination,
1424        CmsResource.CmsResourceCopyMode siblingMode)
1425    throws CmsException, CmsIllegalArgumentException {
1426
1427        // check the sibling mode to see if this resource has to be copied as a sibling
1428        boolean copyAsSibling = false;
1429
1430        // siblings of folders are not supported
1431        if (!source.isFolder()) {
1432            // if the "copy as sibling" mode is used, set the flag to true
1433            if (siblingMode == CmsResource.COPY_AS_SIBLING) {
1434                copyAsSibling = true;
1435            }
1436            // if the mode is "preserve siblings", we have to check the sibling counter
1437            if (siblingMode == CmsResource.COPY_PRESERVE_SIBLING) {
1438                if (source.getSiblingCount() > 1) {
1439                    copyAsSibling = true;
1440                }
1441            }
1442        }
1443
1444        // read the source properties
1445        List<CmsProperty> properties = readPropertyObjects(dbc, source, false);
1446
1447        if (copyAsSibling) {
1448            // create a sibling of the source file at the destination
1449            createSibling(dbc, source, destination, properties);
1450            // after the sibling is created the copy operation is finished
1451            return;
1452        }
1453
1454        // prepare the content if required
1455        byte[] content = null;
1456        if (source.isFile()) {
1457            if (source instanceof CmsFile) {
1458                // resource already is a file
1459                content = ((CmsFile)source).getContents();
1460            }
1461            if ((content == null) || (content.length < 1)) {
1462                // no known content yet - read from database
1463                content = getVfsDriver(dbc).readContent(dbc, dbc.currentProject().getUuid(), source.getResourceId());
1464            }
1465        }
1466
1467        // determine destination folder
1468        String destinationFoldername = CmsResource.getParentFolder(destination);
1469
1470        // read the destination folder (will also check read permissions)
1471        CmsFolder destinationFolder = m_securityManager.readFolder(
1472            dbc,
1473            destinationFoldername,
1474            CmsResourceFilter.IGNORE_EXPIRATION);
1475
1476        // no further permission check required here, will be done in createResource()
1477
1478        // set user and creation time stamps
1479        long currentTime = System.currentTimeMillis();
1480        long dateLastModified;
1481        CmsUUID userLastModified;
1482        if (source.isFolder()) {
1483            // folders always get a new date and user when they are copied
1484            dateLastModified = currentTime;
1485            userLastModified = dbc.currentUser().getId();
1486        } else {
1487            // files keep the date and user last modified from the source
1488            dateLastModified = source.getDateLastModified();
1489            userLastModified = source.getUserLastModified();
1490        }
1491
1492        // check the resource flags
1493        int flags = source.getFlags();
1494        if (source.isLabeled()) {
1495            // reset "labeled" link flag for new resource
1496            flags &= ~CmsResource.FLAG_LABELED;
1497        }
1498
1499        // create the new resource
1500        CmsResource newResource = new CmsResource(
1501            new CmsUUID(),
1502            new CmsUUID(),
1503            destination,
1504            source.getTypeId(),
1505            source.isFolder(),
1506            flags,
1507            dbc.currentProject().getUuid(),
1508            CmsResource.STATE_NEW,
1509            currentTime,
1510            dbc.currentUser().getId(),
1511            dateLastModified,
1512            userLastModified,
1513            source.getDateReleased(),
1514            source.getDateExpired(),
1515            1,
1516            source.getLength(),
1517            source.getDateContent(),
1518            source.getVersion()); // version number does not matter since it will be computed later
1519
1520        // trigger "is touched" state on resource (will ensure modification date is kept unchanged)
1521        newResource.setDateLastModified(dateLastModified);
1522
1523        // log it
1524        log(
1525            dbc,
1526            new CmsLogEntry(
1527                dbc,
1528                newResource.getStructureId(),
1529                CmsLogEntryType.RESOURCE_COPIED,
1530                new String[] {newResource.getRootPath()}),
1531            false);
1532
1533        // create the resource
1534        newResource = createResource(dbc, destination, newResource, content, properties, false);
1535        // copy relations
1536        copyRelations(dbc, source, newResource);
1537
1538        // copy the access control entries to the created resource
1539        copyAccessControlEntries(dbc, source, newResource, false);
1540
1541        // clear the cache
1542        m_monitor.clearAccessControlListCache();
1543
1544        List<CmsResource> modifiedResources = new ArrayList<CmsResource>();
1545        modifiedResources.add(source);
1546        modifiedResources.add(newResource);
1547        modifiedResources.add(destinationFolder);
1548        OpenCms.fireCmsEvent(
1549            new CmsEvent(
1550                I_CmsEventListener.EVENT_RESOURCE_COPIED,
1551                Collections.<String, Object> singletonMap(I_CmsEventListener.KEY_RESOURCES, modifiedResources)));
1552    }
1553
1554    /**
1555     * Copies a resource to the current project of the user.<p>
1556     *
1557     * @param dbc the current database context
1558     * @param resource the resource to apply this operation to
1559     *
1560     * @throws CmsException if something goes wrong
1561     *
1562     * @see CmsObject#copyResourceToProject(String)
1563     * @see I_CmsResourceType#copyResourceToProject(CmsObject, CmsSecurityManager, CmsResource)
1564     */
1565    public void copyResourceToProject(CmsDbContext dbc, CmsResource resource) throws CmsException {
1566
1567        // copy the resource to the project only if the resource is not already in the project
1568        if (!isInsideCurrentProject(dbc, resource.getRootPath())) {
1569            // check if there are already any subfolders of this resource
1570            I_CmsProjectDriver projectDriver = getProjectDriver(dbc);
1571            if (resource.isFolder()) {
1572                List<String> projectResources = projectDriver.readProjectResources(dbc, dbc.currentProject());
1573                for (int i = 0; i < projectResources.size(); i++) {
1574                    String resname = projectResources.get(i);
1575                    if (resname.startsWith(resource.getRootPath())) {
1576                        // delete the existing project resource first
1577                        projectDriver.deleteProjectResource(dbc, dbc.currentProject().getUuid(), resname);
1578                    }
1579                }
1580            }
1581            try {
1582                projectDriver.createProjectResource(dbc, dbc.currentProject().getUuid(), resource.getRootPath());
1583            } catch (CmsException exc) {
1584                // if the subfolder exists already - all is ok
1585            } finally {
1586                m_monitor.flushCache(CmsMemoryMonitor.CacheType.PROJECT_RESOURCES);
1587
1588                OpenCms.fireCmsEvent(
1589                    new CmsEvent(
1590                        I_CmsEventListener.EVENT_PROJECT_MODIFIED,
1591                        Collections.<String, Object> singletonMap("project", dbc.currentProject())));
1592            }
1593        }
1594    }
1595
1596    /**
1597     * Counts the locked resources in this project.<p>
1598     *
1599     * @param project the project to count the locked resources in
1600     *
1601     * @return the amount of locked resources in this project
1602     */
1603    public int countLockedResources(CmsProject project) {
1604
1605        // count locks
1606        return m_lockManager.countExclusiveLocksInProject(project);
1607    }
1608
1609    /**
1610     * Add a new group to the Cms.<p>
1611     *
1612     * Only the admin can do this.
1613     * Only users, which are in the group "administrators" are granted.<p>
1614     *
1615     * @param dbc the current database context
1616     * @param id the id of the new group
1617     * @param name the name of the new group
1618     * @param description the description for the new group
1619     * @param flags the flags for the new group
1620     * @param parent the name of the parent group (or <code>null</code>)
1621     *
1622     * @return new created group
1623     *
1624     * @throws CmsException if the creation of the group failed
1625     * @throws CmsIllegalArgumentException if the length of the given name was below 1
1626     */
1627    public CmsGroup createGroup(CmsDbContext dbc, CmsUUID id, String name, String description, int flags, String parent)
1628    throws CmsIllegalArgumentException, CmsException {
1629
1630        // check the group name
1631        OpenCms.getValidationHandler().checkGroupName(CmsOrganizationalUnit.getSimpleName(name));
1632        // trim the name
1633        name = name.trim();
1634
1635        // check the OU
1636        readOrganizationalUnit(dbc, CmsOrganizationalUnit.getParentFqn(name));
1637
1638        // get the id of the parent group if necessary
1639        if (CmsStringUtil.isNotEmpty(parent)) {
1640            CmsGroup parentGroup = readGroup(dbc, parent);
1641            if (!parentGroup.isRole()
1642                && !CmsOrganizationalUnit.getParentFqn(parent).equals(CmsOrganizationalUnit.getParentFqn(name))) {
1643                throw new CmsDataAccessException(
1644                    Messages.get().container(
1645                        Messages.ERR_PARENT_GROUP_MUST_BE_IN_SAME_OU_3,
1646                        CmsOrganizationalUnit.getSimpleName(name),
1647                        CmsOrganizationalUnit.getParentFqn(name),
1648                        parent));
1649            }
1650        }
1651
1652        // create the group
1653        CmsGroup group = getUserDriver(dbc).createGroup(dbc, id, name, description, flags, parent);
1654
1655        // if the group is in fact a role, initialize it
1656        if (group.isVirtual()) {
1657            // get all users that have the given role
1658            String groupname = CmsRole.valueOf(group).getGroupName();
1659            Iterator<CmsUser> it = getUsersOfGroup(dbc, groupname, true, false, true).iterator();
1660            while (it.hasNext()) {
1661                CmsUser user = it.next();
1662                // put them in the new group
1663                addUserToGroup(dbc, user.getName(), group.getName(), true);
1664            }
1665        }
1666
1667        // put it into the cache
1668        m_monitor.cacheGroup(group);
1669
1670        if (!dbc.getProjectId().isNullUUID()) {
1671            // group modified event is not needed
1672            return group;
1673        }
1674        // fire group modified event
1675        Map<String, Object> eventData = new HashMap<String, Object>();
1676        eventData.put(I_CmsEventListener.KEY_GROUP_NAME, group.getName());
1677        eventData.put(I_CmsEventListener.KEY_GROUP_ID, group.getId().toString());
1678        eventData.put(I_CmsEventListener.KEY_USER_ACTION, I_CmsEventListener.VALUE_GROUP_MODIFIED_ACTION_CREATE);
1679        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_GROUP_MODIFIED, eventData));
1680
1681        // return it
1682        return group;
1683    }
1684
1685    /**
1686     * Creates a new organizational unit.<p>
1687     *
1688     * @param dbc the current db context
1689     * @param ouFqn the fully qualified name of the new organizational unit
1690     * @param description the description of the new organizational unit
1691     * @param flags the flags for the new organizational unit
1692     * @param resource the first associated resource
1693     *
1694     * @return a <code>{@link CmsOrganizationalUnit}</code> object representing
1695     *          the newly created organizational unit
1696     *
1697     * @throws CmsException if operation was not successful
1698     *
1699     * @see org.opencms.security.CmsOrgUnitManager#createOrganizationalUnit(CmsObject, String, String, int, String)
1700     */
1701    public CmsOrganizationalUnit createOrganizationalUnit(
1702        CmsDbContext dbc,
1703        String ouFqn,
1704        String description,
1705        int flags,
1706        CmsResource resource)
1707    throws CmsException {
1708
1709        // normal case
1710        CmsOrganizationalUnit parent = readOrganizationalUnit(dbc, CmsOrganizationalUnit.getParentFqn(ouFqn));
1711        String name = CmsOrganizationalUnit.getSimpleName(ouFqn);
1712        if (name.endsWith(CmsOrganizationalUnit.SEPARATOR)) {
1713            name = name.substring(0, name.length() - 1);
1714        }
1715
1716        // check the name
1717        CmsResource.checkResourceName(name);
1718
1719        // trim the name
1720        name = name.trim();
1721
1722        // check the description
1723        if (CmsStringUtil.isEmptyOrWhitespaceOnly(description)) {
1724            throw new CmsIllegalArgumentException(Messages.get().container(Messages.ERR_BAD_OU_DESCRIPTION_EMPTY_0));
1725        }
1726
1727        // create the organizational unit
1728        CmsOrganizationalUnit orgUnit = getUserDriver(dbc).createOrganizationalUnit(
1729            dbc,
1730            name,
1731            description,
1732            flags,
1733            parent,
1734            resource != null ? resource.getRootPath() : null);
1735        // put the new created org unit into the cache
1736        m_monitor.cacheOrgUnit(orgUnit);
1737
1738        // flush relevant caches
1739        m_monitor.clearPrincipalsCache();
1740        m_monitor.flushCache(CmsMemoryMonitor.CacheType.PROPERTY, CmsMemoryMonitor.CacheType.PROPERTY_LIST);
1741
1742        // create a publish list for the 'virtual' publish event
1743        CmsResource ouRes = readResource(
1744            dbc,
1745            CmsUserDriver.ORGUNIT_BASE_FOLDER + orgUnit.getName(),
1746            CmsResourceFilter.DEFAULT);
1747        CmsPublishList pl = new CmsPublishList(ouRes, false);
1748        pl.add(ouRes, false);
1749
1750        getProjectDriver(dbc).writePublishHistory(
1751            dbc,
1752            pl.getPublishHistoryId(),
1753            new CmsPublishedResource(ouRes, -1, CmsResourceState.STATE_NEW));
1754
1755        // fire the 'virtual' publish event
1756        Map<String, Object> eventData = new HashMap<String, Object>();
1757        eventData.put(I_CmsEventListener.KEY_PUBLISHID, pl.getPublishHistoryId().toString());
1758        eventData.put(I_CmsEventListener.KEY_PROJECTID, dbc.currentProject().getUuid());
1759        eventData.put(I_CmsEventListener.KEY_DBCONTEXT, dbc);
1760        CmsEvent afterPublishEvent = new CmsEvent(I_CmsEventListener.EVENT_PUBLISH_PROJECT, eventData);
1761        OpenCms.fireCmsEvent(afterPublishEvent);
1762
1763        if (!dbc.getProjectId().isNullUUID()) {
1764            // OU modified event is not needed
1765            return orgUnit;
1766        }
1767
1768        // fire OU modified event
1769        Map<String, Object> event2Data = new HashMap<String, Object>();
1770        event2Data.put(I_CmsEventListener.KEY_OU_NAME, orgUnit.getName());
1771        event2Data.put(I_CmsEventListener.KEY_OU_ID, orgUnit.getId().toString());
1772        event2Data.put(I_CmsEventListener.KEY_USER_ACTION, I_CmsEventListener.VALUE_OU_MODIFIED_ACTION_CREATE);
1773        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_OU_MODIFIED, event2Data));
1774
1775        // return it
1776        return orgUnit;
1777    }
1778
1779    /**
1780     * Creates a project.<p>
1781     *
1782     * @param dbc the current database context
1783     * @param name the name of the project to create
1784     * @param description the description of the project
1785     * @param groupname the project user group to be set
1786     * @param managergroupname the project manager group to be set
1787     * @param projecttype the type of the project
1788     *
1789     * @return the created project
1790     *
1791     * @throws CmsIllegalArgumentException if the chosen <code>name</code> is already used
1792     *         by the online project, or if the name is not valid
1793     * @throws CmsException if something goes wrong
1794     */
1795    public CmsProject createProject(
1796        CmsDbContext dbc,
1797        String name,
1798        String description,
1799        String groupname,
1800        String managergroupname,
1801        CmsProject.CmsProjectType projecttype)
1802    throws CmsIllegalArgumentException, CmsException {
1803
1804        if (CmsProject.ONLINE_PROJECT_NAME.equals(name)) {
1805            throw new CmsIllegalArgumentException(
1806                Messages.get().container(
1807                    Messages.ERR_CREATE_PROJECT_ONLINE_PROJECT_NAME_1,
1808                    CmsProject.ONLINE_PROJECT_NAME));
1809        }
1810        // check the name
1811        CmsProject.checkProjectName(CmsOrganizationalUnit.getSimpleName(name));
1812        // check the ou
1813        readOrganizationalUnit(dbc, CmsOrganizationalUnit.getParentFqn(name));
1814        // read the needed groups from the cms
1815        CmsGroup group = readGroup(dbc, groupname);
1816        CmsGroup managergroup = readGroup(dbc, managergroupname);
1817
1818        return getProjectDriver(dbc).createProject(
1819            dbc,
1820            new CmsUUID(),
1821            dbc.currentUser(),
1822            group,
1823            managergroup,
1824            name,
1825            description,
1826            projecttype.getDefaultFlags(),
1827            projecttype);
1828    }
1829
1830    /**
1831     * Creates a property definition.<p>
1832     *
1833     * Property definitions are valid for all resource types.<p>
1834     *
1835     * @param dbc the current database context
1836     * @param name the name of the property definition to create
1837     *
1838     * @return the created property definition
1839     *
1840     * @throws CmsException if something goes wrong
1841     */
1842    public CmsPropertyDefinition createPropertyDefinition(CmsDbContext dbc, String name) throws CmsException {
1843
1844        CmsPropertyDefinition propertyDefinition = null;
1845
1846        name = name.trim();
1847        // validate the property name
1848        CmsPropertyDefinition.checkPropertyName(name);
1849        // TODO: make the type a parameter
1850        try {
1851            try {
1852                propertyDefinition = getVfsDriver(dbc).readPropertyDefinition(
1853                    dbc,
1854                    name,
1855                    dbc.currentProject().getUuid());
1856            } catch (CmsException e) {
1857                propertyDefinition = getVfsDriver(dbc).createPropertyDefinition(
1858                    dbc,
1859                    dbc.currentProject().getUuid(),
1860                    name,
1861                    CmsPropertyDefinition.TYPE_NORMAL);
1862            }
1863
1864            try {
1865                getVfsDriver(dbc).readPropertyDefinition(dbc, name, CmsProject.ONLINE_PROJECT_ID);
1866            } catch (CmsException e) {
1867                getVfsDriver(dbc).createPropertyDefinition(
1868                    dbc,
1869                    CmsProject.ONLINE_PROJECT_ID,
1870                    name,
1871                    CmsPropertyDefinition.TYPE_NORMAL);
1872            }
1873
1874            try {
1875                getHistoryDriver(dbc).readPropertyDefinition(dbc, name);
1876            } catch (CmsException e) {
1877                getHistoryDriver(dbc).createPropertyDefinition(dbc, name, CmsPropertyDefinition.TYPE_NORMAL);
1878            }
1879        } finally {
1880
1881            // fire an event that a property of a resource has been deleted
1882            OpenCms.fireCmsEvent(
1883                new CmsEvent(
1884                    I_CmsEventListener.EVENT_PROPERTY_DEFINITION_CREATED,
1885                    Collections.<String, Object> singletonMap("propertyDefinition", propertyDefinition)));
1886
1887        }
1888
1889        return propertyDefinition;
1890    }
1891
1892    /**
1893     * Creates a new publish job.<p>
1894     *
1895     * @param dbc the current database context
1896     * @param publishJob the publish job to create
1897     *
1898     * @throws CmsException if something goes wrong
1899     */
1900    public void createPublishJob(CmsDbContext dbc, CmsPublishJobInfoBean publishJob) throws CmsException {
1901
1902        getProjectDriver(dbc).createPublishJob(dbc, publishJob);
1903    }
1904
1905    /**
1906     * Creates a new resource with the provided content and properties.<p>
1907     *
1908     * The <code>content</code> parameter may be <code>null</code> if the resource id
1909     * already exists. If so, the created resource will be a sibling of the existing
1910     * resource, the existing content will remain unchanged.<p>
1911     *
1912     * This is used during file import for import of siblings as the
1913     * <code>manifest.xml</code> only contains one binary copy per file.<p>
1914     *
1915     * If the resource id exists but the <code>content</code> is not <code>null</code>,
1916     * the created resource will be made a sibling of the existing resource,
1917     * and both will share the new content.<p>
1918     *
1919     * @param dbc the current database context
1920     * @param resourcePath the name of the resource to create (full path)
1921     * @param resource the new resource to create
1922     * @param content the content for the new resource
1923     * @param properties the properties for the new resource
1924     * @param importCase if <code>true</code>, signals that this operation is done while
1925     *                      importing resource, causing different lock behavior and
1926     *                      potential "lost and found" usage
1927     *
1928     * @return the created resource
1929     *
1930     * @throws CmsException if something goes wrong
1931     */
1932    public CmsResource createResource(
1933        CmsDbContext dbc,
1934        String resourcePath,
1935        CmsResource resource,
1936        byte[] content,
1937        List<CmsProperty> properties,
1938        boolean importCase)
1939    throws CmsException {
1940
1941        CmsResource newResource = null;
1942        if (resource.isFolder()) {
1943            resourcePath = CmsFileUtil.addTrailingSeparator(resourcePath);
1944        }
1945
1946        try {
1947            synchronized (this) {
1948                // need to provide the parent folder id for resource creation
1949                String parentFolderName = CmsResource.getParentFolder(resourcePath);
1950                CmsResource parentFolder = readFolder(dbc, parentFolderName, CmsResourceFilter.IGNORE_EXPIRATION);
1951
1952                CmsLock parentLock = getLock(dbc, parentFolder);
1953                // it is not allowed to create a resource in a folder locked by other user
1954                if (!parentLock.isUnlocked() && !parentLock.isOwnedBy(dbc.currentUser())) {
1955                    // one exception is if the admin user tries to create a temporary resource
1956                    if (!CmsResource.getName(resourcePath).startsWith(TEMP_FILE_PREFIX)
1957                        || !m_securityManager.hasRole(dbc, dbc.currentUser(), CmsRole.ROOT_ADMIN)) {
1958                        throw new CmsLockException(
1959                            Messages.get().container(
1960                                Messages.ERR_CREATE_RESOURCE_PARENT_LOCK_1,
1961                                dbc.removeSiteRoot(resourcePath)));
1962                    }
1963                }
1964                if (CmsResourceTypeJsp.isJsp(resource)) {
1965                    // security check when trying to create a new jsp file
1966                    m_securityManager.checkRoleForResource(dbc, CmsRole.VFS_MANAGER, parentFolder);
1967                }
1968
1969                // check import configuration of "lost and found" folder
1970                boolean useLostAndFound = importCase && !OpenCms.getImportExportManager().overwriteCollidingResources();
1971
1972                // check if the resource already exists by name
1973                CmsResource currentResourceByName = null;
1974                try {
1975                    currentResourceByName = readResource(dbc, resourcePath, CmsResourceFilter.ALL);
1976                } catch (CmsVfsResourceNotFoundException e) {
1977                    // if the resource does exist, we have to check the id later to decide what to do
1978                }
1979
1980                // check if the resource already exists by id
1981                try {
1982                    CmsResource currentResourceById = readResource(
1983                        dbc,
1984                        resource.getStructureId(),
1985                        CmsResourceFilter.ALL);
1986                    // it is not allowed to import resources when there is already a resource with the same id but different path
1987                    if (!currentResourceById.getRootPath().equals(resourcePath)) {
1988                        throw new CmsVfsResourceAlreadyExistsException(
1989                            Messages.get().container(
1990                                Messages.ERR_RESOURCE_WITH_ID_ALREADY_EXISTS_3,
1991                                dbc.removeSiteRoot(resourcePath),
1992                                dbc.removeSiteRoot(currentResourceById.getRootPath()),
1993                                currentResourceById.getStructureId()));
1994                    }
1995                } catch (CmsVfsResourceNotFoundException e) {
1996                    // if the resource does exist, we have to check the id later to decide what to do
1997                }
1998
1999                // check the permissions
2000                if (currentResourceByName == null) {
2001                    // resource does not exist - check parent folder
2002                    m_securityManager.checkPermissions(
2003                        dbc,
2004                        parentFolder,
2005                        CmsPermissionSet.ACCESS_WRITE,
2006                        false,
2007                        CmsResourceFilter.IGNORE_EXPIRATION);
2008                } else {
2009                    // resource already exists - check existing resource
2010                    m_securityManager.checkPermissions(
2011                        dbc,
2012                        currentResourceByName,
2013                        CmsPermissionSet.ACCESS_WRITE,
2014                        !importCase,
2015                        CmsResourceFilter.ALL);
2016                }
2017
2018                // now look for the resource by name
2019                if (currentResourceByName != null) {
2020                    boolean overwrite = true;
2021                    if (currentResourceByName.getState().isDeleted()) {
2022                        if (!currentResourceByName.isFolder()) {
2023                            // if a non-folder resource was deleted it's treated like a new resource
2024                            overwrite = false;
2025                        }
2026                    } else {
2027                        if (!importCase) {
2028                            // direct "overwrite" of a resource is possible only during import,
2029                            // or if the resource has been deleted
2030                            throw new CmsVfsResourceAlreadyExistsException(
2031                                org.opencms.db.generic.Messages.get().container(
2032                                    org.opencms.db.generic.Messages.ERR_RESOURCE_WITH_NAME_ALREADY_EXISTS_1,
2033                                    dbc.removeSiteRoot(resource.getRootPath())));
2034                        }
2035                        // the resource already exists
2036                        if (!resource.isFolder()
2037                            && useLostAndFound
2038                            && (!currentResourceByName.getResourceId().equals(resource.getResourceId()))) {
2039                            // semantic change: the current resource is moved to L&F and the imported resource will overwrite the old one
2040                            // will leave the resource with state deleted,
2041                            // but it does not matter, since the state will be set later again
2042                            moveToLostAndFound(dbc, currentResourceByName, false);
2043                        }
2044                    }
2045                    if (!overwrite) {
2046                        // lock the resource, will throw an exception if not lockable
2047                        lockResource(dbc, currentResourceByName, CmsLockType.EXCLUSIVE);
2048
2049                        // trigger createResource instead of writeResource
2050                        currentResourceByName = null;
2051                    }
2052                }
2053                // if null, create new resource, if not null write resource
2054                CmsResource overwrittenResource = currentResourceByName;
2055
2056                // extract the name (without path)
2057                String targetName = CmsResource.getName(resourcePath);
2058
2059                int contentLength;
2060
2061                // modify target name and content length in case of folder creation
2062                if (resource.isFolder()) {
2063                    // folders never have any content
2064                    contentLength = -1;
2065                    // must cut of trailing '/' for folder creation (or name check fails)
2066                    if (CmsResource.isFolder(targetName)) {
2067                        targetName = targetName.substring(0, targetName.length() - 1);
2068                    }
2069                } else {
2070                    // otherwise ensure content and content length are set correctly
2071                    if (content != null) {
2072                        // if a content is provided, in each case the length is the length of this content
2073                        contentLength = content.length;
2074                    } else if (overwrittenResource != null) {
2075                        // we have no content, but an already existing resource - length remains unchanged
2076                        contentLength = overwrittenResource.getLength();
2077                    } else {
2078                        // we have no content - length is used as set in the resource
2079                        contentLength = resource.getLength();
2080                    }
2081                }
2082
2083                // check if the target name is valid (forbidden chars etc.),
2084                // if not throw an exception
2085                // must do this here since targetName is modified in folder case (see above)
2086                CmsResource.checkResourceName(targetName);
2087
2088                // set structure and resource ids as given
2089                CmsUUID structureId = resource.getStructureId();
2090                CmsUUID resourceId = resource.getResourceId();
2091
2092                // decide which structure id to use
2093                if (overwrittenResource != null) {
2094                    // resource exists, re-use existing ids
2095                    structureId = overwrittenResource.getStructureId();
2096                }
2097                if (structureId.isNullUUID()) {
2098                    // need a new structure id
2099                    structureId = new CmsUUID();
2100                }
2101
2102                // decide which resource id to use
2103                if (overwrittenResource != null) {
2104                    // if we are overwriting we have to assure the resource id is the same
2105                    resourceId = overwrittenResource.getResourceId();
2106                }
2107                if (resourceId.isNullUUID()) {
2108                    // need a new resource id
2109                    resourceId = new CmsUUID();
2110                }
2111
2112                try {
2113                    // check online resource
2114                    CmsResource onlineResource = getVfsDriver(
2115                        dbc).readResource(dbc, CmsProject.ONLINE_PROJECT_ID, resourcePath, true);
2116                    // only allow to overwrite with different id if importing (createResource will set the right id)
2117                    try {
2118                        CmsResource offlineResource = getVfsDriver(dbc).readResource(
2119                            dbc,
2120                            dbc.currentProject().getUuid(),
2121                            onlineResource.getStructureId(),
2122                            true);
2123                        if (!offlineResource.getRootPath().equals(onlineResource.getRootPath())) {
2124                            throw new CmsVfsOnlineResourceAlreadyExistsException(
2125                                Messages.get().container(
2126                                    Messages.ERR_ONLINE_RESOURCE_EXISTS_2,
2127                                    dbc.removeSiteRoot(resourcePath),
2128                                    dbc.removeSiteRoot(offlineResource.getRootPath())));
2129                        }
2130                    } catch (CmsVfsResourceNotFoundException e) {
2131                        // there is no problem for now
2132                        // but should never happen
2133                        if (LOG.isErrorEnabled()) {
2134                            LOG.error(e.getLocalizedMessage(), e);
2135                        }
2136                    }
2137                } catch (CmsVfsResourceNotFoundException e) {
2138                    // ok, there is no online entry to worry about
2139                }
2140
2141                // now create a resource object with all informations
2142                newResource = new CmsResource(
2143                    structureId,
2144                    resourceId,
2145                    resourcePath,
2146                    resource.getTypeId(),
2147                    resource.isFolder(),
2148                    resource.getFlags(),
2149                    dbc.currentProject().getUuid(),
2150                    resource.getState(),
2151                    resource.getDateCreated(),
2152                    resource.getUserCreated(),
2153                    resource.getDateLastModified(),
2154                    resource.getUserLastModified(),
2155                    resource.getDateReleased(),
2156                    resource.getDateExpired(),
2157                    1,
2158                    contentLength,
2159                    resource.getDateContent(),
2160                    resource.getVersion()); // version number does not matter since it will be computed later
2161
2162                // ensure date is updated only if required
2163                if (resource.isTouched()) {
2164                    // this will trigger the internal "is touched" state on the new resource
2165                    newResource.setDateLastModified(resource.getDateLastModified());
2166                }
2167
2168                if (resource.isFile()) {
2169                    // check if a sibling to the imported resource lies in a marked site
2170                    if (labelResource(dbc, resource, resourcePath, 2)) {
2171                        int flags = resource.getFlags();
2172                        flags |= CmsResource.FLAG_LABELED;
2173                        resource.setFlags(flags);
2174                    }
2175                    // ensure siblings don't overwrite existing resource records
2176                    if (content == null) {
2177                        newResource.setState(CmsResource.STATE_KEEP);
2178                    }
2179                }
2180
2181                // delete all relations for the resource, before writing the content
2182                getVfsDriver(
2183                    dbc).deleteRelations(dbc, dbc.currentProject().getUuid(), newResource, CmsRelationFilter.TARGETS);
2184                if (overwrittenResource == null) {
2185                    CmsLock lock = getLock(dbc, newResource);
2186                    if (lock.getEditionLock().isExclusive()) {
2187                        unlockResource(dbc, newResource, true, false);
2188                    }
2189                    // resource does not exist.
2190                    newResource = getVfsDriver(
2191                        dbc).createResource(dbc, dbc.currentProject().getUuid(), newResource, content);
2192                } else {
2193                    // resource already exists.
2194                    // probably the resource is a merged page file that gets overwritten during import, or it gets
2195                    // overwritten by a copy operation. if so, the structure & resource state are not modified to changed.
2196                    int updateStates = (overwrittenResource.getState().isNew()
2197                    ? CmsDriverManager.NOTHING_CHANGED
2198                    : CmsDriverManager.UPDATE_ALL);
2199                    getVfsDriver(dbc).writeResource(dbc, dbc.currentProject().getUuid(), newResource, updateStates);
2200
2201                    if ((content != null) && resource.isFile()) {
2202                        // also update file content if required
2203                        getVfsDriver(dbc).writeContent(dbc, newResource.getResourceId(), content);
2204                    }
2205                }
2206
2207                // write the properties (internal operation, no events or duplicate permission checks)
2208                writePropertyObjects(dbc, newResource, properties, false);
2209
2210                // lock the created resource
2211                try {
2212                    // if it is locked by another user (copied or moved resource) this lock should be preserved and
2213                    // the exception is OK: locks on created resources are a slave feature to original locks
2214                    lockResource(dbc, newResource, CmsLockType.EXCLUSIVE);
2215                } catch (CmsLockException cle) {
2216                    if (LOG.isDebugEnabled()) {
2217                        LOG.debug(
2218                            Messages.get().getBundle().key(
2219                                Messages.ERR_CREATE_RESOURCE_LOCK_1,
2220                                new Object[] {dbc.removeSiteRoot(newResource.getRootPath())}));
2221                    }
2222                }
2223
2224                if (!importCase) {
2225                    log(
2226                        dbc,
2227                        new CmsLogEntry(
2228                            dbc,
2229                            newResource.getStructureId(),
2230                            CmsLogEntryType.RESOURCE_CREATED,
2231                            new String[] {resource.getRootPath()}),
2232                        false);
2233                } else {
2234                    log(
2235                        dbc,
2236                        new CmsLogEntry(
2237                            dbc,
2238                            newResource.getStructureId(),
2239                            CmsLogEntryType.RESOURCE_IMPORTED,
2240                            new String[] {resource.getRootPath()}),
2241                        false);
2242                }
2243            }
2244        } finally {
2245            // clear the internal caches
2246            m_monitor.clearAccessControlListCache();
2247            m_monitor.flushCache(CmsMemoryMonitor.CacheType.PROPERTY, CmsMemoryMonitor.CacheType.PROPERTY_LIST);
2248
2249            if (newResource != null) {
2250                // fire an event that a new resource has been created
2251                OpenCms.fireCmsEvent(
2252                    new CmsEvent(
2253                        I_CmsEventListener.EVENT_RESOURCE_CREATED,
2254                        Collections.<String, Object> singletonMap(I_CmsEventListener.KEY_RESOURCE, newResource)));
2255            }
2256        }
2257        return newResource;
2258    }
2259
2260    /**
2261     * Creates a new resource of the given resource type
2262     * with the provided content and properties.<p>
2263     *
2264     * If the provided content is null and the resource is not a folder,
2265     * the content will be set to an empty byte array.<p>
2266     *
2267     * @param dbc the current database context
2268     * @param resourcename the name of the resource to create (full path)
2269     * @param type the type of the resource to create
2270     * @param content the content for the new resource
2271     * @param properties the properties for the new resource
2272     *
2273     * @return the created resource
2274     *
2275     * @throws CmsException if something goes wrong
2276     * @throws CmsIllegalArgumentException if the <code>resourcename</code> argument is null or of length 0
2277     *
2278     * @see CmsObject#createResource(String, int, byte[], List)
2279     * @see CmsObject#createResource(String, int)
2280     * @see I_CmsResourceType#createResource(CmsObject, CmsSecurityManager, String, byte[], List)
2281     */
2282    @SuppressWarnings("javadoc")
2283    public CmsResource createResource(
2284        CmsDbContext dbc,
2285        String resourcename,
2286        int type,
2287        byte[] content,
2288        List<CmsProperty> properties)
2289    throws CmsException, CmsIllegalArgumentException {
2290
2291        String targetName = resourcename;
2292
2293        if (content == null) {
2294            // name based resource creation MUST have a content
2295            content = new byte[0];
2296        }
2297        int size;
2298
2299        if (CmsFolder.isFolderType(type)) {
2300            // must cut of trailing '/' for folder creation
2301            if (CmsResource.isFolder(targetName)) {
2302                targetName = targetName.substring(0, targetName.length() - 1);
2303            }
2304            size = -1;
2305        } else {
2306            size = content.length;
2307        }
2308
2309        // create a new resource
2310        CmsResource newResource = new CmsResource(
2311            CmsUUID.getNullUUID(), // uuids will be "corrected" later
2312            CmsUUID.getNullUUID(),
2313            targetName,
2314            type,
2315            CmsFolder.isFolderType(type),
2316            0,
2317            dbc.currentProject().getUuid(),
2318            CmsResource.STATE_NEW,
2319            0,
2320            dbc.currentUser().getId(),
2321            0,
2322            dbc.currentUser().getId(),
2323            CmsResource.DATE_RELEASED_DEFAULT,
2324            CmsResource.DATE_EXPIRED_DEFAULT,
2325            1,
2326            size,
2327            0, // version number does not matter since it will be computed later
2328            0); // content time will be corrected later
2329
2330        return createResource(dbc, targetName, newResource, content, properties, false);
2331    }
2332
2333    /**
2334     * Creates a new sibling of the source resource.<p>
2335     *
2336     * @param dbc the current database context
2337     * @param source the resource to create a sibling for
2338     * @param destination the name of the sibling to create with complete path
2339     * @param properties the individual properties for the new sibling
2340     *
2341     * @return the new created sibling
2342     *
2343     * @throws CmsException if something goes wrong
2344     *
2345     * @see CmsObject#createSibling(String, String, List)
2346     * @see I_CmsResourceType#createSibling(CmsObject, CmsSecurityManager, CmsResource, String, List)
2347     */
2348    public CmsResource createSibling(
2349        CmsDbContext dbc,
2350        CmsResource source,
2351        String destination,
2352        List<CmsProperty> properties)
2353    throws CmsException {
2354
2355        if (source.isFolder()) {
2356            throw new CmsVfsException(Messages.get().container(Messages.ERR_VFS_FOLDERS_DONT_SUPPORT_SIBLINGS_0));
2357        }
2358
2359        // determine destination folder and resource name
2360        String destinationFoldername = CmsResource.getParentFolder(destination);
2361
2362        // read the destination folder (will also check read permissions)
2363        CmsFolder destinationFolder = readFolder(dbc, destinationFoldername, CmsResourceFilter.IGNORE_EXPIRATION);
2364
2365        // no further permission check required here, will be done in createResource()
2366
2367        // check the resource flags
2368        int flags = source.getFlags();
2369        if (labelResource(dbc, source, destination, 1)) {
2370            // set "labeled" link flag for new resource
2371            flags |= CmsResource.FLAG_LABELED;
2372        }
2373
2374        // create the new resource
2375        CmsResource newResource = new CmsResource(
2376            new CmsUUID(),
2377            source.getResourceId(),
2378            destination,
2379            source.getTypeId(),
2380            source.isFolder(),
2381            flags,
2382            dbc.currentProject().getUuid(),
2383            CmsResource.STATE_KEEP,
2384            source.getDateCreated(), // ensures current resource record remains untouched
2385            source.getUserCreated(),
2386            source.getDateLastModified(),
2387            source.getUserLastModified(),
2388            source.getDateReleased(),
2389            source.getDateExpired(),
2390            source.getSiblingCount() + 1,
2391            source.getLength(),
2392            source.getDateContent(),
2393            source.getVersion()); // version number does not matter since it will be computed later
2394
2395        // trigger "is touched" state on resource (will ensure modification date is kept unchanged)
2396        newResource.setDateLastModified(newResource.getDateLastModified());
2397
2398        log(
2399            dbc,
2400            new CmsLogEntry(
2401                dbc,
2402                newResource.getStructureId(),
2403                CmsLogEntryType.RESOURCE_CLONED,
2404                new String[] {newResource.getRootPath()}),
2405            false);
2406        // create the resource (null content signals creation of sibling)
2407        newResource = createResource(dbc, destination, newResource, null, properties, false);
2408
2409        // copy relations
2410        copyRelations(dbc, source, newResource);
2411
2412        // clear the caches
2413        m_monitor.clearAccessControlListCache();
2414
2415        List<CmsResource> modifiedResources = new ArrayList<CmsResource>();
2416        modifiedResources.add(source);
2417        modifiedResources.add(newResource);
2418        modifiedResources.add(destinationFolder);
2419        Map<String, Object> eventData = new HashMap<>();
2420        eventData.put(I_CmsEventListener.KEY_RESOURCES, modifiedResources);
2421        eventData.put(I_CmsEventListener.KEY_CHANGE, I_CmsEventListener.VALUE_CREATE_SIBLING);
2422        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCES_AND_PROPERTIES_MODIFIED, eventData));
2423
2424        return newResource;
2425    }
2426
2427    /**
2428     * Creates the project for the temporary workplace files.<p>
2429     *
2430     * @param dbc the current database context
2431     *
2432     * @return the created project for the temporary workplace files
2433     *
2434     * @throws CmsException if something goes wrong
2435     */
2436    public CmsProject createTempfileProject(CmsDbContext dbc) throws CmsException {
2437
2438        // read the needed groups from the cms
2439        CmsGroup projectUserGroup = readGroup(dbc, dbc.currentProject().getGroupId());
2440        CmsGroup projectManagerGroup = readGroup(dbc, dbc.currentProject().getManagerGroupId());
2441
2442        CmsProject tempProject = getProjectDriver(dbc).createProject(
2443            dbc,
2444            new CmsUUID(),
2445            dbc.currentUser(),
2446            projectUserGroup,
2447            projectManagerGroup,
2448            I_CmsProjectDriver.TEMP_FILE_PROJECT_NAME,
2449            Messages.get().getBundle(dbc.getRequestContext().getLocale()).key(
2450                Messages.GUI_WORKPLACE_TEMPFILE_PROJECT_DESC_0),
2451            CmsProject.PROJECT_FLAG_HIDDEN,
2452            CmsProject.PROJECT_TYPE_NORMAL);
2453        getProjectDriver(dbc).createProjectResource(dbc, tempProject.getUuid(), "/");
2454
2455        OpenCms.fireCmsEvent(
2456            new CmsEvent(
2457                I_CmsEventListener.EVENT_PROJECT_MODIFIED,
2458                Collections.<String, Object> singletonMap("project", tempProject)));
2459
2460        return tempProject;
2461    }
2462
2463    /**
2464     * Creates a new user.<p>
2465     *
2466     * @param dbc the current database context
2467     * @param name the name for the new user
2468     * @param password the password for the new user
2469     * @param description the description for the new user
2470     * @param additionalInfos the additional infos for the user
2471     *
2472     * @return the created user
2473     *
2474     * @see CmsObject#createUser(String, String, String, Map)
2475     *
2476     * @throws CmsException if something goes wrong
2477     * @throws CmsIllegalArgumentException if the name for the user is not valid
2478     */
2479    public CmsUser createUser(
2480        CmsDbContext dbc,
2481        String name,
2482        String password,
2483        String description,
2484        Map<String, Object> additionalInfos)
2485    throws CmsException, CmsIllegalArgumentException {
2486
2487        // no space before or after the name
2488        name = name.trim();
2489        // check the user name
2490        String userName = CmsOrganizationalUnit.getSimpleName(name);
2491        OpenCms.getValidationHandler().checkUserName(userName);
2492        if (CmsStringUtil.isEmptyOrWhitespaceOnly(userName)) {
2493            throw new CmsIllegalArgumentException(Messages.get().container(Messages.ERR_BAD_USER_1, userName));
2494        }
2495        // check the ou
2496        CmsOrganizationalUnit ou = readOrganizationalUnit(dbc, CmsOrganizationalUnit.getParentFqn(name));
2497        // check the password
2498        validatePassword(password);
2499
2500        Map<String, Object> info = new HashMap<String, Object>();
2501        if (additionalInfos != null) {
2502            info.putAll(additionalInfos);
2503        }
2504        if (description != null) {
2505            info.put(CmsUserSettings.ADDITIONAL_INFO_DESCRIPTION, description);
2506        }
2507        int flags = 0;
2508        if (ou.hasFlagWebuser()) {
2509            flags += I_CmsPrincipal.FLAG_USER_WEBUSER;
2510        }
2511        CmsUser user = getUserDriver(dbc).createUser(
2512            dbc,
2513            new CmsUUID(),
2514            name,
2515            OpenCms.getPasswordHandler().digest(password),
2516            " ",
2517            " ",
2518            " ",
2519            0,
2520            I_CmsPrincipal.FLAG_ENABLED + flags,
2521            0,
2522            info);
2523
2524        if (!dbc.getProjectId().isNullUUID()) {
2525            // user modified event is not needed
2526            return user;
2527        }
2528        // fire user modified event
2529        Map<String, Object> eventData = new HashMap<String, Object>();
2530        eventData.put(I_CmsEventListener.KEY_USER_ID, user.getId().toString());
2531        eventData.put(I_CmsEventListener.KEY_USER_ACTION, I_CmsEventListener.VALUE_USER_MODIFIED_ACTION_CREATE_USER);
2532        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_USER_MODIFIED, eventData));
2533        return user;
2534    }
2535
2536    /**
2537     * Deletes aliases indicated by a filter.<p>
2538     *
2539     * @param dbc the current database context
2540     * @param project the current project
2541     * @param filter the filter which describes which aliases to delete
2542     *
2543     * @throws CmsException if something goes wrong
2544     */
2545    public void deleteAliases(CmsDbContext dbc, CmsProject project, CmsAliasFilter filter) throws CmsException {
2546
2547        I_CmsVfsDriver vfsDriver = getVfsDriver(dbc);
2548        vfsDriver.deleteAliases(dbc, project, filter);
2549    }
2550
2551    /**
2552     * Deletes all property values of a file or folder.<p>
2553     *
2554     * If there are no other siblings than the specified resource,
2555     * both the structure and resource property values get deleted.
2556     * If the specified resource has siblings, only the structure
2557     * property values get deleted.<p>
2558     *
2559     * @param dbc the current database context
2560     * @param resourcename the name of the resource for which all properties should be deleted
2561     *
2562     * @throws CmsException if operation was not successful
2563     */
2564    public void deleteAllProperties(CmsDbContext dbc, String resourcename) throws CmsException {
2565
2566        CmsResource resource = null;
2567        List<CmsResource> resources = new ArrayList<CmsResource>();
2568
2569        try {
2570            // read the resource
2571            resource = readResource(dbc, resourcename, CmsResourceFilter.IGNORE_EXPIRATION);
2572
2573            // check the security
2574            m_securityManager.checkPermissions(
2575                dbc,
2576                resource,
2577                CmsPermissionSet.ACCESS_WRITE,
2578                false,
2579                CmsResourceFilter.ALL);
2580
2581            // delete the property values
2582            if (resource.getSiblingCount() > 1) {
2583                // the resource has siblings- delete only the (structure) properties of this sibling
2584                getVfsDriver(dbc).deletePropertyObjects(
2585                    dbc,
2586                    dbc.currentProject().getUuid(),
2587                    resource,
2588                    CmsProperty.DELETE_OPTION_DELETE_STRUCTURE_VALUES);
2589                resources.addAll(readSiblings(dbc, resource, CmsResourceFilter.ALL));
2590
2591            } else {
2592                // the resource has no other siblings- delete all (structure+resource) properties
2593                getVfsDriver(dbc).deletePropertyObjects(
2594                    dbc,
2595                    dbc.currentProject().getUuid(),
2596                    resource,
2597                    CmsProperty.DELETE_OPTION_DELETE_STRUCTURE_AND_RESOURCE_VALUES);
2598                resources.add(resource);
2599            }
2600        } finally {
2601            // clear the driver manager cache
2602            m_monitor.flushCache(CmsMemoryMonitor.CacheType.PROPERTY, CmsMemoryMonitor.CacheType.PROPERTY_LIST);
2603
2604            // fire an event that all properties of a resource have been deleted
2605            OpenCms.fireCmsEvent(
2606                new CmsEvent(
2607                    I_CmsEventListener.EVENT_RESOURCES_AND_PROPERTIES_MODIFIED,
2608                    Collections.<String, Object> singletonMap(I_CmsEventListener.KEY_RESOURCES, resources)));
2609        }
2610    }
2611
2612    /**
2613     * Deletes all entries in the published resource table.<p>
2614     *
2615     * @param dbc the current database context
2616     * @param linkType the type of resource deleted (0= non-paramter, 1=parameter)
2617     *
2618     * @throws CmsException if something goes wrong
2619     */
2620    public void deleteAllStaticExportPublishedResources(CmsDbContext dbc, int linkType) throws CmsException {
2621
2622        getProjectDriver(dbc).deleteAllStaticExportPublishedResources(dbc, linkType);
2623    }
2624
2625    /**
2626     * Deletes a group, where all permissions, users and children of the group
2627     * are transfered to a replacement group.<p>
2628     *
2629     * @param dbc the current request context
2630     * @param group the id of the group to be deleted
2631     * @param replacementId the id of the group to be transfered, can be <code>null</code>
2632     *
2633     * @throws CmsException if operation was not successful
2634     * @throws CmsDataAccessException if group to be deleted contains user
2635     */
2636    public void deleteGroup(CmsDbContext dbc, CmsGroup group, CmsUUID replacementId)
2637    throws CmsDataAccessException, CmsException {
2638
2639        CmsGroup replacementGroup = null;
2640        if (replacementId != null) {
2641            replacementGroup = readGroup(dbc, replacementId);
2642        }
2643        // get all child groups of the group
2644        List<CmsGroup> children = getChildren(dbc, group, false);
2645        // get all users in this group
2646        List<CmsUser> users = getUsersOfGroup(dbc, group.getName(), true, true, group.isRole());
2647        // get online project
2648        CmsProject onlineProject = readProject(dbc, CmsProject.ONLINE_PROJECT_ID);
2649        if (replacementGroup == null) {
2650            // remove users
2651            Iterator<CmsUser> itUsers = users.iterator();
2652            while (itUsers.hasNext()) {
2653                CmsUser user = itUsers.next();
2654                if (userInGroup(dbc, user.getName(), group.getName(), group.isRole())) {
2655                    removeUserFromGroup(dbc, user.getName(), group.getName(), group.isRole());
2656                }
2657            }
2658            // transfer children to grandfather if possible
2659            CmsUUID parentId = group.getParentId();
2660            if (parentId == null) {
2661                parentId = CmsUUID.getNullUUID();
2662            }
2663            Iterator<CmsGroup> itChildren = children.iterator();
2664            while (itChildren.hasNext()) {
2665                CmsGroup child = itChildren.next();
2666                child.setParentId(parentId);
2667                writeGroup(dbc, child);
2668            }
2669        } else {
2670            // move children
2671            Iterator<CmsGroup> itChildren = children.iterator();
2672            while (itChildren.hasNext()) {
2673                CmsGroup child = itChildren.next();
2674                child.setParentId(replacementId);
2675                writeGroup(dbc, child);
2676            }
2677            // move users
2678            Iterator<CmsUser> itUsers = users.iterator();
2679            while (itUsers.hasNext()) {
2680                CmsUser user = itUsers.next();
2681                addUserToGroup(dbc, user.getName(), replacementGroup.getName(), group.isRole());
2682                removeUserFromGroup(dbc, user.getName(), group.getName(), group.isRole());
2683            }
2684            // transfer for offline
2685            transferPrincipalResources(dbc, dbc.currentProject(), group.getId(), replacementId, true);
2686            // transfer for online
2687            transferPrincipalResources(dbc, onlineProject, group.getId(), replacementId, true);
2688        }
2689        // remove the group
2690        getUserDriver(
2691            dbc).removeAccessControlEntriesForPrincipal(dbc, dbc.currentProject(), onlineProject, group.getId());
2692        getUserDriver(dbc).deleteGroup(dbc, group.getName());
2693        // backup the group
2694        getHistoryDriver(dbc).writePrincipal(dbc, group);
2695        if (OpenCms.getSubscriptionManager().isEnabled()) {
2696            // delete all subscribed resources for group
2697            unsubscribeAllResourcesFor(dbc, OpenCms.getSubscriptionManager().getPoolName(), group);
2698        }
2699
2700        // clear the relevant caches
2701        m_monitor.uncacheGroup(group);
2702        m_monitor.flushCache(
2703            CmsMemoryMonitor.CacheType.USERGROUPS,
2704            CmsMemoryMonitor.CacheType.USER_LIST,
2705            CmsMemoryMonitor.CacheType.ACL);
2706
2707        if (!dbc.getProjectId().isNullUUID()) {
2708            // group modified event is not needed
2709            return;
2710        }
2711        // fire group modified event
2712        Map<String, Object> eventData = new HashMap<String, Object>();
2713        eventData.put(I_CmsEventListener.KEY_GROUP_ID, group.getId().toString());
2714        eventData.put(I_CmsEventListener.KEY_GROUP_NAME, group.getName());
2715        eventData.put(I_CmsEventListener.KEY_USER_ACTION, I_CmsEventListener.VALUE_GROUP_MODIFIED_ACTION_DELETE);
2716        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_GROUP_MODIFIED, eventData));
2717    }
2718
2719    /**
2720     * Deletes the versions from the history tables, keeping the given number of versions per resource.<p>
2721     *
2722     * if the <code>cleanUp</code> option is set, additionally versions of deleted resources will be removed.<p>
2723     *
2724     * @param dbc the current database context
2725     * @param versionsToKeep number of versions to keep, is ignored if negative
2726     * @param versionsDeleted number of versions to keep for deleted resources, is ignored if negative
2727     * @param timeDeleted deleted resources older than this will also be deleted, is ignored if negative
2728     * @param report the report for output logging
2729     *
2730     * @throws CmsException if operation was not successful
2731     */
2732    public void deleteHistoricalVersions(
2733        CmsDbContext dbc,
2734        int versionsToKeep,
2735        int versionsDeleted,
2736        long timeDeleted,
2737        I_CmsReport report)
2738    throws CmsException {
2739
2740        report.println(Messages.get().container(Messages.RPT_START_DELETE_VERSIONS_0), I_CmsReport.FORMAT_HEADLINE);
2741        if (versionsToKeep >= 0) {
2742            report.println(
2743                Messages.get().container(Messages.RPT_START_DELETE_ACT_VERSIONS_1, Integer.valueOf(versionsToKeep)),
2744                I_CmsReport.FORMAT_HEADLINE);
2745
2746            List<I_CmsHistoryResource> resources = getHistoryDriver(dbc).getAllNotDeletedEntries(dbc);
2747            if (resources.isEmpty()) {
2748                report.println(Messages.get().container(Messages.RPT_DELETE_NOTHING_0), I_CmsReport.FORMAT_OK);
2749            }
2750            int n = resources.size();
2751            int m = 1;
2752            Iterator<I_CmsHistoryResource> itResources = resources.iterator();
2753            while (itResources.hasNext()) {
2754                I_CmsHistoryResource histResource = itResources.next();
2755
2756                report.print(
2757                    org.opencms.report.Messages.get().container(
2758                        org.opencms.report.Messages.RPT_SUCCESSION_2,
2759                        String.valueOf(m),
2760                        String.valueOf(n)),
2761                    I_CmsReport.FORMAT_NOTE);
2762                report.print(
2763                    org.opencms.report.Messages.get().container(
2764                        org.opencms.report.Messages.RPT_ARGUMENT_1,
2765                        dbc.removeSiteRoot(histResource.getRootPath())));
2766                report.print(org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_DOTS_0));
2767
2768                try {
2769                    int deleted = getHistoryDriver(dbc).deleteEntries(dbc, histResource, versionsToKeep, -1);
2770
2771                    report.print(
2772                        Messages.get().container(Messages.RPT_VERSION_DELETING_1, Integer.valueOf(deleted)),
2773                        I_CmsReport.FORMAT_NOTE);
2774                    report.print(org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_DOTS_0));
2775                    report.println(
2776                        org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_OK_0),
2777                        I_CmsReport.FORMAT_OK);
2778                } catch (CmsDataAccessException e) {
2779                    report.println(
2780                        org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_ERROR_0),
2781                        I_CmsReport.FORMAT_ERROR);
2782
2783                    if (LOG.isDebugEnabled()) {
2784                        LOG.debug(e.getLocalizedMessage(), e);
2785                    }
2786                }
2787
2788                m++;
2789            }
2790
2791            report.println(
2792                Messages.get().container(Messages.RPT_END_DELETE_ACT_VERSIONS_0),
2793                I_CmsReport.FORMAT_HEADLINE);
2794        }
2795        if ((versionsDeleted >= 0) || (timeDeleted >= 0)) {
2796            if (timeDeleted >= 0) {
2797                report.println(
2798                    Messages.get().container(
2799                        Messages.RPT_START_DELETE_DEL_VERSIONS_2,
2800                        Integer.valueOf(versionsDeleted),
2801                        new Date(timeDeleted)),
2802                    I_CmsReport.FORMAT_HEADLINE);
2803            } else {
2804                report.println(
2805                    Messages.get().container(Messages.RPT_START_DELETE_DEL_VERSIONS_1, Integer.valueOf(versionsDeleted)),
2806                    I_CmsReport.FORMAT_HEADLINE);
2807            }
2808            List<I_CmsHistoryResource> resources = getHistoryDriver(dbc).getAllDeletedEntries(dbc);
2809            if (resources.isEmpty()) {
2810                report.println(Messages.get().container(Messages.RPT_DELETE_NOTHING_0), I_CmsReport.FORMAT_OK);
2811            }
2812            int n = resources.size();
2813            int m = 1;
2814            Iterator<I_CmsHistoryResource> itResources = resources.iterator();
2815            while (itResources.hasNext()) {
2816                I_CmsHistoryResource histResource = itResources.next();
2817
2818                report.print(
2819                    org.opencms.report.Messages.get().container(
2820                        org.opencms.report.Messages.RPT_SUCCESSION_2,
2821                        String.valueOf(m),
2822                        String.valueOf(n)),
2823                    I_CmsReport.FORMAT_NOTE);
2824                report.print(
2825                    org.opencms.report.Messages.get().container(
2826                        org.opencms.report.Messages.RPT_ARGUMENT_1,
2827                        dbc.removeSiteRoot(histResource.getRootPath())));
2828                report.print(org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_DOTS_0));
2829
2830                try {
2831                    int deleted = getHistoryDriver(dbc).deleteEntries(dbc, histResource, versionsDeleted, timeDeleted);
2832
2833                    report.print(
2834                        Messages.get().container(Messages.RPT_VERSION_DELETING_1, Integer.valueOf(deleted)),
2835                        I_CmsReport.FORMAT_NOTE);
2836                    report.print(org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_DOTS_0));
2837                    report.println(
2838                        org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_OK_0),
2839                        I_CmsReport.FORMAT_OK);
2840                } catch (CmsDataAccessException e) {
2841                    report.println(
2842                        org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_ERROR_0),
2843                        I_CmsReport.FORMAT_ERROR);
2844
2845                    if (LOG.isDebugEnabled()) {
2846                        LOG.debug(e.getLocalizedMessage(), e);
2847                    }
2848                }
2849
2850                m++;
2851            }
2852            report.println(
2853                Messages.get().container(Messages.RPT_END_DELETE_DEL_VERSIONS_0),
2854                I_CmsReport.FORMAT_HEADLINE);
2855        }
2856        report.println(Messages.get().container(Messages.RPT_END_DELETE_VERSIONS_0), I_CmsReport.FORMAT_HEADLINE);
2857    }
2858
2859    /**
2860     * Deletes all log entries matching the given filter.<p>
2861     *
2862     * @param dbc the current db context
2863     * @param filter the filter to use for deletion
2864     *
2865     * @throws CmsException if something goes wrong
2866     *
2867     * @see CmsSecurityManager#deleteLogEntries(CmsRequestContext, CmsLogFilter)
2868     */
2869    public void deleteLogEntries(CmsDbContext dbc, CmsLogFilter filter) throws CmsException {
2870
2871        updateLog(dbc);
2872        m_projectDriver.deleteLog(dbc, filter);
2873    }
2874
2875    /**
2876     * Deletes an organizational unit.<p>
2877     *
2878     * Only organizational units that contain no suborganizational unit can be deleted.<p>
2879     *
2880     * The organizational unit can not be delete if it is used in the request context,
2881     * or if the current user belongs to it.<p>
2882     *
2883     * All users and groups in the given organizational unit will be deleted.<p>
2884     *
2885     * @param dbc the current db context
2886     * @param organizationalUnit the organizational unit to delete
2887     *
2888     * @throws CmsException if operation was not successful
2889     *
2890     * @see org.opencms.security.CmsOrgUnitManager#deleteOrganizationalUnit(CmsObject, String)
2891     */
2892    public void deleteOrganizationalUnit(CmsDbContext dbc, CmsOrganizationalUnit organizationalUnit)
2893    throws CmsException {
2894
2895        // check organizational unit in context
2896        if (dbc.getRequestContext().getOuFqn().equals(organizationalUnit.getName())) {
2897            throw new CmsDbConsistencyException(
2898                Messages.get().container(Messages.ERR_ORGUNIT_DELETE_IN_CONTEXT_1, organizationalUnit.getName()));
2899        }
2900        // check organizational unit for user
2901        if (dbc.currentUser().getOuFqn().equals(organizationalUnit.getName())) {
2902            throw new CmsDbConsistencyException(
2903                Messages.get().container(Messages.ERR_ORGUNIT_DELETE_CURRENT_USER_1, organizationalUnit.getName()));
2904        }
2905        // check sub organizational units
2906        if (!getOrganizationalUnits(dbc, organizationalUnit, true).isEmpty()) {
2907            throw new CmsDbConsistencyException(
2908                Messages.get().container(Messages.ERR_ORGUNIT_DELETE_SUB_ORGUNITS_1, organizationalUnit.getName()));
2909        }
2910        // check groups
2911        List<CmsGroup> groups = getGroups(dbc, organizationalUnit, true, false);
2912        Iterator<CmsGroup> itGroups = groups.iterator();
2913        while (itGroups.hasNext()) {
2914            CmsGroup group = itGroups.next();
2915            if (!OpenCms.getDefaultUsers().isDefaultGroup(group.getName())) {
2916                throw new CmsDbConsistencyException(
2917                    Messages.get().container(Messages.ERR_ORGUNIT_DELETE_GROUPS_1, organizationalUnit.getName()));
2918            }
2919        }
2920        // check users
2921        if (!getUsers(dbc, organizationalUnit, true).isEmpty()) {
2922            throw new CmsDbConsistencyException(
2923                Messages.get().container(Messages.ERR_ORGUNIT_DELETE_USERS_1, organizationalUnit.getName()));
2924        }
2925
2926        // delete default groups if needed
2927        itGroups = groups.iterator();
2928        while (itGroups.hasNext()) {
2929            CmsGroup group = itGroups.next();
2930            deleteGroup(dbc, group, null);
2931        }
2932
2933        // delete projects
2934        Iterator<CmsProject> itProjects = getProjectDriver(dbc).readProjects(
2935            dbc,
2936            organizationalUnit.getName()).iterator();
2937        while (itProjects.hasNext()) {
2938            CmsProject project = itProjects.next();
2939            deleteProject(dbc, project, false);
2940        }
2941
2942        // delete roles
2943        Iterator<CmsGroup> itRoles = getGroups(dbc, organizationalUnit, true, true).iterator();
2944        while (itRoles.hasNext()) {
2945            CmsGroup role = itRoles.next();
2946            deleteGroup(dbc, role, null);
2947        }
2948
2949        // create a publish list for the 'virtual' publish event
2950        CmsResource resource = readResource(dbc, organizationalUnit.getId(), CmsResourceFilter.DEFAULT);
2951        CmsPublishList pl = new CmsPublishList(resource, false);
2952        pl.add(resource, false);
2953
2954        // remove the organizational unit itself
2955        getUserDriver(dbc).deleteOrganizationalUnit(dbc, organizationalUnit);
2956
2957        // write the publish history entry
2958        getProjectDriver(dbc).writePublishHistory(
2959            dbc,
2960            pl.getPublishHistoryId(),
2961            new CmsPublishedResource(resource, -1, CmsResourceState.STATE_DELETED));
2962
2963        // flush relevant caches
2964        m_monitor.clearPrincipalsCache();
2965        m_monitor.flushCache(CmsMemoryMonitor.CacheType.PROPERTY, CmsMemoryMonitor.CacheType.PROPERTY_LIST);
2966
2967        // fire the 'virtual' publish event
2968        Map<String, Object> eventData = new HashMap<String, Object>();
2969        eventData.put(I_CmsEventListener.KEY_PUBLISHID, pl.getPublishHistoryId().toString());
2970        eventData.put(I_CmsEventListener.KEY_PROJECTID, dbc.currentProject().getUuid());
2971        eventData.put(I_CmsEventListener.KEY_DBCONTEXT, dbc);
2972        CmsEvent afterPublishEvent = new CmsEvent(I_CmsEventListener.EVENT_PUBLISH_PROJECT, eventData);
2973        OpenCms.fireCmsEvent(afterPublishEvent);
2974
2975        m_lockManager.removeDeletedResource(dbc, resource.getRootPath());
2976
2977        if (!dbc.getProjectId().isNullUUID()) {
2978            // OU modified event is not needed
2979            return;
2980        }
2981        // fire OU modified event
2982        Map<String, Object> event2Data = new HashMap<String, Object>();
2983        event2Data.put(I_CmsEventListener.KEY_OU_NAME, organizationalUnit.getName());
2984        event2Data.put(I_CmsEventListener.KEY_USER_ACTION, I_CmsEventListener.VALUE_OU_MODIFIED_ACTION_DELETE);
2985        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_OU_MODIFIED, event2Data));
2986
2987    }
2988
2989    /**
2990     * Deletes a project.<p>
2991     *
2992     * Only the admin or the owner of the project can do this.
2993     *
2994     * @param dbc the current database context
2995     * @param deleteProject the project to be deleted
2996     *
2997     * @throws CmsException if something goes wrong
2998     */
2999    public void deleteProject(CmsDbContext dbc, CmsProject deleteProject) throws CmsException {
3000
3001        deleteProject(dbc, deleteProject, true);
3002    }
3003
3004    /**
3005     * Deletes a project.<p>
3006     *
3007     * Only the admin or the owner of the project can do this.
3008     *
3009     * @param dbc the current database context
3010     * @param deleteProject the project to be deleted
3011     * @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
3012     *
3013     * @throws CmsException if something goes wrong
3014     */
3015    public void deleteProject(CmsDbContext dbc, CmsProject deleteProject, boolean resetResources) throws CmsException {
3016
3017        CmsUUID projectId = deleteProject.getUuid();
3018
3019        if (resetResources) {
3020            // changed/new/deleted files in the specified project
3021            List<CmsResource> modifiedFiles = readChangedResourcesInsideProject(dbc, projectId, RCPRM_FILES_ONLY_MODE);
3022            // changed/new/deleted folders in the specified project
3023            List<CmsResource> modifiedFolders = readChangedResourcesInsideProject(
3024                dbc,
3025                projectId,
3026                RCPRM_FOLDERS_ONLY_MODE);
3027            resetResourcesInProject(dbc, projectId, modifiedFiles, modifiedFolders);
3028        }
3029
3030        // unlock all resources in the project
3031        m_lockManager.removeResourcesInProject(deleteProject.getUuid(), true);
3032        m_monitor.clearAccessControlListCache();
3033        m_monitor.clearResourceCache();
3034
3035        // set project to online project if current project is the one which will be deleted
3036        if (projectId.equals(dbc.currentProject().getUuid())) {
3037            dbc.getRequestContext().setCurrentProject(readProject(dbc, CmsProject.ONLINE_PROJECT_ID));
3038        }
3039
3040        // delete the project itself
3041        getProjectDriver(dbc).deleteProject(dbc, deleteProject);
3042        m_monitor.uncacheProject(deleteProject);
3043
3044        // fire the corresponding event
3045        OpenCms.fireCmsEvent(
3046            new CmsEvent(
3047                I_CmsEventListener.EVENT_PROJECT_MODIFIED,
3048                Collections.<String, Object> singletonMap("project", deleteProject)));
3049
3050    }
3051
3052    /**
3053     * Deletes a property definition.<p>
3054     *
3055     * @param dbc the current database context
3056     * @param name the name of the property definition to delete
3057     *
3058     * @throws CmsException if something goes wrong
3059     */
3060    public void deletePropertyDefinition(CmsDbContext dbc, String name) throws CmsException {
3061
3062        CmsPropertyDefinition propertyDefinition = null;
3063
3064        try {
3065            // first read and then delete the metadefinition.
3066            propertyDefinition = readPropertyDefinition(dbc, name);
3067            getVfsDriver(dbc).deletePropertyDefinition(dbc, propertyDefinition);
3068            getHistoryDriver(dbc).deletePropertyDefinition(dbc, propertyDefinition);
3069        } finally {
3070
3071            // fire an event that a property of a resource has been deleted
3072            OpenCms.fireCmsEvent(
3073                new CmsEvent(
3074                    I_CmsEventListener.EVENT_PROPERTY_DEFINITION_MODIFIED,
3075                    Collections.<String, Object> singletonMap("propertyDefinition", propertyDefinition)));
3076        }
3077    }
3078
3079    /**
3080     * Deletes a publish job identified by its history id.<p>
3081     *
3082     * @param dbc the current database context
3083     * @param publishHistoryId the history id identifying the publish job
3084     *
3085     * @throws CmsException if something goes wrong
3086     */
3087    public void deletePublishJob(CmsDbContext dbc, CmsUUID publishHistoryId) throws CmsException {
3088
3089        getProjectDriver(dbc).deletePublishJob(dbc, publishHistoryId);
3090    }
3091
3092    /**
3093     * Deletes the publish list assigned to a publish job.<p>
3094     *
3095     * @param dbc the current database context
3096     * @param publishHistoryId the history id identifying the publish job
3097     * @throws CmsException if something goes wrong
3098     */
3099    public void deletePublishList(CmsDbContext dbc, CmsUUID publishHistoryId) throws CmsException {
3100
3101        getProjectDriver(dbc).deletePublishList(dbc, publishHistoryId);
3102    }
3103
3104    /**
3105     * Deletes all relations for the given resource matching the given filter.<p>
3106     *
3107     * @param dbc the current db context
3108     * @param resource the resource to delete the relations for
3109     * @param filter the filter to use for deletion
3110     *
3111     * @throws CmsException if something goes wrong
3112     *
3113     * @see CmsSecurityManager#deleteRelationsForResource(CmsRequestContext, CmsResource, CmsRelationFilter)
3114     */
3115    public void deleteRelationsForResource(CmsDbContext dbc, CmsResource resource, CmsRelationFilter filter)
3116    throws CmsException {
3117
3118        if (filter.includesDefinedInContent()) {
3119            throw new CmsIllegalArgumentException(
3120                Messages.get().container(
3121                    Messages.ERR_DELETE_RELATION_IN_CONTENT_2,
3122                    dbc.removeSiteRoot(resource.getRootPath()),
3123                    filter.getTypes()));
3124        }
3125        getVfsDriver(dbc).deleteRelations(dbc, dbc.currentProject().getUuid(), resource, filter);
3126        setDateLastModified(dbc, resource, System.currentTimeMillis());
3127        log(
3128            dbc,
3129            new CmsLogEntry(
3130                dbc,
3131                resource.getStructureId(),
3132                CmsLogEntryType.RESOURCE_REMOVE_RELATION,
3133                new String[] {resource.getRootPath(), filter.toString()}),
3134            false);
3135    }
3136
3137    /**
3138     * Deletes a resource.<p>
3139     *
3140     * The <code>siblingMode</code> parameter controls how to handle siblings
3141     * during the delete operation.
3142     * Possible values for this parameter are:
3143     * <ul>
3144     * <li><code>{@link CmsResource#DELETE_REMOVE_SIBLINGS}</code></li>
3145     * <li><code>{@link CmsResource#DELETE_PRESERVE_SIBLINGS}</code></li>
3146     * </ul><p>
3147     *
3148     * @param dbc the current database context
3149     * @param resource the name of the resource to delete (full path)
3150     * @param siblingMode indicates how to handle siblings of the deleted resource
3151     *
3152     * @throws CmsException if something goes wrong
3153     *
3154     * @see CmsObject#deleteResource(String, CmsResource.CmsResourceDeleteMode)
3155     * @see I_CmsResourceType#deleteResource(CmsObject, CmsSecurityManager, CmsResource, CmsResource.CmsResourceDeleteMode)
3156     */
3157    public void deleteResource(CmsDbContext dbc, CmsResource resource, CmsResource.CmsResourceDeleteMode siblingMode)
3158    throws CmsException {
3159
3160        // upgrade a potential inherited, non-shared lock into a common lock
3161        CmsLock currentLock = getLock(dbc, resource);
3162        if (currentLock.getEditionLock().isDirectlyInherited()) {
3163            // upgrade the lock status if required
3164            lockResource(dbc, resource, CmsLockType.EXCLUSIVE);
3165        }
3166
3167        // check if siblings of the resource exist and must be deleted as well
3168        if (resource.isFolder()) {
3169            // folder can have no siblings
3170            siblingMode = CmsResource.DELETE_PRESERVE_SIBLINGS;
3171        }
3172
3173        // if selected, add all siblings of this resource to the list of resources to be deleted
3174        boolean allSiblingsRemoved;
3175        List<CmsResource> resources;
3176        if (siblingMode == CmsResource.DELETE_REMOVE_SIBLINGS) {
3177            resources = new ArrayList<CmsResource>(readSiblings(dbc, resource, CmsResourceFilter.ALL));
3178            allSiblingsRemoved = true;
3179
3180            // ensure that the resource requested to be deleted is the last resource that gets actually deleted
3181            // to keep the shared locks of the siblings while those get deleted.
3182            resources.remove(resource);
3183            resources.add(resource);
3184        } else {
3185            // only delete the resource, no siblings
3186            resources = Collections.singletonList(resource);
3187            allSiblingsRemoved = false;
3188        }
3189
3190        int size = resources.size();
3191        // if we have only one resource no further check is required
3192        if (size > 1) {
3193            CmsMultiException me = new CmsMultiException();
3194            // ensure that each sibling is unlocked or locked by the current user
3195            for (int i = 0; i < size; i++) {
3196                CmsResource currentResource = resources.get(i);
3197                currentLock = getLock(dbc, currentResource);
3198                if (!currentLock.getEditionLock().isUnlocked() && !currentLock.isOwnedBy(dbc.currentUser())) {
3199                    // the resource is locked by a user different from the current user
3200                    CmsRequestContext context = dbc.getRequestContext();
3201                    me.addException(
3202                        new CmsLockException(
3203                            org.opencms.lock.Messages.get().container(
3204                                org.opencms.lock.Messages.ERR_SIBLING_LOCKED_2,
3205                                context.getSitePath(currentResource),
3206                                context.getSitePath(resource))));
3207                }
3208            }
3209            if (!me.getExceptions().isEmpty()) {
3210                throw me;
3211            }
3212        }
3213
3214        boolean removeAce = true;
3215
3216        if (resource.isFolder()) {
3217            // check if the folder has any resources in it
3218            Iterator<CmsResource> childResources = getVfsDriver(
3219                dbc).readChildResources(dbc, dbc.currentProject(), resource, true, true).iterator();
3220
3221            CmsUUID projectId = CmsProject.ONLINE_PROJECT_ID;
3222            if (dbc.currentProject().isOnlineProject()) {
3223                projectId = CmsUUID.getOpenCmsUUID(); // HACK: to get an offline project id
3224            }
3225
3226            // collect the names of the resources inside the folder, excluding the moved resources
3227            StringBuffer errorResNames = new StringBuffer(128);
3228            while (childResources.hasNext()) {
3229                CmsResource errorRes = childResources.next();
3230                if (errorRes.getState().isDeleted()) {
3231                    continue;
3232                }
3233                // if deleting offline, or not moved, or just renamed inside the deleted folder
3234                // so, it may remain some orphan online entries for moved resources
3235                // which will be fixed during the publishing of the moved resources
3236                boolean error = !dbc.currentProject().isOnlineProject();
3237                if (!error) {
3238                    try {
3239                        String originalPath = getVfsDriver(
3240                            dbc).readResource(dbc, projectId, errorRes.getRootPath(), true).getRootPath();
3241                        error = originalPath.equals(errorRes.getRootPath())
3242                            || originalPath.startsWith(resource.getRootPath());
3243                    } catch (CmsVfsResourceNotFoundException e) {
3244                        // ignore
3245                    }
3246                }
3247                if (error) {
3248                    if (errorResNames.length() != 0) {
3249                        errorResNames.append(", ");
3250                    }
3251                    errorResNames.append("[" + dbc.removeSiteRoot(errorRes.getRootPath()) + "]");
3252                }
3253            }
3254
3255            // the current implementation only deletes empty folders
3256            if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(errorResNames.toString())) {
3257                throw new CmsVfsException(
3258                    org.opencms.db.generic.Messages.get().container(
3259                        org.opencms.db.generic.Messages.ERR_DELETE_NONEMTY_FOLDER_2,
3260                        dbc.removeSiteRoot(resource.getRootPath()),
3261                        errorResNames.toString()));
3262            }
3263        }
3264
3265        // delete all collected resources
3266        for (int i = 0; i < size; i++) {
3267            CmsResource currentResource = resources.get(i);
3268
3269            // try to delete/remove the resource only if the user has write access to the resource
3270            // check permissions only for the sibling, the resource it self was already checked or
3271            // is to be removed without write permissions, ie. while deleting a folder
3272            if (!currentResource.equals(resource)
3273                && (I_CmsPermissionHandler.PERM_ALLOWED != m_securityManager.hasPermissions(
3274                    dbc,
3275                    currentResource,
3276                    CmsPermissionSet.ACCESS_WRITE,
3277                    LockCheck.yes,
3278                    CmsResourceFilter.ALL))) {
3279
3280                // no write access to sibling - must keep ACE (see below)
3281                allSiblingsRemoved = false;
3282            } else {
3283                // write access to sibling granted
3284                boolean existsOnline = (getVfsDriver(dbc).validateStructureIdExists(
3285                    dbc,
3286                    CmsProject.ONLINE_PROJECT_ID,
3287                    currentResource.getStructureId()) || !(currentResource.getState().equals(CmsResource.STATE_NEW)));
3288                if (!existsOnline) {
3289                    // the resource does not exist online => remove the resource
3290                    // this means the resource is "new" (blue) in the offline project
3291
3292                    // delete all properties of this resource
3293                    deleteAllProperties(dbc, currentResource.getRootPath());
3294
3295                    if (currentResource.isFolder()) {
3296                        getVfsDriver(dbc).removeFolder(dbc, dbc.currentProject(), currentResource);
3297                    } else {
3298                        // check labels
3299                        if (currentResource.isLabeled() && !labelResource(dbc, currentResource, null, 2)) {
3300                            // update the resource flags to "un label" the other siblings
3301                            int flags = currentResource.getFlags();
3302                            flags &= ~CmsResource.FLAG_LABELED;
3303                            currentResource.setFlags(flags);
3304                        }
3305                        getVfsDriver(dbc).removeFile(dbc, dbc.currentProject().getUuid(), currentResource);
3306                    }
3307
3308                    // ensure an exclusive lock is removed in the lock manager for a deleted new resource,
3309                    // otherwise it would "stick" in the lock manager, preventing other users from creating
3310                    // a file with the same name (issue with temp files in editor)
3311                    m_lockManager.removeDeletedResource(dbc, currentResource.getRootPath());
3312                    // delete relations
3313                    getVfsDriver(dbc).deleteRelations(
3314                        dbc,
3315                        dbc.currentProject().getUuid(),
3316                        currentResource,
3317                        CmsRelationFilter.TARGETS);
3318                    getVfsDriver(dbc).deleteUrlNameMappingEntries(
3319                        dbc,
3320                        false,
3321                        CmsUrlNameMappingFilter.ALL.filterStructureId(currentResource.getStructureId()));
3322                    getVfsDriver(dbc).deleteAliases(
3323                        dbc,
3324                        dbc.currentProject(),
3325                        new CmsAliasFilter(null, null, currentResource.getStructureId()));
3326                    log(
3327                        dbc,
3328                        new CmsLogEntry(
3329                            dbc,
3330                            currentResource.getStructureId(),
3331                            CmsLogEntryType.RESOURCE_NEW_DELETED,
3332                            new String[] {currentResource.getRootPath()}),
3333                        true);
3334                } else {
3335                    // the resource exists online => mark the resource as deleted
3336                    // structure record is removed during next publish
3337                    // if one (or more) siblings are not removed, the ACE can not be removed
3338                    removeAce = false;
3339                    // set resource state to deleted
3340                    currentResource.setState(CmsResource.STATE_DELETED);
3341                    getVfsDriver(
3342                        dbc).writeResourceState(dbc, dbc.currentProject(), currentResource, UPDATE_STRUCTURE, false);
3343
3344                    // update the project ID
3345                    getVfsDriver(dbc).writeLastModifiedProjectId(
3346                        dbc,
3347                        dbc.currentProject(),
3348                        dbc.currentProject().getUuid(),
3349                        currentResource);
3350                    // log it
3351
3352                    log(
3353                        dbc,
3354                        new CmsLogEntry(
3355                            dbc,
3356                            currentResource.getStructureId(),
3357                            CmsLogEntryType.RESOURCE_DELETED,
3358                            new String[] {currentResource.getRootPath()}),
3359                        true);
3360                }
3361            }
3362        }
3363
3364        if ((resource.getSiblingCount() <= 1) || allSiblingsRemoved) {
3365            if (removeAce) {
3366                // remove the access control entries
3367                getUserDriver(dbc).removeAccessControlEntries(dbc, dbc.currentProject(), resource.getResourceId());
3368            }
3369        }
3370
3371        // flush all caches
3372        m_monitor.clearAccessControlListCache();
3373        m_monitor.flushCache(
3374            CmsMemoryMonitor.CacheType.PROPERTY,
3375            CmsMemoryMonitor.CacheType.PROPERTY_LIST,
3376            CmsMemoryMonitor.CacheType.PROJECT_RESOURCES);
3377
3378        Map<String, Object> eventData = new HashMap<String, Object>();
3379        eventData.put(I_CmsEventListener.KEY_RESOURCES, resources);
3380        eventData.put(I_CmsEventListener.KEY_DBCONTEXT, dbc);
3381        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_DELETED, eventData));
3382    }
3383
3384    /**
3385     * Deletes an entry in the published resource table.<p>
3386     *
3387     * @param dbc the current database context
3388     * @param resourceName The name of the resource to be deleted in the static export
3389     * @param linkType the type of resource deleted (0= non-parameter, 1=parameter)
3390     * @param linkParameter the parameters of the resource
3391     *
3392     * @throws CmsException if something goes wrong
3393     */
3394    public void deleteStaticExportPublishedResource(
3395        CmsDbContext dbc,
3396        String resourceName,
3397        int linkType,
3398        String linkParameter)
3399    throws CmsException {
3400
3401        getProjectDriver(dbc).deleteStaticExportPublishedResource(dbc, resourceName, linkType, linkParameter);
3402    }
3403
3404    /**
3405     * Deletes a user, where all permissions and resources attributes of the user
3406     * were transfered to a replacement user, if given.<p>
3407     *
3408     * Only users, which are in the group "administrators" are granted.<p>
3409     *
3410     * @param dbc the current database context
3411     * @param project the current project
3412     * @param username the name of the user to be deleted
3413     * @param replacementUsername the name of the user to be transfered, can be <code>null</code>
3414     *
3415     * @throws CmsException if operation was not successful
3416     */
3417    public void deleteUser(CmsDbContext dbc, CmsProject project, String username, String replacementUsername)
3418    throws CmsException {
3419
3420        // Test if the users exists
3421        CmsUser user = readUser(dbc, username);
3422        CmsUser replacementUser = null;
3423        if (replacementUsername != null) {
3424            replacementUser = readUser(dbc, replacementUsername);
3425        }
3426
3427        CmsProject onlineProject = readProject(dbc, CmsProject.ONLINE_PROJECT_ID);
3428        boolean withACEs = true;
3429        if (replacementUser == null) {
3430            withACEs = false;
3431            replacementUser = readUser(dbc, OpenCms.getDefaultUsers().getUserDeletedResource());
3432        }
3433
3434        boolean isVfsManager = m_securityManager.hasRole(dbc, replacementUser, CmsRole.VFS_MANAGER);
3435
3436        // iterate groups and roles
3437        for (int i = 0; i < 2; i++) {
3438            boolean readRoles = i != 0;
3439            Iterator<CmsGroup> itGroups = getGroupsOfUser(
3440                dbc,
3441                username,
3442                "",
3443                true,
3444                readRoles,
3445                true,
3446                dbc.getRequestContext().getRemoteAddress()).iterator();
3447            while (itGroups.hasNext()) {
3448                CmsGroup group = itGroups.next();
3449                if (!isVfsManager) {
3450                    // add replacement user to user groups
3451                    if (!userInGroup(dbc, replacementUser.getName(), group.getName(), readRoles)) {
3452                        addUserToGroup(dbc, replacementUser.getName(), group.getName(), readRoles);
3453                    }
3454                }
3455                // remove user from groups
3456                if (userInGroup(dbc, username, group.getName(), readRoles)) {
3457                    // we need this additional check because removing a user from a group
3458                    // may also automatically remove him from other groups if the group was
3459                    // associated with a role.
3460                    removeUserFromGroup(dbc, username, group.getName(), readRoles);
3461                }
3462            }
3463        }
3464        // remove all locks set for the deleted user
3465        m_lockManager.removeLocks(user.getId());
3466        // offline
3467        if (dbc.getProjectId().isNullUUID()) {
3468            // offline project available
3469            transferPrincipalResources(dbc, project, user.getId(), replacementUser.getId(), withACEs);
3470        }
3471        // online
3472        transferPrincipalResources(dbc, onlineProject, user.getId(), replacementUser.getId(), withACEs);
3473        getUserDriver(dbc).removeAccessControlEntriesForPrincipal(dbc, project, onlineProject, user.getId());
3474        getHistoryDriver(dbc).writePrincipal(dbc, user);
3475        getUserDriver(dbc).deleteUser(dbc, username);
3476        // delete user from cache
3477        m_monitor.clearUserCache(user);
3478
3479        if (!dbc.getProjectId().isNullUUID()) {
3480            // user modified event is not needed
3481            return;
3482        }
3483        // fire user modified event
3484        Map<String, Object> eventData = new HashMap<String, Object>();
3485        eventData.put(I_CmsEventListener.KEY_USER_ID, user.getId().toString());
3486        eventData.put(I_CmsEventListener.KEY_USER_NAME, user.getName());
3487        eventData.put(I_CmsEventListener.KEY_USER_ACTION, I_CmsEventListener.VALUE_USER_MODIFIED_ACTION_DELETE_USER);
3488        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_USER_MODIFIED, eventData));
3489    }
3490
3491    /**
3492     * Destroys this driver manager and releases all allocated resources.<p>
3493     */
3494    public void destroy() {
3495
3496        try {
3497            if (m_projectDriver != null) {
3498                try {
3499                    m_projectDriver.destroy();
3500                } catch (Throwable t) {
3501                    LOG.error(Messages.get().getBundle().key(Messages.ERR_CLOSE_PROJECT_DRIVER_0), t);
3502                }
3503                m_projectDriver = null;
3504            }
3505            if (m_userDriver != null) {
3506                try {
3507                    m_userDriver.destroy();
3508                } catch (Throwable t) {
3509                    LOG.error(Messages.get().getBundle().key(Messages.ERR_CLOSE_USER_DRIVER_0), t);
3510                }
3511                m_userDriver = null;
3512            }
3513            if (m_vfsDriver != null) {
3514                try {
3515                    m_vfsDriver.destroy();
3516                } catch (Throwable t) {
3517                    LOG.error(Messages.get().getBundle().key(Messages.ERR_CLOSE_VFS_DRIVER_0), t);
3518                }
3519                m_vfsDriver = null;
3520            }
3521            if (m_historyDriver != null) {
3522                try {
3523                    m_historyDriver.destroy();
3524                } catch (Throwable t) {
3525                    LOG.error(Messages.get().getBundle().key(Messages.ERR_CLOSE_HISTORY_DRIVER_0), t);
3526                }
3527                m_historyDriver = null;
3528            }
3529
3530            if (m_pools != null) {
3531                for (CmsDbPoolV11 pool : m_pools.values()) {
3532                    try {
3533                        pool.close();
3534                        if (CmsLog.INIT.isDebugEnabled()) {
3535                            CmsLog.INIT.debug(Messages.get().getBundle().key(Messages.INIT_CLOSE_CONN_POOL_1, pool));
3536                        }
3537
3538                    } catch (Throwable t) {
3539                        LOG.error(Messages.get().getBundle().key(Messages.LOG_CLOSE_CONN_POOL_ERROR_1, pool), t);
3540                    }
3541                }
3542                m_pools.clear();
3543            }
3544
3545            m_monitor.clearCache();
3546
3547            m_lockManager = null;
3548            m_htmlLinkValidator = null;
3549        } catch (Throwable t) {
3550            // ignore
3551        }
3552        if (CmsLog.INIT.isInfoEnabled()) {
3553            CmsLog.INIT.info(
3554                Messages.get().getBundle().key(Messages.INIT_DRIVER_MANAGER_DESTROY_1, getClass().getName()));
3555        }
3556    }
3557
3558    /**
3559     * Tests if a resource with the given resourceId does already exist in the Database.<p>
3560     *
3561     * @param dbc the current database context
3562     * @param resourceId the resource id to test for
3563     * @return true if a resource with the given id was found, false otherweise
3564     * @throws CmsException if something goes wrong
3565     */
3566    public boolean existsResourceId(CmsDbContext dbc, CmsUUID resourceId) throws CmsException {
3567
3568        return getVfsDriver(dbc).validateResourceIdExists(dbc, dbc.currentProject().getUuid(), resourceId);
3569    }
3570
3571    /**
3572     * Fills the given publish list with the the VFS resources that actually get published.<p>
3573     *
3574     * Please refer to the source code of this method for the rules on how to decide whether a
3575     * new/changed/deleted <code>{@link CmsResource}</code> object can be published or not.<p>
3576     *
3577     * @param dbc the current database context
3578     * @param publishList must be initialized with basic publish information (Project or direct publish operation),
3579     *                    the given publish list will be filled with all new/changed/deleted files from the current
3580     *                    (offline) project that will be actually published
3581     *
3582     * @throws CmsException if something goes wrong
3583     *
3584     * @see org.opencms.db.CmsPublishList
3585     */
3586    public void fillPublishList(CmsDbContext dbc, CmsPublishList publishList) throws CmsException {
3587
3588        if (!publishList.isDirectPublish()) {
3589            // when publishing a project
3590            // all modified resources with the last change done in the current project are candidates if unlocked
3591            List<CmsResource> folderList = getVfsDriver(dbc).readResourceTree(
3592                dbc,
3593                dbc.currentProject().getUuid(),
3594                CmsDriverManager.READ_IGNORE_PARENT,
3595                CmsDriverManager.READ_IGNORE_TYPE,
3596                CmsResource.STATE_UNCHANGED,
3597                CmsDriverManager.READ_IGNORE_TIME,
3598                CmsDriverManager.READ_IGNORE_TIME,
3599                CmsDriverManager.READ_IGNORE_TIME,
3600                CmsDriverManager.READ_IGNORE_TIME,
3601                CmsDriverManager.READ_IGNORE_TIME,
3602                CmsDriverManager.READ_IGNORE_TIME,
3603                CmsDriverManager.READMODE_INCLUDE_TREE
3604                    | CmsDriverManager.READMODE_INCLUDE_PROJECT
3605                    | CmsDriverManager.READMODE_EXCLUDE_STATE
3606                    | CmsDriverManager.READMODE_ONLY_FOLDERS);
3607
3608            List<CmsResource> fileList = getVfsDriver(dbc).readResourceTree(
3609                dbc,
3610                dbc.currentProject().getUuid(),
3611                CmsDriverManager.READ_IGNORE_PARENT,
3612                CmsDriverManager.READ_IGNORE_TYPE,
3613                CmsResource.STATE_UNCHANGED,
3614                CmsDriverManager.READ_IGNORE_TIME,
3615                CmsDriverManager.READ_IGNORE_TIME,
3616                CmsDriverManager.READ_IGNORE_TIME,
3617                CmsDriverManager.READ_IGNORE_TIME,
3618                CmsDriverManager.READ_IGNORE_TIME,
3619                CmsDriverManager.READ_IGNORE_TIME,
3620                CmsDriverManager.READMODE_INCLUDE_TREE
3621                    | CmsDriverManager.READMODE_INCLUDE_PROJECT
3622                    | CmsDriverManager.READMODE_EXCLUDE_STATE
3623                    | CmsDriverManager.READMODE_ONLY_FILES);
3624            CmsRequestContext context = dbc.getRequestContext();
3625            if ((context != null)
3626                && (context.getAttribute(CmsDefaultWorkflowManager.ATTR_CHECK_PUBLISH_RESOURCE_LIMIT) != null)) {
3627
3628                // check if total size and if it exceeds the resource limit and the request
3629                // context attribute is set, throw an exception.
3630                // we do it here since filterResources() can be very expensive on large resource lists
3631
3632                int limit = OpenCms.getWorkflowManager().getResourceLimit();
3633                int total = fileList.size() + folderList.size();
3634                if (total > limit) {
3635                    throw new CmsTooManyPublishResourcesException(total);
3636                }
3637            }
3638            publishList.addAll(filterResources(dbc, null, folderList), true);
3639            publishList.addAll(filterResources(dbc, publishList, fileList), true);
3640        } else {
3641            // this is a direct publish
3642            Iterator<CmsResource> it = publishList.getDirectPublishResources().iterator();
3643            while (it.hasNext()) {
3644                // iterate all resources in the direct publish list
3645                CmsResource directPublishResource = it.next();
3646                if (directPublishResource.isFolder()) {
3647                    // when publishing a folder directly,
3648                    // the folder and all modified resources within the tree below this folder
3649                    // and with the last change done in the current project are candidates if lockable
3650                    CmsLock lock = getLock(dbc, directPublishResource);
3651                    if (!directPublishResource.getState().isUnchanged() && lock.isLockableBy(dbc.currentUser())) {
3652
3653                        try {
3654                            m_securityManager.checkPermissions(
3655                                dbc,
3656                                directPublishResource,
3657                                CmsPermissionSet.ACCESS_DIRECT_PUBLISH,
3658                                false,
3659                                CmsResourceFilter.ALL);
3660                            publishList.add(directPublishResource, true);
3661                        } catch (CmsException e) {
3662                            // skip if not enough permissions
3663                        }
3664                    }
3665                    boolean shouldPublishDeletedSubResources = publishList.isUserPublishList()
3666                        && directPublishResource.getState().isDeleted();
3667                    if (publishList.isPublishSubResources() || shouldPublishDeletedSubResources) {
3668                        addSubResources(dbc, publishList, directPublishResource, resource -> true);
3669                    }
3670                } else if (directPublishResource.isFile() && !directPublishResource.getState().isUnchanged()) {
3671
3672                    // when publishing a file directly this file is the only candidate
3673                    // if it is modified and lockable
3674                    CmsLock lock = getLock(dbc, directPublishResource);
3675                    if (lock.isLockableBy(dbc.currentUser())) {
3676                        // check permissions
3677                        try {
3678                            m_securityManager.checkPermissions(
3679                                dbc,
3680                                directPublishResource,
3681                                CmsPermissionSet.ACCESS_DIRECT_PUBLISH,
3682                                false,
3683                                CmsResourceFilter.ALL);
3684                            publishList.add(directPublishResource, true);
3685                        } catch (CmsException e) {
3686                            // skip if not enough permissions
3687                        }
3688                    }
3689                }
3690            }
3691        }
3692
3693        // Step 2: if desired, extend the list of files to publish with related siblings
3694        if (publishList.isPublishSiblings()) {
3695            List<CmsResource> publishFiles = publishList.getFileList();
3696            int size = publishFiles.size();
3697
3698            // Improved: first calculate closure of all siblings, then filter and add them
3699            Set<CmsResource> siblingsClosure = new HashSet<CmsResource>(publishFiles);
3700            for (int i = 0; i < size; i++) {
3701                CmsResource currentFile = publishFiles.get(i);
3702                if (currentFile.getSiblingCount() > 1) {
3703                    siblingsClosure.addAll(readSiblings(dbc, currentFile, CmsResourceFilter.ALL_MODIFIED));
3704                }
3705            }
3706            publishList.addAll(filterSiblings(dbc, publishList, siblingsClosure), true);
3707        }
3708        publishList.initialize();
3709    }
3710
3711    /**
3712     * Returns the list of access control entries of a resource given its name.<p>
3713     *
3714     * @param dbc the current database context
3715     * @param resource the resource to read the access control entries for
3716     * @param getInherited true if the result should include all access control entries inherited by parent folders
3717     *
3718     * @return a list of <code>{@link CmsAccessControlEntry}</code> objects defining all permissions for the given resource
3719     *
3720     * @throws CmsException if something goes wrong
3721     */
3722    public List<CmsAccessControlEntry> getAccessControlEntries(
3723        CmsDbContext dbc,
3724        CmsResource resource,
3725        boolean getInherited)
3726    throws CmsException {
3727
3728        // get the ACE of the resource itself
3729        I_CmsUserDriver userDriver = getUserDriver(dbc);
3730        I_CmsVfsDriver vfsDriver = getVfsDriver(dbc);
3731        List<CmsAccessControlEntry> ace = userDriver.readAccessControlEntries(
3732            dbc,
3733            dbc.currentProject(),
3734            resource.getResourceId(),
3735            false);
3736
3737        // sort and check if we got the 'overwrite all' ace to stop looking up
3738        boolean overwriteAll = sortAceList(ace);
3739
3740        // get the ACE of each parent folder
3741        // Note: for the immediate parent, get non-inherited access control entries too,
3742        // if the resource is not a folder
3743        String parentPath = CmsResource.getParentFolder(resource.getRootPath());
3744        int d = (resource.isFolder()) ? 1 : 0;
3745
3746        while (!overwriteAll && getInherited && (parentPath != null)) {
3747            resource = vfsDriver.readFolder(dbc, dbc.currentProject().getUuid(), parentPath);
3748            List<CmsAccessControlEntry> entries = userDriver.readAccessControlEntries(
3749                dbc,
3750                dbc.currentProject(),
3751                resource.getResourceId(),
3752                d > 0);
3753
3754            // sort and check if we got the 'overwrite all' ace to stop looking up
3755            overwriteAll = sortAceList(entries);
3756
3757            for (CmsAccessControlEntry e : entries) {
3758                e.setFlags(CmsAccessControlEntry.ACCESS_FLAGS_INHERITED);
3759            }
3760
3761            ace.addAll(entries);
3762            parentPath = CmsResource.getParentFolder(resource.getRootPath());
3763            d++;
3764        }
3765
3766        return ace;
3767    }
3768
3769    /**
3770     * Returns the full access control list of a given resource.<p>
3771     *
3772     * @param dbc the current database context
3773     * @param resource the resource
3774     *
3775     * @return the access control list of the resource
3776     *
3777     * @throws CmsException if something goes wrong
3778     */
3779    public CmsAccessControlList getAccessControlList(CmsDbContext dbc, CmsResource resource) throws CmsException {
3780
3781        return getAccessControlList(dbc, resource, false);
3782    }
3783
3784    /**
3785     * Returns the access control list of a given resource.<p>
3786     *
3787     * If <code>inheritedOnly</code> is set, only inherited access control entries
3788     * are returned.<p>
3789     *
3790     * Note: For file resources, *all* permissions set at the immediate parent folder are inherited,
3791     * not only these marked to inherit.
3792     *
3793     * @param dbc the current database context
3794     * @param resource the resource
3795     * @param inheritedOnly skip non-inherited entries if set
3796     *
3797     * @return the access control list of the resource
3798     *
3799     * @throws CmsException if something goes wrong
3800     */
3801    public CmsAccessControlList getAccessControlList(CmsDbContext dbc, CmsResource resource, boolean inheritedOnly)
3802    throws CmsException {
3803
3804        return getAccessControlList(dbc, resource, inheritedOnly, resource.isFolder(), 0);
3805    }
3806
3807    /**
3808     * Returns the number of active connections managed by a pool.<p>
3809     *
3810     * @param dbPoolUrl the url of a pool
3811     * @return the number of active connections
3812     * @throws CmsDbException if something goes wrong
3813     */
3814    public int getActiveConnections(String dbPoolUrl) throws CmsDbException {
3815
3816        CmsDbPoolV11 pool = m_pools.get(dbPoolUrl);
3817        if (pool == null) {
3818            CmsMessageContainer message = Messages.get().container(Messages.ERR_UNKNOWN_POOL_URL_1, dbPoolUrl);
3819            throw new CmsDbException(message);
3820        }
3821        try {
3822            return pool.getActiveConnections();
3823        } catch (Exception exc) {
3824            CmsMessageContainer message = Messages.get().container(Messages.ERR_ACCESSING_POOL_1, dbPoolUrl);
3825            throw new CmsDbException(message, exc);
3826        }
3827
3828    }
3829
3830    /**
3831     * Reads all access control entries.<p>
3832     *
3833     * @param dbc the current database context
3834     * @return all access control entries for the current project (offline/online)
3835     *
3836     * @throws CmsException if something goes wrong
3837     */
3838    public List<CmsAccessControlEntry> getAllAccessControlEntries(CmsDbContext dbc) throws CmsException {
3839
3840        I_CmsUserDriver userDriver = getUserDriver(dbc);
3841        List<CmsAccessControlEntry> ace = userDriver.readAccessControlEntries(
3842            dbc,
3843            dbc.currentProject(),
3844            CmsAccessControlEntry.PRINCIPAL_READALL_ID,
3845            false);
3846        return ace;
3847    }
3848
3849    /**
3850     * Returns all projects which are owned by the current user or which are
3851     * accessible by the current user.<p>
3852     *
3853     * @param dbc the current database context
3854     * @param orgUnit the organizational unit to search project in
3855     * @param includeSubOus if to include sub organizational units
3856     *
3857     * @return a list of objects of type <code>{@link CmsProject}</code>
3858     *
3859     * @throws CmsException if something goes wrong
3860     */
3861    public List<CmsProject> getAllAccessibleProjects(
3862        CmsDbContext dbc,
3863        CmsOrganizationalUnit orgUnit,
3864        boolean includeSubOus)
3865    throws CmsException {
3866
3867        Set<CmsProject> projects = new HashSet<CmsProject>();
3868
3869        // get the ous where the user has the project manager role
3870        List<CmsOrganizationalUnit> ous = getOrgUnitsForRole(
3871            dbc,
3872            CmsRole.PROJECT_MANAGER.forOrgUnit(orgUnit.getName()),
3873            includeSubOus);
3874
3875        // get the groups of the user if needed
3876        Set<CmsUUID> userGroupIds = new HashSet<CmsUUID>();
3877        Iterator<CmsGroup> itGroups = getGroupsOfUser(dbc, dbc.currentUser().getName(), false).iterator();
3878        while (itGroups.hasNext()) {
3879            CmsGroup group = itGroups.next();
3880            userGroupIds.add(group.getId());
3881        }
3882
3883        // TODO: this could be optimize if this method would have an additional parameter 'includeSubOus'
3884        // get all projects that might come in question
3885        projects.addAll(getProjectDriver(dbc).readProjects(dbc, orgUnit.getName()));
3886
3887        // filter hidden and not accessible projects
3888        Iterator<CmsProject> itProjects = projects.iterator();
3889        while (itProjects.hasNext()) {
3890            CmsProject project = itProjects.next();
3891            boolean accessible = true;
3892            // if hidden
3893            accessible = accessible && !project.isHidden();
3894
3895            if (!includeSubOus) {
3896                // if not exact in the given ou
3897                accessible = accessible && project.getOuFqn().equals(orgUnit.getName());
3898            } else {
3899                // if not in the given ou
3900                accessible = accessible && project.getOuFqn().startsWith(orgUnit.getName());
3901            }
3902
3903            if (!accessible) {
3904                itProjects.remove();
3905                continue;
3906            }
3907
3908            accessible = false;
3909            // online project
3910            accessible = accessible || project.isOnlineProject();
3911            // if owner
3912            accessible = accessible || project.getOwnerId().equals(dbc.currentUser().getId());
3913
3914            // project managers
3915            Iterator<CmsOrganizationalUnit> itOus = ous.iterator();
3916            while (!accessible && itOus.hasNext()) {
3917                CmsOrganizationalUnit ou = itOus.next();
3918                // for project managers check visibility
3919                accessible = accessible || project.getOuFqn().startsWith(ou.getName());
3920            }
3921
3922            if (!accessible) {
3923                // if direct user or manager of project
3924                CmsUUID groupId = null;
3925                if (userGroupIds.contains(project.getGroupId())) {
3926                    groupId = project.getGroupId();
3927                } else if (userGroupIds.contains(project.getManagerGroupId())) {
3928                    groupId = project.getManagerGroupId();
3929                }
3930                if (groupId != null) {
3931                    String oufqn = readGroup(dbc, groupId).getOuFqn();
3932                    accessible = accessible || (oufqn.startsWith(dbc.getRequestContext().getOuFqn()));
3933                }
3934            }
3935            if (!accessible) {
3936                // remove not accessible project
3937                itProjects.remove();
3938            }
3939        }
3940
3941        List<CmsProject> accessibleProjects = new ArrayList<CmsProject>(projects);
3942        // sort the list of projects based on the project name
3943        Collections.sort(accessibleProjects);
3944        // ensure the online project is in first place
3945        CmsProject onlineProject = readProject(dbc, CmsProject.ONLINE_PROJECT_ID);
3946        if (accessibleProjects.contains(onlineProject)) {
3947            accessibleProjects.remove(onlineProject);
3948        }
3949        accessibleProjects.add(0, onlineProject);
3950
3951        return accessibleProjects;
3952    }
3953
3954    /**
3955     * Returns a list with all projects from history.<p>
3956     *
3957     * @param dbc the current database context
3958     *
3959     * @return list of <code>{@link CmsHistoryProject}</code> objects
3960     *           with all projects from history.
3961     *
3962     * @throws CmsException if operation was not successful
3963     */
3964    public List<CmsHistoryProject> getAllHistoricalProjects(CmsDbContext dbc) throws CmsException {
3965
3966        // user is allowed to access all existing projects for the ous he has the project_manager role
3967        Set<CmsOrganizationalUnit> manOus = new HashSet<CmsOrganizationalUnit>(
3968            getOrgUnitsForRole(dbc, CmsRole.PROJECT_MANAGER, true));
3969
3970        List<CmsHistoryProject> projects = getHistoryDriver(dbc).readProjects(dbc);
3971        Iterator<CmsHistoryProject> itProjects = projects.iterator();
3972        while (itProjects.hasNext()) {
3973            CmsHistoryProject project = itProjects.next();
3974            if (project.isHidden()) {
3975                // project is hidden
3976                itProjects.remove();
3977                continue;
3978            }
3979            if (!project.getOuFqn().startsWith(dbc.currentUser().getOuFqn())) {
3980                // project is not visible from the users ou
3981                itProjects.remove();
3982                continue;
3983            }
3984            CmsOrganizationalUnit ou = readOrganizationalUnit(dbc, project.getOuFqn());
3985            if (manOus.contains(ou)) {
3986                // user is project manager for this project
3987                continue;
3988            } else if (project.getOwnerId().equals(dbc.currentUser().getId())) {
3989                // user is owner of the project
3990                continue;
3991            } else {
3992                boolean found = false;
3993                Iterator<CmsGroup> itGroups = getGroupsOfUser(dbc, dbc.currentUser().getName(), false).iterator();
3994                while (itGroups.hasNext()) {
3995                    CmsGroup group = itGroups.next();
3996                    if (project.getManagerGroupId().equals(group.getId())) {
3997                        found = true;
3998                        break;
3999                    }
4000                }
4001                if (found) {
4002                    // user is member of the manager group of the project
4003                    continue;
4004                }
4005            }
4006            itProjects.remove();
4007        }
4008        return projects;
4009    }
4010
4011    /**
4012     * Returns all projects which are owned by the current user or which are manageable
4013     * for the group of the user.<p>
4014     *
4015     * @param dbc the current database context
4016     * @param orgUnit the organizational unit to search project in
4017     * @param includeSubOus if to include sub organizational units
4018     *
4019     * @return a list of objects of type <code>{@link CmsProject}</code>
4020     *
4021     * @throws CmsException if operation was not successful
4022     */
4023    public List<CmsProject> getAllManageableProjects(
4024        CmsDbContext dbc,
4025        CmsOrganizationalUnit orgUnit,
4026        boolean includeSubOus)
4027    throws CmsException {
4028
4029        Set<CmsProject> projects = new HashSet<CmsProject>();
4030
4031        // get the ous where the user has the project manager role
4032        List<CmsOrganizationalUnit> ous = getOrgUnitsForRole(
4033            dbc,
4034            CmsRole.PROJECT_MANAGER.forOrgUnit(orgUnit.getName()),
4035            includeSubOus);
4036
4037        // get the groups of the user if needed
4038        Set<CmsUUID> userGroupIds = new HashSet<CmsUUID>();
4039        Iterator<CmsGroup> itGroups = getGroupsOfUser(dbc, dbc.currentUser().getName(), false).iterator();
4040        while (itGroups.hasNext()) {
4041            CmsGroup group = itGroups.next();
4042            userGroupIds.add(group.getId());
4043        }
4044
4045        // TODO: this could be optimize if this method would have an additional parameter 'includeSubOus'
4046        // get all projects that might come in question
4047        projects.addAll(getProjectDriver(dbc).readProjects(dbc, orgUnit.getName()));
4048
4049        // filter hidden and not manageable projects
4050        Iterator<CmsProject> itProjects = projects.iterator();
4051        while (itProjects.hasNext()) {
4052            CmsProject project = itProjects.next();
4053            boolean manageable = true;
4054            // if online
4055            manageable = manageable && !project.isOnlineProject();
4056            // if hidden
4057            manageable = manageable && !project.isHidden();
4058
4059            if (!includeSubOus) {
4060                // if not exact in the given ou
4061                manageable = manageable && project.getOuFqn().equals(orgUnit.getName());
4062            } else {
4063                // if not in the given ou
4064                manageable = manageable && project.getOuFqn().startsWith(orgUnit.getName());
4065            }
4066
4067            if (!manageable) {
4068                itProjects.remove();
4069                continue;
4070            }
4071
4072            manageable = false;
4073            // if owner
4074            manageable = manageable || project.getOwnerId().equals(dbc.currentUser().getId());
4075
4076            // project managers
4077            Iterator<CmsOrganizationalUnit> itOus = ous.iterator();
4078            while (!manageable && itOus.hasNext()) {
4079                CmsOrganizationalUnit ou = itOus.next();
4080                // for project managers check visibility
4081                manageable = manageable || project.getOuFqn().startsWith(ou.getName());
4082            }
4083
4084            if (!manageable) {
4085                // if manager of project
4086                if (userGroupIds.contains(project.getManagerGroupId())) {
4087                    String oufqn = readGroup(dbc, project.getManagerGroupId()).getOuFqn();
4088                    manageable = manageable || (oufqn.startsWith(dbc.getRequestContext().getOuFqn()));
4089                }
4090            }
4091            if (!manageable) {
4092                // remove not accessible project
4093                itProjects.remove();
4094            }
4095        }
4096
4097        List<CmsProject> manageableProjects = new ArrayList<CmsProject>(projects);
4098        // sort the list of projects based on the project name
4099        Collections.sort(manageableProjects);
4100        // ensure the online project is not in the list
4101        CmsProject onlineProject = readProject(dbc, CmsProject.ONLINE_PROJECT_ID);
4102        if (manageableProjects.contains(onlineProject)) {
4103            manageableProjects.remove(onlineProject);
4104        }
4105
4106        return manageableProjects;
4107    }
4108
4109    /**
4110     * Returns all child groups of a group.<p>
4111     *
4112     * @param dbc the current database context
4113     * @param group the group to get the child for
4114     * @param includeSubChildren if set also returns all sub-child groups of the given group
4115     *
4116     * @return a list of all child <code>{@link CmsGroup}</code> objects
4117     *
4118     * @throws CmsException if operation was not successful
4119     */
4120    public List<CmsGroup> getChildren(CmsDbContext dbc, CmsGroup group, boolean includeSubChildren)
4121    throws CmsException {
4122
4123        if (!includeSubChildren) {
4124            return getUserDriver(dbc).readChildGroups(dbc, group.getName());
4125        }
4126        Set<CmsGroup> allChildren = new TreeSet<CmsGroup>();
4127        // iterate all child groups
4128        Iterator<CmsGroup> it = getUserDriver(dbc).readChildGroups(dbc, group.getName()).iterator();
4129        while (it.hasNext()) {
4130            CmsGroup child = it.next();
4131            // add the group itself
4132            allChildren.add(child);
4133            // now get all sub-children for each group
4134            allChildren.addAll(getChildren(dbc, child, true));
4135        }
4136        return new ArrayList<CmsGroup>(allChildren);
4137    }
4138
4139    /**
4140     * Returns the date when the resource was last visited by the user.<p>
4141     *
4142     * @param dbc the database context
4143     * @param poolName the name of the database pool to use
4144     * @param user the user to check the date
4145     * @param resource the resource to check the date
4146     *
4147     * @return the date when the resource was last visited by the user
4148     *
4149     * @throws CmsException if something goes wrong
4150     */
4151    public long getDateLastVisitedBy(CmsDbContext dbc, String poolName, CmsUser user, CmsResource resource)
4152    throws CmsException {
4153
4154        return m_subscriptionDriver.getDateLastVisitedBy(dbc, poolName, user, resource);
4155    }
4156
4157    /**
4158     * Returns all groups of the given organizational unit.<p>
4159     *
4160     * @param dbc the current db context
4161     * @param orgUnit the organizational unit to get the groups for
4162     * @param includeSubOus if all groups of sub-organizational units should be retrieved too
4163     * @param readRoles if to read roles or groups
4164     *
4165     * @return all <code>{@link CmsGroup}</code> objects in the organizational unit
4166     *
4167     * @throws CmsException if operation was not successful
4168     *
4169     * @see org.opencms.security.CmsOrgUnitManager#getResourcesForOrganizationalUnit(CmsObject, String)
4170     * @see org.opencms.security.CmsOrgUnitManager#getGroups(CmsObject, String, boolean)
4171     */
4172    public List<CmsGroup> getGroups(
4173        CmsDbContext dbc,
4174        CmsOrganizationalUnit orgUnit,
4175        boolean includeSubOus,
4176        boolean readRoles)
4177    throws CmsException {
4178
4179        return getUserDriver(dbc).getGroups(dbc, orgUnit, includeSubOus, readRoles);
4180    }
4181
4182    /**
4183     * Returns the groups of an user filtered by the specified IP address.<p>
4184     *
4185     * @param dbc the current database context
4186     * @param username the name of the user
4187     * @param readRoles if to read roles or groups
4188     *
4189     * @return the groups of the given user, as a list of {@link CmsGroup} objects
4190     *
4191     * @throws CmsException if something goes wrong
4192     */
4193    public List<CmsGroup> getGroupsOfUser(CmsDbContext dbc, String username, boolean readRoles) throws CmsException {
4194
4195        return getGroupsOfUser(dbc, username, "", true, readRoles, false, dbc.getRequestContext().getRemoteAddress());
4196    }
4197
4198    /**
4199     * Returns the groups of an user filtered by the specified IP address.<p>
4200     *
4201     * @param dbc the current database context
4202     * @param username the name of the user
4203     * @param ouFqn the fully qualified name of the organizational unit to restrict the result set for
4204     * @param includeChildOus include groups of child organizational units
4205     * @param readRoles if to read roles or groups
4206     * @param directGroupsOnly if set only the direct assigned groups will be returned, if not also indirect groups
4207     * @param remoteAddress the IP address to filter the groups in the result list
4208     *
4209     * @return a list of <code>{@link CmsGroup}</code> objects
4210     *
4211     * @throws CmsException if operation was not successful
4212     */
4213    public List<CmsGroup> getGroupsOfUser(
4214        CmsDbContext dbc,
4215        String username,
4216        String ouFqn,
4217        boolean includeChildOus,
4218        boolean readRoles,
4219        boolean directGroupsOnly,
4220        String remoteAddress)
4221    throws CmsException {
4222
4223        CmsUser user = readUser(dbc, username);
4224        String prefix = ouFqn + "_" + includeChildOus + "_" + directGroupsOnly + "_" + readRoles + "_" + remoteAddress;
4225        String cacheKey = m_keyGenerator.getCacheKeyForUserGroups(prefix, dbc, user);
4226        List<CmsGroup> groups = m_monitor.getCachedUserGroups(user.getId(), cacheKey);
4227        if (groups == null) {
4228            // get all groups of the user
4229            List<CmsGroup> directGroups = getUserDriver(dbc).readGroupsOfUser(
4230                dbc,
4231                user.getId(),
4232                readRoles ? "" : ouFqn,
4233                readRoles ? true : includeChildOus,
4234                remoteAddress,
4235                readRoles);
4236            Set<CmsGroup> allGroups = new HashSet<CmsGroup>();
4237            if (!readRoles) {
4238                allGroups.addAll(directGroups);
4239            }
4240            if (!directGroupsOnly) {
4241                if (!readRoles) {
4242                    // now get all parents of the groups
4243                    for (int i = 0; i < directGroups.size(); i++) {
4244                        CmsGroup parent = getParent(dbc, directGroups.get(i).getName());
4245                        while ((parent != null) && (!allGroups.contains(parent))) {
4246                            if (parent.getOuFqn().startsWith(ouFqn)) {
4247                                allGroups.add(parent);
4248                            }
4249                            // read next parent group
4250                            parent = getParent(dbc, parent.getName());
4251                        }
4252                    }
4253                }
4254            }
4255            if (readRoles) {
4256                // for each for role
4257                for (int i = 0; i < directGroups.size(); i++) {
4258                    CmsGroup group = directGroups.get(i);
4259                    CmsRole role = CmsRole.valueOf(group);
4260                    if (!includeChildOus && role.getOuFqn().equals(ouFqn)) {
4261                        allGroups.add(group);
4262                    }
4263                    if (includeChildOus && role.getOuFqn().startsWith(ouFqn)) {
4264                        allGroups.add(group);
4265                    }
4266                    if (directGroupsOnly || (!includeChildOus && !role.getOuFqn().equals(ouFqn))) {
4267                        // if roles of child OUs are not requested and the role does not belong to the requested OU don't include the role children
4268                        continue;
4269                    }
4270                    CmsOrganizationalUnit currentOu = readOrganizationalUnit(dbc, group.getOuFqn());
4271                    boolean readChildRoleGroups = true;
4272                    if (currentOu.hasFlagWebuser() && role.forOrgUnit(null).equals(CmsRole.ACCOUNT_MANAGER)) {
4273                        readChildRoleGroups = false;
4274                    }
4275                    if (readChildRoleGroups) {
4276                        // get the child roles
4277                        Iterator<CmsRole> itChildRoles = role.getChildren(true).iterator();
4278                        while (itChildRoles.hasNext()) {
4279                            CmsRole childRole = itChildRoles.next();
4280                            if (childRole.isSystemRole()) {
4281                                if (canReadRoleInOu(currentOu, childRole)) {
4282                                    // include system roles only
4283                                    try {
4284                                        allGroups.add(readGroup(dbc, childRole.getGroupName()));
4285                                    } catch (CmsDataAccessException e) {
4286                                        // should not happen, log error if it does
4287                                        LOG.error(e.getLocalizedMessage(), e);
4288                                    }
4289                                }
4290                            }
4291                        }
4292                    } else {
4293                        LOG.info("Skipping child role group check for web user OU " + currentOu.getName());
4294                    }
4295                    if (includeChildOus) {
4296                        // if needed include the roles of child ous
4297                        Iterator<CmsOrganizationalUnit> itSubOus = getOrganizationalUnits(
4298                            dbc,
4299                            readOrganizationalUnit(dbc, group.getOuFqn()),
4300                            true).iterator();
4301                        while (itSubOus.hasNext()) {
4302                            CmsOrganizationalUnit subOu = itSubOus.next();
4303                            // add role in child ou
4304                            try {
4305                                if (canReadRoleInOu(subOu, role)) {
4306                                    allGroups.add(readGroup(dbc, role.forOrgUnit(subOu.getName()).getGroupName()));
4307                                }
4308                            } catch (CmsDbEntryNotFoundException e) {
4309                                // ignore, this may happen while deleting an orgunit
4310                                if (LOG.isDebugEnabled()) {
4311                                    LOG.debug(e.getLocalizedMessage(), e);
4312                                }
4313                            }
4314                            // add child roles in child ous
4315                            Iterator<CmsRole> itChildRoles = role.getChildren(true).iterator();
4316                            while (itChildRoles.hasNext()) {
4317                                CmsRole childRole = itChildRoles.next();
4318                                try {
4319                                    if (canReadRoleInOu(subOu, childRole)) {
4320                                        allGroups.add(
4321                                            readGroup(dbc, childRole.forOrgUnit(subOu.getName()).getGroupName()));
4322                                    }
4323                                } catch (CmsDbEntryNotFoundException e) {
4324                                    // ignore, this may happen while deleting an orgunit
4325                                    if (LOG.isDebugEnabled()) {
4326                                        LOG.debug(e.getLocalizedMessage(), e);
4327                                    }
4328                                }
4329                            }
4330                        }
4331                    }
4332                }
4333            }
4334            // make group list unmodifiable for caching
4335            groups = Collections.unmodifiableList(new ArrayList<CmsGroup>(allGroups));
4336            if (dbc.getProjectId().isNullUUID()) {
4337                m_monitor.getGroupListCache().setGroups(user, cacheKey, groups);
4338            }
4339        }
4340
4341        return groups;
4342    }
4343
4344    /**
4345     * Returns the history driver.<p>
4346     *
4347     * @return the history driver
4348     */
4349    public I_CmsHistoryDriver getHistoryDriver() {
4350
4351        return m_historyDriver;
4352    }
4353
4354    /**
4355     * Returns the history driver for a given database context.<p>
4356     *
4357     * @param dbc the database context
4358     * @return the history driver for the database context
4359     */
4360    public I_CmsHistoryDriver getHistoryDriver(CmsDbContext dbc) {
4361
4362        if ((dbc == null) || (dbc.getProjectId() == null) || dbc.getProjectId().isNullUUID()) {
4363            return m_historyDriver;
4364        }
4365        I_CmsHistoryDriver driver = dbc.getHistoryDriver(dbc.getProjectId());
4366        return driver != null ? driver : m_historyDriver;
4367
4368    }
4369
4370    /**
4371     * Returns the number of idle connections managed by a pool.<p>
4372     *
4373     * @param dbPoolUrl the url of a pool
4374     * @return the number of idle connections
4375     * @throws CmsDbException if something goes wrong
4376     */
4377    public int getIdleConnections(String dbPoolUrl) throws CmsDbException {
4378
4379        CmsDbPoolV11 pool = m_pools.get(dbPoolUrl);
4380        if (pool == null) {
4381            CmsMessageContainer message = Messages.get().container(Messages.ERR_UNKNOWN_POOL_URL_1, dbPoolUrl);
4382            throw new CmsDbException(message);
4383        }
4384        try {
4385            return pool.getIdleConnections();
4386        } catch (Exception exc) {
4387            CmsMessageContainer message = Messages.get().container(Messages.ERR_ACCESSING_POOL_1, dbPoolUrl);
4388            throw new CmsDbException(message, exc);
4389        }
4390
4391    }
4392
4393    /**
4394     * Returns the lock state of a resource.<p>
4395     *
4396     * @param dbc the current database context
4397     * @param resource the resource to return the lock state for
4398     *
4399     * @return the lock state of the resource
4400     *
4401     * @throws CmsException if something goes wrong
4402     */
4403    public CmsLock getLock(CmsDbContext dbc, CmsResource resource) throws CmsException {
4404
4405        return m_lockManager.getLock(dbc, resource);
4406    }
4407
4408    /**
4409     * Returns all locked resources in a given folder.<p>
4410     *
4411     * @param dbc the current database context
4412     * @param resource the folder to search in
4413     * @param filter the lock filter
4414     *
4415     * @return a list of locked resource paths (relative to current site)
4416     *
4417     * @throws CmsException if the current project is locked
4418     */
4419    public List<String> getLockedResources(CmsDbContext dbc, CmsResource resource, CmsLockFilter filter)
4420    throws CmsException {
4421
4422        List<String> lockedResources = new ArrayList<String>();
4423        // get locked resources
4424        Iterator<CmsLock> it = m_lockManager.getLocks(dbc, resource.getRootPath(), filter).iterator();
4425        while (it.hasNext()) {
4426            CmsLock lock = it.next();
4427            lockedResources.add(dbc.removeSiteRoot(lock.getResourceName()));
4428        }
4429        Collections.sort(lockedResources);
4430        return lockedResources;
4431    }
4432
4433    /**
4434     * Returns all locked resources in a given folder.<p>
4435     *
4436     * @param dbc the current database context
4437     * @param resource the folder to search in
4438     * @param filter the lock filter
4439     *
4440     * @return a list of locked resources
4441     *
4442     * @throws CmsException if the current project is locked
4443     */
4444    public List<CmsResource> getLockedResourcesObjects(CmsDbContext dbc, CmsResource resource, CmsLockFilter filter)
4445    throws CmsException {
4446
4447        return m_lockManager.getLockedResources(dbc, resource, filter);
4448    }
4449
4450    /**
4451     * Returns all locked resources in a given folder, but uses a cache for resource lookups.<p>
4452     *
4453     * @param dbc the current database context
4454     * @param resource the folder to search in
4455     * @param filter the lock filter
4456     * @param cache the cache to use for resource lookups
4457     *
4458     * @return a list of locked resources
4459     *
4460     * @throws CmsException if the current project is locked
4461     */
4462    public List<CmsResource> getLockedResourcesObjectsWithCache(
4463        CmsDbContext dbc,
4464        CmsResource resource,
4465        CmsLockFilter filter,
4466        Map<String, CmsResource> cache)
4467    throws CmsException {
4468
4469        return m_lockManager.getLockedResourcesWithCache(dbc, resource, filter, cache);
4470    }
4471
4472    /**
4473     * Returns all log entries matching the given filter.<p>
4474     *
4475     * @param dbc the current db context
4476     * @param filter the filter to match the log entries
4477     *
4478     * @return all log entries matching the given filter
4479     *
4480     * @throws CmsException if something goes wrong
4481     *
4482     * @see CmsSecurityManager#getLogEntries(CmsRequestContext, CmsLogFilter)
4483     */
4484    public List<CmsLogEntry> getLogEntries(CmsDbContext dbc, CmsLogFilter filter) throws CmsException {
4485
4486        updateLog(dbc);
4487        return m_projectDriver.readLog(dbc, filter);
4488    }
4489
4490    /**
4491     * Returns the next publish tag for the published historical resources.<p>
4492     *
4493     * @param dbc the current database context
4494     *
4495     * @return the next available publish tag
4496     */
4497    public int getNextPublishTag(CmsDbContext dbc) {
4498
4499        return getHistoryDriver(dbc).readNextPublishTag(dbc);
4500    }
4501
4502    /**
4503     * Returns all child organizational units of the given parent organizational unit including
4504     * hierarchical deeper organization units if needed.<p>
4505     *
4506     * @param dbc the current db context
4507     * @param parent the parent organizational unit, or <code>null</code> for the root
4508     * @param includeChildren if hierarchical deeper organization units should also be returned
4509     *
4510     * @return a list of <code>{@link CmsOrganizationalUnit}</code> objects
4511     *
4512     * @throws CmsException if operation was not successful
4513     *
4514     * @see org.opencms.security.CmsOrgUnitManager#getOrganizationalUnits(CmsObject, String, boolean)
4515     */
4516    public List<CmsOrganizationalUnit> getOrganizationalUnits(
4517        CmsDbContext dbc,
4518        CmsOrganizationalUnit parent,
4519        boolean includeChildren)
4520    throws CmsException {
4521
4522        if (parent == null) {
4523            throw new CmsIllegalArgumentException(Messages.get().container(Messages.ERR_PARENT_ORGUNIT_NULL_0));
4524        }
4525        return getUserDriver(dbc).getOrganizationalUnits(dbc, parent, includeChildren);
4526    }
4527
4528    /**
4529     * Returns all the organizational units for which the current user has the given role.<p>
4530     *
4531     * @param dbc the current database context
4532     * @param role the role to check
4533     * @param includeSubOus if sub organizational units should be included in the search
4534     *
4535     * @return a list of {@link org.opencms.security.CmsOrganizationalUnit} objects
4536     *
4537     * @throws CmsException if something goes wrong
4538     */
4539    public List<CmsOrganizationalUnit> getOrgUnitsForRole(CmsDbContext dbc, CmsRole role, boolean includeSubOus)
4540    throws CmsException {
4541
4542        String ouFqn = role.getOuFqn();
4543        if (ouFqn == null) {
4544            ouFqn = "";
4545            role = role.forOrgUnit("");
4546        }
4547        CmsOrganizationalUnit ou = readOrganizationalUnit(dbc, ouFqn);
4548        List<CmsOrganizationalUnit> orgUnits = new ArrayList<CmsOrganizationalUnit>();
4549        if (m_securityManager.hasRole(dbc, dbc.currentUser(), role)) {
4550            orgUnits.add(ou);
4551        }
4552        if (includeSubOus) {
4553            Iterator<CmsOrganizationalUnit> it = getOrganizationalUnits(dbc, ou, true).iterator();
4554            while (it.hasNext()) {
4555                CmsOrganizationalUnit orgUnit = it.next();
4556                if (m_securityManager.hasRole(dbc, dbc.currentUser(), role.forOrgUnit(orgUnit.getName()))) {
4557                    orgUnits.add(orgUnit);
4558                }
4559            }
4560        }
4561        return orgUnits;
4562    }
4563
4564    /**
4565     * Returns the parent group of a group.<p>
4566     *
4567     * @param dbc the current database context
4568     * @param groupname the name of the group
4569     *
4570     * @return group the parent group or <code>null</code>
4571     *
4572     * @throws CmsException if operation was not successful
4573     */
4574    public CmsGroup getParent(CmsDbContext dbc, String groupname) throws CmsException {
4575
4576        CmsGroup group = readGroup(dbc, groupname);
4577        if (group.getParentId().isNullUUID()) {
4578            return null;
4579        }
4580
4581        // try to read from cache
4582        CmsGroup parent = m_monitor.getCachedGroup(group.getParentId().toString());
4583        if (parent == null) {
4584            parent = getUserDriver(dbc).readGroup(dbc, group.getParentId());
4585            m_monitor.cacheGroup(parent);
4586        }
4587        return parent;
4588    }
4589
4590    /**
4591     * Returns the set of permissions of the current user for a given resource.<p>
4592     *
4593     * @param dbc the current database context
4594     * @param resource the resource
4595     * @param user the user
4596     *
4597     * @return bit set with allowed permissions
4598     *
4599     * @throws CmsException if something goes wrong
4600     */
4601    public CmsPermissionSetCustom getPermissions(CmsDbContext dbc, CmsResource resource, CmsUser user)
4602    throws CmsException {
4603
4604        CmsAccessControlList acList = getAccessControlList(dbc, resource, false);
4605        List<CmsGroup> groups = getGroupsOfUser(dbc, user.getName(), false);
4606        List<CmsRole> roles = getRolesForUser(dbc, user);
4607        CmsPermissionSetCustom permissions = acList.getPermissions(user, groups, roles);
4608
4609        if (acList.getExclusiveAccessPrincipals().size() > 0) {
4610            long now;
4611            @SuppressWarnings("unchecked")
4612            Supplier<Long> alternativeClock = (Supplier<Long>)(dbc.getRequestContext().getAttribute(
4613                ATTR_EXCLUSIVE_ACCESS_CLOCK));
4614            if (alternativeClock != null) {
4615                // used for testing
4616                now = alternativeClock.get().longValue();
4617            } else {
4618                // *NOT* using dbc.getRequestContext().getRequestTime(), even though that value is used for normal resource availability checks, because
4619                // that value may be manipulated by some workplace classes, and we want the real time for permission checks
4620                now = System.currentTimeMillis();
4621            }
4622            permissions.setCacheable(false); // resources going in/out of availability can change permissions - don't cache
4623            if (!resource.isReleasedAndNotExpired(now)) {
4624                boolean hasExclusiveAccess = false;
4625                for (CmsGroup group : groups) {
4626                    if (acList.getExclusiveAccessPrincipals().contains(group.getId())) {
4627                        hasExclusiveAccess = true;
4628                        break;
4629                    }
4630                }
4631                hasExclusiveAccess |= acList.getExclusiveAccessPrincipals().contains(user.getId());
4632                if (!hasExclusiveAccess) {
4633                    permissions.denyPermissions(CmsPermissionSet.PERMISSION_FULL);
4634                }
4635            }
4636        }
4637        return permissions;
4638    }
4639
4640    /**
4641     * Returns the project driver.<p>
4642     *
4643     * @return the project driver
4644     */
4645    public I_CmsProjectDriver getProjectDriver() {
4646
4647        return m_projectDriver;
4648    }
4649
4650    /**
4651     * Returns the project driver for a given DB context.<p>
4652     *
4653     * @param dbc the database context
4654     *
4655     * @return the project driver for the database context
4656     */
4657    public I_CmsProjectDriver getProjectDriver(CmsDbContext dbc) {
4658
4659        if ((dbc == null) || (dbc.getProjectId() == null) || dbc.getProjectId().isNullUUID()) {
4660            return m_projectDriver;
4661        }
4662        I_CmsProjectDriver driver = dbc.getProjectDriver(dbc.getProjectId());
4663        return driver != null ? driver : m_projectDriver;
4664    }
4665
4666    /**
4667     * Returns either the project driver for the DB context (if it has one) or a default project driver.<p>
4668     *
4669     * @param dbc the DB context
4670     * @param defaultDriver the driver which should be returned if there is no project driver for the DB context
4671     *
4672     * @return either the project driver for the DB context, or the default driver
4673     */
4674    public I_CmsProjectDriver getProjectDriver(CmsDbContext dbc, I_CmsProjectDriver defaultDriver) {
4675
4676        if ((dbc == null) || (dbc.getProjectId() == null) || dbc.getProjectId().isNullUUID()) {
4677            return defaultDriver;
4678        }
4679        I_CmsProjectDriver driver = dbc.getProjectDriver(dbc.getProjectId());
4680        return driver != null ? driver : defaultDriver;
4681    }
4682
4683    /**
4684     * Returns the uuid id for the given id.<p>
4685     *
4686     * TODO: remove this method as soon as possible
4687     *
4688     * @param dbc the current database context
4689     * @param id the old project id
4690     *
4691     * @return the new uuid for the given id
4692     *
4693     * @throws CmsException if something goes wrong
4694     */
4695    public CmsUUID getProjectId(CmsDbContext dbc, int id) throws CmsException {
4696
4697        Iterator<CmsProject> itProjects = getAllAccessibleProjects(
4698            dbc,
4699            readOrganizationalUnit(dbc, ""),
4700            true).iterator();
4701        while (itProjects.hasNext()) {
4702            CmsProject project = itProjects.next();
4703            if (project.getUuid().hashCode() == id) {
4704                return project.getUuid();
4705            }
4706        }
4707        return null;
4708    }
4709
4710    /**
4711     * Returns the configuration read from the <code>opencms.properties</code> file.<p>
4712     *
4713     * @return the configuration read from the <code>opencms.properties</code> file
4714     */
4715    public CmsParameterConfiguration getPropertyConfiguration() {
4716
4717        return m_propertyConfiguration;
4718    }
4719
4720    /**
4721     * Returns a new publish list that contains the unpublished resources related
4722     * to all resources in the given publish list, the related resources exclude
4723     * all resources in the given publish list and also locked (by other users) resources.<p>
4724     *
4725     * @param dbc the current database context
4726     * @param publishList the publish list to exclude from result
4727     * @param filter the relation filter to use to get the related resources
4728     *
4729     * @return a new publish list that contains the related resources
4730     *
4731     * @throws CmsException if something goes wrong
4732     *
4733     * @see org.opencms.publish.CmsPublishManager#getRelatedResourcesToPublish(CmsObject, CmsPublishList)
4734     */
4735    public CmsPublishList getRelatedResourcesToPublish(
4736        CmsDbContext dbc,
4737        CmsPublishList publishList,
4738        CmsRelationFilter filter)
4739    throws CmsException {
4740
4741        Map<String, CmsResource> relations = new HashMap<String, CmsResource>();
4742
4743        // check if progress should be set in the thread
4744        A_CmsProgressThread thread = null;
4745        if (Thread.currentThread() instanceof A_CmsProgressThread) {
4746            thread = (A_CmsProgressThread)Thread.currentThread();
4747        }
4748
4749        // get all resources to publish
4750        List<CmsResource> publishResources = publishList.getAllResources();
4751        Iterator<CmsResource> itCheckList = publishResources.iterator();
4752        // iterate over them
4753        int count = 0;
4754        while (itCheckList.hasNext()) {
4755
4756            // set progress in thread
4757            count++;
4758            if (thread != null) {
4759
4760                if (thread.isInterrupted()) {
4761                    throw new CmsIllegalStateException(
4762                        org.opencms.workplace.commons.Messages.get().container(
4763                            org.opencms.workplace.commons.Messages.ERR_PROGRESS_INTERRUPTED_0));
4764                }
4765                thread.setProgress((count * 20) / publishResources.size());
4766                thread.setDescription(
4767                    org.opencms.workplace.commons.Messages.get().getBundle().key(
4768                        org.opencms.workplace.commons.Messages.GUI_PROGRESS_PUBLISH_STEP1_2,
4769                        Integer.valueOf(count),
4770                        Integer.valueOf(publishResources.size())));
4771            }
4772
4773            CmsResource checkResource = itCheckList.next();
4774            // get and iterate over all related resources
4775            Iterator<CmsRelation> itRelations = getRelationsForResource(dbc, checkResource, filter).iterator();
4776            while (itRelations.hasNext()) {
4777                CmsRelation relation = itRelations.next();
4778                try {
4779                    // get the target of the relation, see CmsRelation#getTarget(CmsObject, CmsResourceFilter)
4780                    CmsResource target;
4781                    try {
4782                        // first look up by id
4783                        target = readResource(dbc, relation.getTargetId(), CmsResourceFilter.ALL);
4784                    } catch (CmsVfsResourceNotFoundException e) {
4785                        // then look up by name, but from the root site
4786                        String storedSiteRoot = dbc.getRequestContext().getSiteRoot();
4787                        try {
4788                            dbc.getRequestContext().setSiteRoot("");
4789                            target = readResource(dbc, relation.getTargetPath(), CmsResourceFilter.ALL);
4790                        } finally {
4791                            dbc.getRequestContext().setSiteRoot(storedSiteRoot);
4792                        }
4793                    }
4794                    CmsLock lock = getLock(dbc, target);
4795                    // just add resources that may come in question
4796                    if (!publishResources.contains(target) // is not in the original list
4797                        && !relations.containsKey(target.getRootPath()) // has not been already added by another relation
4798                        && !target.getState().isUnchanged() // has been changed
4799                        && lock.isLockableBy(dbc.currentUser())) { // is lockable by current user
4800
4801                        relations.put(target.getRootPath(), target);
4802                        // now check the folder structure
4803                        CmsResource parent = getVfsDriver(dbc).readParentFolder(
4804                            dbc,
4805                            dbc.currentProject().getUuid(),
4806                            target.getStructureId());
4807                        while ((parent != null) && parent.getState().isNew()) {
4808                            // just add resources that may come in question
4809                            if (!publishResources.contains(parent) // is not in the original list
4810                                && !relations.containsKey(parent.getRootPath())) { // has not been already added by another relation
4811
4812                                relations.put(parent.getRootPath(), parent);
4813                            }
4814                            parent = getVfsDriver(dbc).readParentFolder(
4815                                dbc,
4816                                dbc.currentProject().getUuid(),
4817                                parent.getStructureId());
4818                        }
4819                    }
4820                } catch (CmsVfsResourceNotFoundException e) {
4821                    // ignore broken links
4822                    if (LOG.isDebugEnabled()) {
4823                        LOG.debug(e.getLocalizedMessage(), e);
4824                    }
4825                }
4826            }
4827        }
4828
4829        CmsPublishList ret = new CmsPublishList(publishList.getDirectPublishResources(), false, false);
4830        ret.addAll(relations.values(), false);
4831        ret.initialize();
4832        return ret;
4833    }
4834
4835    /**
4836     * Returns all relations for the given resource matching the given filter.<p>
4837     *
4838     * @param dbc the current db context
4839     * @param resource the resource to retrieve the relations for
4840     * @param filter the filter to match the relation
4841     *
4842     * @return all relations for the given resource matching the given filter
4843     *
4844     * @throws CmsException if something goes wrong
4845     *
4846     * @see CmsSecurityManager#getRelationsForResource(CmsRequestContext, CmsResource, CmsRelationFilter)
4847     */
4848    public List<CmsRelation> getRelationsForResource(CmsDbContext dbc, CmsResource resource, CmsRelationFilter filter)
4849    throws CmsException {
4850
4851        CmsUUID projectId = getProjectIdForContext(dbc);
4852        return getVfsDriver(dbc).readRelations(dbc, projectId, resource, filter);
4853    }
4854
4855    /**
4856     * Returns the list of organizational units the given resource belongs to.<p>
4857     *
4858     * @param dbc the current database context
4859     * @param resource the resource
4860     *
4861     * @return list of {@link CmsOrganizationalUnit} objects
4862     *
4863     * @throws CmsException if something goes wrong
4864     */
4865    public List<CmsOrganizationalUnit> getResourceOrgUnits(CmsDbContext dbc, CmsResource resource) throws CmsException {
4866
4867        boolean nullDbcProjectId = (dbc.getProjectId() == null) || dbc.getProjectId().isNullUUID();
4868        if (nullDbcProjectId && resourceOrgUnitCachingEnabled) {
4869            try {
4870                return m_monitor.getResourceOuCache().get(new ResourceOUCacheKey(this, dbc)).getResourceOrgUnits(
4871                    resource.getRootPath());
4872            } catch (ExecutionException e) {
4873                LOG.error(e.getLocalizedMessage(), e);
4874            }
4875        }
4876        List<CmsOrganizationalUnit> result = getVfsDriver(dbc).getResourceOus(
4877            dbc,
4878            dbc.currentProject().getUuid(),
4879            resource);
4880
4881        return result;
4882    }
4883
4884    /**
4885     * Returns all resources of the given organizational unit.<p>
4886     *
4887     * @param dbc the current db context
4888     * @param orgUnit the organizational unit to get all resources for
4889     *
4890     * @return all <code>{@link CmsResource}</code> objects in the organizational unit
4891     *
4892     * @throws CmsException if operation was not successful
4893     *
4894     * @see org.opencms.security.CmsOrgUnitManager#getResourcesForOrganizationalUnit(CmsObject, String)
4895     * @see org.opencms.security.CmsOrgUnitManager#getUsers(CmsObject, String, boolean)
4896     * @see org.opencms.security.CmsOrgUnitManager#getGroups(CmsObject, String, boolean)
4897     */
4898    public List<CmsResource> getResourcesForOrganizationalUnit(CmsDbContext dbc, CmsOrganizationalUnit orgUnit)
4899    throws CmsException {
4900
4901        return getUserDriver(dbc).getResourcesForOrganizationalUnit(dbc, orgUnit);
4902    }
4903
4904    /**
4905     * Returns all resources associated to a given principal via an ACE with the given permissions.<p>
4906     *
4907     * If the <code>includeAttr</code> flag is set it returns also all resources associated to
4908     * a given principal through some of following attributes.<p>
4909     *
4910     * <ul>
4911     *    <li>User Created</li>
4912     *    <li>User Last Modified</li>
4913     * </ul><p>
4914     *
4915     * @param dbc the current database context
4916     * @param project the to read the entries from
4917     * @param principalId the id of the principal
4918     * @param permissions a set of permissions to match, can be <code>null</code> for all ACEs
4919     * @param includeAttr a flag to include resources associated by attributes
4920     *
4921     * @return a set of <code>{@link CmsResource}</code> objects
4922     *
4923     * @throws CmsException if something goes wrong
4924     */
4925    public Set<CmsResource> getResourcesForPrincipal(
4926        CmsDbContext dbc,
4927        CmsProject project,
4928        CmsUUID principalId,
4929        CmsPermissionSet permissions,
4930        boolean includeAttr)
4931    throws CmsException {
4932
4933        Set<CmsResource> resources = new HashSet<CmsResource>(
4934            getVfsDriver(dbc).readResourcesForPrincipalACE(dbc, project, principalId));
4935        if (permissions != null) {
4936            Iterator<CmsResource> itRes = resources.iterator();
4937            while (itRes.hasNext()) {
4938                CmsAccessControlEntry ace = readAccessControlEntry(dbc, itRes.next(), principalId);
4939                if ((ace.getPermissions().getPermissions()
4940                    & permissions.getPermissions()) != permissions.getPermissions()) {
4941                    // remove if permissions does not match
4942                    itRes.remove();
4943                }
4944            }
4945        }
4946        if (includeAttr) {
4947            resources.addAll(getVfsDriver(dbc).readResourcesForPrincipalAttr(dbc, project, principalId));
4948        }
4949        return resources;
4950    }
4951
4952    /**
4953     * Gets the rewrite aliases matching a given filter.<p>
4954     *
4955     * @param dbc the current database context
4956     * @param filter the filter used for filtering rewrite aliases
4957     *
4958     * @return the rewrite aliases matching the given filter
4959     *
4960     * @throws CmsException if something goes wrong
4961     */
4962    public List<CmsRewriteAlias> getRewriteAliases(CmsDbContext dbc, CmsRewriteAliasFilter filter) throws CmsException {
4963
4964        return getVfsDriver(dbc).readRewriteAliases(dbc, filter);
4965    }
4966
4967    /**
4968     * Collects the groups which constitute a given role.<p>
4969     *
4970     * @param dbc the database context
4971     * @param roleGroupName the group related to the role
4972     * @param directUsersOnly if true, only the group belonging to the entry itself wil
4973     *
4974     * @return the set of groups which constitute the role
4975     *
4976     * @throws CmsException if something goes wrong
4977     */
4978    public Set<CmsGroup> getRoleGroups(CmsDbContext dbc, String roleGroupName, boolean directUsersOnly)
4979    throws CmsException {
4980
4981        return getRoleGroupsImpl(dbc, roleGroupName, directUsersOnly, new HashMap<String, Set<CmsGroup>>());
4982    }
4983
4984    /**
4985     * Collects the groups which constitute a given role.<p>
4986     *
4987     * @param dbc the database context
4988     * @param roleGroupName the group related to the role
4989     * @param directUsersOnly if true, only the group belonging to the entry itself wil
4990     * @param accumulator a map for memoizing return values of recursive calls
4991     *
4992     * @return the set of groups which constitute the role
4993     *
4994     * @throws CmsException if something goes wrong
4995     */
4996    public Set<CmsGroup> getRoleGroupsImpl(
4997        CmsDbContext dbc,
4998        String roleGroupName,
4999        boolean directUsersOnly,
5000        Map<String, Set<CmsGroup>> accumulator)
5001    throws CmsException {
5002
5003        Set<CmsGroup> result = new HashSet<CmsGroup>();
5004        if (accumulator.get(roleGroupName) != null) {
5005            return accumulator.get(roleGroupName);
5006        }
5007        CmsGroup group = readGroup(dbc, roleGroupName); // check that the group really exists
5008        if ((group == null) || (!group.isRole())) {
5009            throw new CmsDbEntryNotFoundException(
5010                Messages.get().container(Messages.ERR_UNKNOWN_GROUP_1, roleGroupName));
5011        }
5012        result.add(group);
5013        if (!directUsersOnly) {
5014            CmsRole role = CmsRole.valueOf(group);
5015            if (role.getParentRole() != null) {
5016                try {
5017                    String parentGroup = role.getParentRole().getGroupName();
5018                    // iterate the parent roles
5019                    result.addAll(getRoleGroupsImpl(dbc, parentGroup, directUsersOnly, accumulator));
5020                } catch (CmsDbEntryNotFoundException e) {
5021                    // ignore, this may happen while deleting an orgunit
5022                    if (LOG.isDebugEnabled()) {
5023                        LOG.debug(e.getLocalizedMessage(), e);
5024                    }
5025                }
5026            }
5027            String parentOu = CmsOrganizationalUnit.getParentFqn(group.getOuFqn());
5028            if (parentOu != null) {
5029                // iterate the parent ou's
5030                result.addAll(getRoleGroupsImpl(dbc, parentOu + group.getSimpleName(), directUsersOnly, accumulator));
5031            }
5032        }
5033        accumulator.put(roleGroupName, result);
5034        return result;
5035    }
5036
5037    /**
5038     * Returns all roles the given user has for the given resource.<p>
5039     *
5040     * @param dbc the current database context
5041     * @param user the user to check
5042     * @param resource the resource to check the roles for
5043     *
5044     * @return a list of {@link CmsRole} objects
5045     *
5046     * @throws CmsException if something goes wrong
5047     */
5048    public List<CmsRole> getRolesForResource(CmsDbContext dbc, CmsUser user, CmsResource resource) throws CmsException {
5049
5050        // guest user has no role
5051        if (user.isGuestUser()) {
5052            return Collections.emptyList();
5053        }
5054
5055        // try to read from cache
5056        String key = user.getId().toString() + resource.getRootPath();
5057        List<CmsRole> result = m_monitor.getCachedRoleList(key);
5058        if (result != null) {
5059            return result;
5060        }
5061        result = new ArrayList<CmsRole>();
5062
5063        Iterator<CmsOrganizationalUnit> itOus = getResourceOrgUnits(dbc, resource).iterator();
5064        while (itOus.hasNext()) {
5065            CmsOrganizationalUnit ou = itOus.next();
5066
5067            // read all roles of the current user
5068            List<CmsGroup> groups = new ArrayList<CmsGroup>(
5069                getGroupsOfUser(
5070                    dbc,
5071                    user.getName(),
5072                    ou.getName(),
5073                    false,
5074                    true,
5075                    false,
5076                    dbc.getRequestContext().getRemoteAddress()));
5077            // check the roles applying to the given resource
5078            Iterator<CmsGroup> it = groups.iterator();
5079            while (it.hasNext()) {
5080                CmsGroup group = it.next();
5081                CmsRole givenRole = CmsRole.valueOf(group).forOrgUnit(null);
5082                if (givenRole.isOrganizationalUnitIndependent() || result.contains(givenRole)) {
5083                    // skip already added roles
5084                    continue;
5085                }
5086                result.add(givenRole);
5087            }
5088        }
5089
5090        result = Collections.unmodifiableList(result);
5091        m_monitor.cacheRoleList(key, result);
5092        return result;
5093    }
5094
5095    /**
5096     * Returns all roles the given user has independent of the resource.<p>
5097     *
5098     * @param dbc the current database context
5099     * @param user the user to check
5100     *
5101     * @return a list of {@link CmsRole} objects
5102     *
5103     * @throws CmsException if something goes wrong
5104     */
5105    public List<CmsRole> getRolesForUser(CmsDbContext dbc, CmsUser user) throws CmsException {
5106
5107        // guest user has no role
5108        if (user.isGuestUser()) {
5109            return Collections.emptyList();
5110        }
5111
5112        // try to read from cache
5113        List<CmsRole> result = m_monitor.getGroupListCache().getBareRoles(user.getId());
5114        if (result != null) {
5115            return result;
5116        }
5117        result = new ArrayList<CmsRole>();
5118
5119        // read all roles of the current user
5120        List<CmsGroup> groups = new ArrayList<CmsGroup>(
5121            getGroupsOfUser(dbc, user.getName(), "", true, true, false, dbc.getRequestContext().getRemoteAddress()));
5122
5123        // check the roles applying to the given resource
5124        Iterator<CmsGroup> it = groups.iterator();
5125        while (it.hasNext()) {
5126            CmsGroup group = it.next();
5127            CmsRole givenRole = CmsRole.valueOf(group);
5128            givenRole = givenRole.forOrgUnit(null);
5129            if (!result.contains(givenRole)) {
5130                result.add(givenRole);
5131            }
5132        }
5133        result = Collections.unmodifiableList(result);
5134        m_monitor.getGroupListCache().setBareRoles(user, result);
5135        return result;
5136    }
5137
5138    /**
5139     * Returns the security manager this driver manager belongs to.<p>
5140     *
5141     * @return the security manager this driver manager belongs to
5142     */
5143    public CmsSecurityManager getSecurityManager() {
5144
5145        return m_securityManager;
5146    }
5147
5148    /**
5149     * Returns an instance of the common sql manager.<p>
5150     *
5151     * @return an instance of the common sql manager
5152     */
5153    public CmsSqlManager getSqlManager() {
5154
5155        return m_sqlManager;
5156    }
5157
5158    /**
5159     * Returns the subscription driver of this driver manager.<p>
5160     *
5161     * @return a subscription driver
5162     */
5163    public I_CmsSubscriptionDriver getSubscriptionDriver() {
5164
5165        return m_subscriptionDriver;
5166    }
5167
5168    /**
5169     * Returns the user driver.<p>
5170     *
5171     * @return the user driver
5172     */
5173    public I_CmsUserDriver getUserDriver() {
5174
5175        return m_userDriver;
5176    }
5177
5178    /**
5179     * Returns the user driver for a given database context.<p>
5180     *
5181     * @param dbc the database context
5182     *
5183     * @return the user driver for the database context
5184     */
5185    public I_CmsUserDriver getUserDriver(CmsDbContext dbc) {
5186
5187        if ((dbc == null) || (dbc.getProjectId() == null) || dbc.getProjectId().isNullUUID()) {
5188            return m_userDriver;
5189        }
5190        I_CmsUserDriver driver = dbc.getUserDriver(dbc.getProjectId());
5191        return driver != null ? driver : m_userDriver;
5192
5193    }
5194
5195    /**
5196     * Returns either the user driver for the given DB context (if it has one) or a default value instead.<p>
5197     *
5198     * @param dbc the DB context
5199     * @param defaultDriver the driver that should be returned if no driver for the DB context was found
5200     *
5201     * @return either the user driver for the DB context, or <code>defaultDriver</code> if none were found
5202     */
5203    public I_CmsUserDriver getUserDriver(CmsDbContext dbc, I_CmsUserDriver defaultDriver) {
5204
5205        if ((dbc == null) || (dbc.getProjectId() == null) || dbc.getProjectId().isNullUUID()) {
5206            return defaultDriver;
5207        }
5208        I_CmsUserDriver driver = dbc.getUserDriver(dbc.getProjectId());
5209        return driver != null ? driver : defaultDriver;
5210    }
5211
5212    /**
5213     * Returns all direct users of the given organizational unit.<p>
5214     *
5215     * @param dbc the current db context
5216     * @param orgUnit the organizational unit to get all users for
5217     * @param recursive if all groups of sub-organizational units should be retrieved too
5218     *
5219     * @return all <code>{@link CmsUser}</code> objects in the organizational unit
5220     *
5221     * @throws CmsException if operation was not successful
5222     *
5223     * @see org.opencms.security.CmsOrgUnitManager#getResourcesForOrganizationalUnit(CmsObject, String)
5224     * @see org.opencms.security.CmsOrgUnitManager#getUsers(CmsObject, String, boolean)
5225     */
5226    public List<CmsUser> getUsers(CmsDbContext dbc, CmsOrganizationalUnit orgUnit, boolean recursive)
5227    throws CmsException {
5228
5229        return getUserDriver(dbc).getUsers(dbc, orgUnit, recursive);
5230    }
5231
5232    /**
5233     * Returns a list of users in a group.<p>
5234     *
5235     * @param dbc the current database context
5236     * @param groupname the name of the group to list users from
5237     * @param includeOtherOuUsers include users of other organizational units
5238     * @param directUsersOnly if set only the direct assigned users will be returned,
5239     *                        if not also indirect users, ie. members of parent roles,
5240     *                        this parameter only works with roles
5241     * @param readRoles if to read roles or groups
5242     *
5243     * @return all <code>{@link CmsUser}</code> objects in the group
5244     *
5245     * @throws CmsException if operation was not successful
5246     */
5247    public List<CmsUser> getUsersOfGroup(
5248        CmsDbContext dbc,
5249        String groupname,
5250        boolean includeOtherOuUsers,
5251        boolean directUsersOnly,
5252        boolean readRoles)
5253    throws CmsException {
5254
5255        return internalUsersOfGroup(
5256            dbc,
5257            CmsOrganizationalUnit.getParentFqn(groupname),
5258            groupname,
5259            includeOtherOuUsers,
5260            directUsersOnly,
5261            readRoles);
5262    }
5263
5264    /**
5265     * Returns the given user's publish list.<p>
5266     *
5267     * @param dbc the database context
5268     * @param userId the user's id
5269     *
5270     * @return the given user's publish list
5271     *
5272     * @throws CmsDataAccessException if something goes wrong
5273     */
5274    public List<CmsResource> getUsersPubList(CmsDbContext dbc, CmsUUID userId) throws CmsDataAccessException {
5275
5276        synchronized (m_publishListUpdateLock) {
5277            updateLog(dbc);
5278            return m_projectDriver.getUsersPubList(dbc, userId);
5279        }
5280    }
5281
5282    /**
5283     * Returns all direct users of the given organizational unit, without their additional info.<p>
5284     *
5285     * @param dbc the current db context
5286     * @param orgUnit the organizational unit to get all users for
5287     * @param recursive if all groups of sub-organizational units should be retrieved too
5288     *
5289     * @return all <code>{@link CmsUser}</code> objects in the organizational unit
5290     *
5291     * @throws CmsException if operation was not successful
5292     *
5293     * @see org.opencms.security.CmsOrgUnitManager#getResourcesForOrganizationalUnit(CmsObject, String)
5294     * @see org.opencms.security.CmsOrgUnitManager#getUsers(CmsObject, String, boolean)
5295     */
5296    public List<CmsUser> getUsersWithoutAdditionalInfo(
5297        CmsDbContext dbc,
5298        CmsOrganizationalUnit orgUnit,
5299        boolean recursive)
5300    throws CmsException {
5301
5302        return getUserDriver(dbc).getUsersWithoutAdditionalInfo(dbc, orgUnit, recursive);
5303    }
5304
5305    /**
5306     * Returns the VFS driver.<p>
5307     *
5308     * @return the VFS driver
5309     */
5310    public I_CmsVfsDriver getVfsDriver() {
5311
5312        return m_vfsDriver;
5313    }
5314
5315    /**
5316     * Returns the VFS driver for the given database context.<p>
5317     *
5318     * @param dbc the database context
5319     *
5320     * @return a VFS driver
5321     */
5322    public I_CmsVfsDriver getVfsDriver(CmsDbContext dbc) {
5323
5324        if ((dbc == null) || (dbc.getProjectId() == null) || dbc.getProjectId().isNullUUID()) {
5325            return m_vfsDriver;
5326        }
5327        I_CmsVfsDriver driver = dbc.getVfsDriver(dbc.getProjectId());
5328        return driver != null ? driver : m_vfsDriver;
5329
5330    }
5331
5332    /**
5333     * Writes a vector of access control entries as new access control entries of a given resource.<p>
5334     *
5335     * Already existing access control entries of this resource are removed before.
5336     * Access is granted, if:<p>
5337     * <ul>
5338     * <li>the current user has control permission on the resource</li>
5339     * </ul>
5340     *
5341     * @param dbc the current database context
5342     * @param resource the resource
5343     * @param acEntries a list of <code>{@link CmsAccessControlEntry}</code> objects
5344     *
5345     * @throws CmsException if something goes wrong
5346     */
5347    public void importAccessControlEntries(
5348        CmsDbContext dbc,
5349        CmsResource resource,
5350        List<CmsAccessControlEntry> acEntries)
5351    throws CmsException {
5352
5353        I_CmsUserDriver userDriver = getUserDriver(dbc);
5354        userDriver.removeAccessControlEntries(dbc, dbc.currentProject(), resource.getResourceId());
5355        List<CmsAccessControlEntry> fixedAces = new ArrayList<>();
5356        for (CmsAccessControlEntry entry : acEntries) {
5357            if (entry.getResource() == null) {
5358                entry = new CmsAccessControlEntry(
5359                    resource.getResourceId(),
5360                    entry.getPrincipal(),
5361                    entry.getPermissions(),
5362                    entry.getFlags());
5363            }
5364            fixedAces.add(entry);
5365        }
5366
5367        Iterator<CmsAccessControlEntry> i = fixedAces.iterator();
5368        while (i.hasNext()) {
5369            userDriver.writeAccessControlEntry(dbc, dbc.currentProject(), i.next());
5370        }
5371        m_monitor.clearAccessControlListCache();
5372    }
5373
5374    /**
5375     * Imports a rewrite alias.<p>
5376     *
5377     * @param dbc the database context
5378     * @param siteRoot the site root of the alias
5379     * @param source the source of the alias
5380     * @param target the target of the alias
5381     * @param mode the alias mode
5382     *
5383     * @return the import result
5384     *
5385     * @throws CmsException if something goes wrong
5386     */
5387    public CmsAliasImportResult importRewriteAlias(
5388        CmsDbContext dbc,
5389        String siteRoot,
5390        String source,
5391        String target,
5392        CmsAliasMode mode)
5393    throws CmsException {
5394
5395        I_CmsVfsDriver vfs = getVfsDriver(dbc);
5396        List<CmsRewriteAlias> existingAliases = vfs.readRewriteAliases(
5397            dbc,
5398            new CmsRewriteAliasFilter().setSiteRoot(siteRoot));
5399        CmsUUID idToDelete = null;
5400        for (CmsRewriteAlias alias : existingAliases) {
5401            if (alias.getPatternString().equals(source)) {
5402                idToDelete = alias.getId();
5403            }
5404        }
5405        if (idToDelete != null) {
5406            vfs.deleteRewriteAliases(dbc, new CmsRewriteAliasFilter().setId(idToDelete));
5407        }
5408        CmsRewriteAlias alias = new CmsRewriteAlias(new CmsUUID(), siteRoot, source, target, mode);
5409        List<CmsRewriteAlias> aliases = new ArrayList<CmsRewriteAlias>();
5410        aliases.add(alias);
5411        getVfsDriver(dbc).insertRewriteAliases(dbc, aliases);
5412        CmsAliasImportResult result = new CmsAliasImportResult(
5413            CmsAliasImportStatus.aliasNew,
5414            "OK",
5415            source,
5416            target,
5417            mode);
5418        return result;
5419    }
5420
5421    /**
5422     * Creates a new user by import.<p>
5423     *
5424     * @param dbc the current database context
5425     * @param id the id of the user
5426     * @param name the new name for the user
5427     * @param password the new password for the user (already encrypted)
5428     * @param firstname the firstname of the user
5429     * @param lastname the lastname of the user
5430     * @param email the email of the user
5431     * @param flags the flags for a user (for example <code>{@link I_CmsPrincipal#FLAG_ENABLED}</code>)
5432     * @param dateCreated the creation date
5433     * @param additionalInfos the additional user infos
5434     *
5435     * @return the imported user
5436     *
5437     * @throws CmsException if something goes wrong
5438     */
5439    public CmsUser importUser(
5440        CmsDbContext dbc,
5441        String id,
5442        String name,
5443        String password,
5444        String firstname,
5445        String lastname,
5446        String email,
5447        int flags,
5448        long dateCreated,
5449        Map<String, Object> additionalInfos)
5450    throws CmsException {
5451
5452        // no space before or after the name
5453        name = name.trim();
5454        // check the user name
5455        String userName = CmsOrganizationalUnit.getSimpleName(name);
5456        OpenCms.getValidationHandler().checkUserName(userName);
5457        if (CmsStringUtil.isEmptyOrWhitespaceOnly(userName)) {
5458            throw new CmsIllegalArgumentException(Messages.get().container(Messages.ERR_BAD_USER_1, userName));
5459        }
5460        // check the ou
5461        CmsOrganizationalUnit ou = readOrganizationalUnit(dbc, CmsOrganizationalUnit.getParentFqn(name));
5462
5463        // check webuser ou
5464        if (ou.hasFlagWebuser() && ((flags & I_CmsPrincipal.FLAG_USER_WEBUSER) == 0)) {
5465            flags += I_CmsPrincipal.FLAG_USER_WEBUSER;
5466        }
5467        CmsUser newUser = getUserDriver(dbc).createUser(
5468            dbc,
5469            new CmsUUID(id),
5470            name,
5471            password,
5472            firstname,
5473            lastname,
5474            email,
5475            0,
5476            flags,
5477            dateCreated,
5478            additionalInfos);
5479        return newUser;
5480    }
5481
5482    /**
5483     * Increments a counter and returns its value before incrementing.<p>
5484     *
5485     * @param dbc the current database context
5486     * @param name the name of the counter which should be incremented
5487     *
5488     * @return the value of the counter
5489     *
5490     * @throws CmsException if something goes wrong
5491     */
5492    public int incrementCounter(CmsDbContext dbc, String name) throws CmsException {
5493
5494        return getVfsDriver(dbc).incrementCounter(dbc, name);
5495    }
5496
5497    /**
5498     * Initializes the driver and sets up all required modules and connections.<p>
5499     *
5500     * @param configurationManager the configuration manager
5501     * @param dbContextFactory the db context factory
5502     *
5503     * @throws CmsException if something goes wrong
5504     * @throws Exception if something goes wrong
5505     */
5506    public void init(CmsConfigurationManager configurationManager, I_CmsDbContextFactory dbContextFactory)
5507    throws CmsException, Exception {
5508
5509        // initialize the access-module.
5510        if (CmsLog.INIT.isInfoEnabled()) {
5511            CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_DRIVER_MANAGER_START_PHASE4_0));
5512        }
5513        // store local reference to the memory monitor to avoid multiple lookups through the OpenCms singelton
5514        m_monitor = OpenCms.getMemoryMonitor();
5515
5516        CmsSystemConfiguration systemConfiguation = (CmsSystemConfiguration)configurationManager.getConfiguration(
5517            CmsSystemConfiguration.class);
5518        CmsCacheSettings settings = systemConfiguation.getCacheSettings();
5519
5520        // initialize the key generator
5521        m_keyGenerator = (I_CmsCacheKey)Class.forName(settings.getCacheKeyGenerator()).newInstance();
5522
5523        // initialize the HTML link validator
5524        m_htmlLinkValidator = new CmsRelationSystemValidator(this);
5525
5526        // fills the defaults if needed
5527        CmsDbContext dbc1 = dbContextFactory.getDbContext();
5528        getUserDriver().fillDefaults(dbc1);
5529        getProjectDriver().fillDefaults(dbc1);
5530
5531        // set the driver manager in the publish engine
5532        m_publishEngine.setDriverManager(this);
5533        // create the root organizational unit if needed
5534        CmsDbContext dbc2 = dbContextFactory.getDbContext(
5535            new CmsRequestContext(
5536                readUser(dbc1, OpenCms.getDefaultUsers().getUserAdmin()),
5537                readProject(dbc1, CmsProject.ONLINE_PROJECT_ID),
5538                null,
5539                CmsSiteMatcher.DEFAULT_MATCHER,
5540                "",
5541                false,
5542                null,
5543                null,
5544                null,
5545                0,
5546                null,
5547                null,
5548                "",
5549                false));
5550        dbc1.clear();
5551        getUserDriver().createRootOrganizationalUnit(dbc2);
5552        dbc2.clear();
5553    }
5554
5555    /**
5556     * Initializes the organizational unit.<p>
5557     *
5558     * @param dbc the DB context
5559     * @param ou the organizational unit
5560     */
5561    public void initOrgUnit(CmsDbContext dbc, CmsOrganizationalUnit ou) {
5562
5563        try {
5564            dbc.setAttribute(ATTR_INIT_OU, ou);
5565            m_userDriver.fillDefaults(dbc);
5566        } finally {
5567            dbc.removeAttribute(ATTR_INIT_OU);
5568        }
5569    }
5570
5571    /**
5572     * Checks if the specified resource is inside the current project.<p>
5573     *
5574     * The project "view" is determined by a set of path prefixes.
5575     * If the resource starts with any one of this prefixes, it is considered to
5576     * be "inside" the project.<p>
5577     *
5578     * @param dbc the current database context
5579     * @param resourcename the specified resource name (full path)
5580     *
5581     * @return <code>true</code>, if the specified resource is inside the current project
5582     */
5583    public boolean isInsideCurrentProject(CmsDbContext dbc, String resourcename) {
5584
5585        List<String> projectResources = null;
5586        try {
5587            projectResources = readProjectResources(dbc, dbc.currentProject());
5588        } catch (CmsException e) {
5589            if (LOG.isErrorEnabled()) {
5590                LOG.error(
5591                    Messages.get().getBundle().key(
5592                        Messages.LOG_CHECK_RESOURCE_INSIDE_CURRENT_PROJECT_2,
5593                        resourcename,
5594                        dbc.currentProject().getName()),
5595                    e);
5596            }
5597            return false;
5598        }
5599        return CmsProject.isInsideProject(projectResources, resourcename);
5600    }
5601
5602    /**
5603     * Checks whether the subscription driver is available.<p>
5604     *
5605     * @return true if the subscription driver is available
5606     */
5607    public boolean isSubscriptionDriverAvailable() {
5608
5609        return m_subscriptionDriver != null;
5610    }
5611
5612    /**
5613     * Checks if a project is the tempfile project.<p>
5614     * @param project the project to test
5615     * @return true if the project is the tempfile project
5616     */
5617    public boolean isTempfileProject(CmsProject project) {
5618
5619        return project.getName().equals("tempFileProject");
5620    }
5621
5622    /**
5623     * Checks if one of the resources (except the resource itself)
5624     * is a sibling in a "labeled" site folder.<p>
5625     *
5626     * This method is used when creating a new sibling
5627     * (use the <code>newResource</code> parameter & <code>action = 1</code>)
5628     * or deleting/importing a resource (call with <code>action = 2</code>).<p>
5629     *
5630     * @param dbc the current database context
5631     * @param resource the resource
5632     * @param newResource absolute path for a resource sibling which will be created
5633     * @param action the action which has to be performed (1: create VFS link, 2: all other actions)
5634     *
5635     * @return <code>true</code> if the flag should be set for the resource, otherwise <code>false</code>
5636     *
5637     * @throws CmsDataAccessException if something goes wrong
5638     */
5639    public boolean labelResource(CmsDbContext dbc, CmsResource resource, String newResource, int action)
5640    throws CmsDataAccessException {
5641
5642        // get the list of labeled site folders from the runtime property
5643        List<String> labeledSites = OpenCms.getWorkplaceManager().getLabelSiteFolders();
5644
5645        if (labeledSites.size() == 0) {
5646            // no labeled sites defined, just return false
5647            return false;
5648        }
5649
5650        if (action == 1) {
5651            // CASE 1: a new resource is created, check the sites
5652            if (!resource.isLabeled()) {
5653                // source isn't labeled yet, so check!
5654                boolean linkInside = false;
5655                boolean sourceInside = false;
5656                for (int i = 0; i < labeledSites.size(); i++) {
5657                    String curSite = labeledSites.get(i);
5658                    if (newResource.startsWith(curSite)) {
5659                        // the link lies in a labeled site
5660                        linkInside = true;
5661                    }
5662                    if (resource.getRootPath().startsWith(curSite)) {
5663                        // the source lies in a labeled site
5664                        sourceInside = true;
5665                    }
5666                    if (linkInside && sourceInside) {
5667                        break;
5668                    }
5669                }
5670                // return true when either source or link is in labeled site, otherwise false
5671                return (linkInside != sourceInside);
5672            }
5673            // resource is already labeled
5674            return false;
5675
5676        } else {
5677            // CASE 2: the resource will be deleted or created (import)
5678            // check if at least one of the other siblings resides inside a "labeled site"
5679            // and if at least one of the other siblings resides outside a "labeled site"
5680            boolean isInside = false;
5681            boolean isOutside = false;
5682            // check if one of the other vfs links lies in a labeled site folder
5683            List<CmsResource> siblings = getVfsDriver(
5684                dbc).readSiblings(dbc, dbc.currentProject().getUuid(), resource, false);
5685            updateContextDates(dbc, siblings);
5686            Iterator<CmsResource> i = siblings.iterator();
5687            while (i.hasNext() && (!isInside || !isOutside)) {
5688                CmsResource currentResource = i.next();
5689                if (currentResource.equals(resource)) {
5690                    // dont't check the resource itself!
5691                    continue;
5692                }
5693                String curPath = currentResource.getRootPath();
5694                boolean curInside = false;
5695                for (int k = 0; k < labeledSites.size(); k++) {
5696                    if (curPath.startsWith(labeledSites.get(k))) {
5697                        // the link is in the labeled site
5698                        isInside = true;
5699                        curInside = true;
5700                        break;
5701                    }
5702                }
5703                if (!curInside) {
5704                    // the current link was not found in labeled site, so it is outside
5705                    isOutside = true;
5706                }
5707            }
5708            // now check the new resource name if present
5709            if (newResource != null) {
5710                boolean curInside = false;
5711                for (int k = 0; k < labeledSites.size(); k++) {
5712                    if (newResource.startsWith(labeledSites.get(k))) {
5713                        // the new resource is in the labeled site
5714                        isInside = true;
5715                        curInside = true;
5716                        break;
5717                    }
5718                }
5719                if (!curInside) {
5720                    // the new resource was not found in labeled site, so it is outside
5721                    isOutside = true;
5722                }
5723            }
5724            return (isInside && isOutside);
5725        }
5726    }
5727
5728    /**
5729     * Returns the user, who had locked the resource.<p>
5730     *
5731     * A user can lock a resource, so he is the only one who can write this
5732     * resource. This methods checks, if a resource was locked.
5733     *
5734     * @param dbc the current database context
5735     * @param resource the resource
5736     *
5737     * @return the user, who had locked the resource
5738     *
5739     * @throws CmsException will be thrown, if the user has not the rights for this resource
5740     */
5741    public CmsUser lockedBy(CmsDbContext dbc, CmsResource resource) throws CmsException {
5742
5743        return readUser(dbc, m_lockManager.getLock(dbc, resource).getEditionLock().getUserId());
5744    }
5745
5746    /**
5747     * Locks a resource.<p>
5748     *
5749     * The <code>type</code> parameter controls what kind of lock is used.<br>
5750     * Possible values for this parameter are: <br>
5751     * <ul>
5752     * <li><code>{@link org.opencms.lock.CmsLockType#EXCLUSIVE}</code></li>
5753     * <li><code>{@link org.opencms.lock.CmsLockType#TEMPORARY}</code></li>
5754     * <li><code>{@link org.opencms.lock.CmsLockType#PUBLISH}</code></li>
5755     * </ul><p>
5756     *
5757     * @param dbc the current database context
5758     * @param resource the resource to lock
5759     * @param type type of the lock
5760     *
5761     * @throws CmsException if something goes wrong
5762     *
5763     * @see CmsObject#lockResource(String)
5764     * @see CmsObject#lockResourceTemporary(String)
5765     * @see org.opencms.file.types.I_CmsResourceType#lockResource(CmsObject, CmsSecurityManager, CmsResource, CmsLockType)
5766     */
5767    public void lockResource(CmsDbContext dbc, CmsResource resource, CmsLockType type) throws CmsException {
5768
5769        // update the resource cache
5770        m_monitor.clearResourceCache();
5771
5772        CmsProject project = dbc.currentProject();
5773
5774        // add the resource to the lock dispatcher
5775        m_lockManager.addResource(dbc, resource, dbc.currentUser(), project, type);
5776        boolean changedProjectLastModified = false;
5777        if (!resource.getState().isUnchanged() && !resource.getState().isKeep()) {
5778            // update the project flag of a modified resource as "last modified inside the current project"
5779            getVfsDriver(dbc).writeLastModifiedProjectId(dbc, project, project.getUuid(), resource);
5780            changedProjectLastModified = true;
5781        }
5782
5783        // we must also clear the permission cache
5784        m_monitor.flushCache(CmsMemoryMonitor.CacheType.PERMISSION);
5785
5786        // fire resource modification event
5787        Map<String, Object> data = new HashMap<String, Object>(2);
5788        data.put(I_CmsEventListener.KEY_RESOURCE, resource);
5789        data.put(
5790            I_CmsEventListener.KEY_CHANGE,
5791            Integer.valueOf(changedProjectLastModified ? CHANGED_PROJECT : NOTHING_CHANGED));
5792        data.put(I_CmsEventListener.KEY_SKIPINDEX, Boolean.TRUE);
5793        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_MODIFIED, data));
5794    }
5795
5796    /**
5797     * Adds the given log entry to the current user's log.<p>
5798     *
5799     * This operation works only on memory, to get the log entries actually
5800     * written to DB you have to call the {@link #updateLog(CmsDbContext)} method.<p>
5801     *
5802     * @param dbc the current database context
5803     * @param logEntry the log entry to create
5804     * @param force forces the log entry to be counted,
5805     *              if not only the first log entry in a transaction will be taken into account
5806     */
5807    public void log(CmsDbContext dbc, CmsLogEntry logEntry, boolean force) {
5808
5809        if (dbc == null) {
5810            return;
5811        }
5812        // check log level
5813        if (!logEntry.getType().isActive()) {
5814            // do not log inactive entries
5815            return;
5816        }
5817        // if not forcing
5818        if (!force) {
5819            // operation already logged
5820            boolean abort = (dbc.getAttribute(CmsLogEntry.ATTR_LOG_ENTRY) != null);
5821            // disabled logging from outside
5822            abort |= (dbc.getRequestContext().getAttribute(CmsLogEntry.ATTR_LOG_ENTRY) != null);
5823            if (abort) {
5824                return;
5825            }
5826        }
5827        // prevent several entries for the same operation
5828        dbc.setAttribute(CmsLogEntry.ATTR_LOG_ENTRY, Boolean.TRUE);
5829        // keep it for later
5830        m_log.add(logEntry);
5831    }
5832
5833    /**
5834     * Attempts to authenticate a user into OpenCms with the given password.
5835     *
5836     * <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,
5837     * while check mode merely checks the login details without firing the events normally fired during login, and without modifying the user. However,
5838     * in the case an incorrect password is given, the invalid login counter is still incremented.
5839     *
5840     * @param dbc the current database context
5841     * @param userName the name of the user to be logged in
5842     * @param password the password of the user
5843     * @param secondFactorInfo the second factor information for 2FA (may be null)
5844     * @param remoteAddress the ip address of the request
5845     * @param mode the mode to use (real login or check only)
5846     *
5847     * @return the logged in user
5848     *
5849     * @throws CmsAuthentificationException if the login was not successful
5850     * @throws CmsDataAccessException in case of errors accessing the database
5851     * @throws CmsPasswordEncryptionException in case of errors encrypting the users password
5852     */
5853    public CmsUser loginUser(
5854        CmsDbContext dbc,
5855        String userName,
5856        String password,
5857        CmsSecondFactorInfo secondFactorInfo,
5858        String remoteAddress,
5859        LoginUserMode mode)
5860    throws CmsAuthentificationException, CmsDataAccessException, CmsPasswordEncryptionException {
5861
5862        if (CmsStringUtil.isEmptyOrWhitespaceOnly(password)) {
5863            throw new CmsDbEntryNotFoundException(Messages.get().container(Messages.ERR_UNKNOWN_USER_1, userName));
5864        }
5865        CmsUser newUser;
5866        CmsUser userCopy;
5867        try {
5868            // read the user from the driver to avoid the cache
5869            newUser = getUserDriver(dbc).readUser(dbc, userName, password, remoteAddress);
5870            userCopy = newUser.clone();
5871            userName = newUser.getName();
5872
5873        } catch (CmsDbEntryNotFoundException e) {
5874            // this indicates that the username / password combination does not exist
5875            // any other exception indicates database issues, these are not catched here
5876
5877            // check if a user with this name exists at all
5878            CmsUser user = null;
5879            try {
5880                user = readUser(dbc, userName);
5881                userName = user.getName();
5882            } catch (CmsDataAccessException e2) {
5883                // apparently this user does not exist in the database
5884            }
5885
5886            if (user != null) {
5887                if (dbc.currentUser().isGuestUser()) {
5888                    // add an invalid login attempt for this user to the storage
5889                    OpenCms.getLoginManager().addInvalidLogin(userName, remoteAddress);
5890                }
5891                OpenCms.getLoginManager().checkInvalidLogins(userName, remoteAddress);
5892                throw new CmsAuthentificationException(
5893                    org.opencms.security.Messages.get().container(
5894                        org.opencms.security.Messages.ERR_LOGIN_FAILED_2,
5895                        userName,
5896                        remoteAddress),
5897                    e);
5898            } else {
5899                String userOu = CmsOrganizationalUnit.getParentFqn(userName);
5900                if (userOu != null) {
5901                    String parentOu = CmsOrganizationalUnit.getParentFqn(userOu);
5902                    if (parentOu != null) {
5903                        // try a higher level ou
5904                        String uName = CmsOrganizationalUnit.getSimpleName(userName);
5905                        return loginUser(dbc, parentOu + uName, password, secondFactorInfo, remoteAddress, mode);
5906                    }
5907                }
5908                throw new CmsAuthentificationException(
5909                    org.opencms.security.Messages.get().container(
5910                        org.opencms.security.Messages.ERR_LOGIN_FAILED_NO_USER_2,
5911                        userName,
5912                        remoteAddress),
5913                    e);
5914            }
5915        }
5916        // check if the "enabled" flag is set for the user
5917        if (!newUser.isEnabled()) {
5918            // user is disabled, throw a securiy exception
5919            throw new CmsAuthentificationException(
5920                org.opencms.security.Messages.get().container(
5921                    org.opencms.security.Messages.ERR_LOGIN_FAILED_DISABLED_2,
5922                    userName,
5923                    remoteAddress));
5924        }
5925
5926        if (mode == LoginUserMode.standard) {
5927            CmsTwoFactorAuthenticationHandler handler = OpenCms.getTwoFactorAuthenticationHandler();
5928            if (handler.needsTwoFactorAuthentication(newUser)) {
5929                // note that password check must already have been successful at this stage
5930
5931                if (handler.hasSecondFactor(newUser)) {
5932                    if (!handler.verifySecondFactor(newUser, secondFactorInfo)) {
5933                        if (dbc.currentUser().isGuestUser()) {
5934                            // add an invalid login attempt for this user to the storage
5935                            OpenCms.getLoginManager().addInvalidLogin(userName, remoteAddress);
5936                        }
5937                        OpenCms.getLoginManager().checkInvalidLogins(userName, remoteAddress);
5938                        throw new CmsAuthentificationException(
5939                            org.opencms.security.Messages.get().container(
5940                                org.opencms.security.Messages.ERR_VERIFICATION_FAILED_1,
5941                                userName));
5942                    }
5943                } else {
5944                    try {
5945                        if (handler.setUpAndVerifySecondFactor(newUser, secondFactorInfo)) {
5946                            LOG.info("Second factor setup successful for user " + newUser.getName());
5947                        } else {
5948                            if (dbc.currentUser().isGuestUser()) {
5949                                // add an invalid login attempt for this user to the storage
5950                                OpenCms.getLoginManager().addInvalidLogin(userName, remoteAddress);
5951                            }
5952                            OpenCms.getLoginManager().checkInvalidLogins(userName, remoteAddress);
5953                            throw new CmsAuthentificationException(
5954                                org.opencms.security.Messages.get().container(
5955                                    org.opencms.security.Messages.ERR_VERIFICATION_FAILED_1,
5956                                    userName));
5957                        }
5958                    } catch (CmsSecondFactorSetupException e) {
5959                        throw new CmsAuthentificationException(
5960                            org.opencms.security.Messages.get().container(
5961                                org.opencms.security.Messages.ERR_VERIFICATION_FAILED_1,
5962                                userName),
5963                            e);
5964                    }
5965                }
5966            }
5967        }
5968        if (dbc.currentUser().isGuestUser()) {
5969            // check if this account is temporarily disabled because of too many invalid login attempts
5970            // this will throw an exception if the test fails
5971            OpenCms.getLoginManager().checkInvalidLogins(userName, remoteAddress);
5972            if (mode == LoginUserMode.standard) {
5973                // test successful, remove all previous invalid login attempts for this user from the storage
5974                OpenCms.getLoginManager().removeInvalidLogins(userName, remoteAddress);
5975            }
5976        }
5977
5978        if (!m_securityManager.hasRole(
5979            dbc,
5980            newUser,
5981            CmsRole.ADMINISTRATOR.forOrgUnit(dbc.getRequestContext().getOuFqn()))) {
5982            // new user is not Administrator, check if login is currently allowed
5983            OpenCms.getLoginManager().checkLoginAllowed();
5984        }
5985
5986        if (mode == LoginUserMode.standard) {
5987
5988            newUser.setLastlogin(System.currentTimeMillis());
5989            m_monitor.clearUserCache(newUser);
5990
5991            // write the changed user object back to the user driver
5992            Map<String, Object> additionalInfosForRepositories = OpenCms.getRepositoryManager().getAdditionalInfoForLogin(
5993                newUser.getName(),
5994                password);
5995            boolean requiresAddInfoUpdate = false;
5996
5997            // check for changes
5998            for (Entry<String, Object> entry : additionalInfosForRepositories.entrySet()) {
5999                Object value = entry.getValue();
6000                Object current = newUser.getAdditionalInfo(entry.getKey());
6001                if (((value == null) && (current != null)) || ((value != null) && !value.equals(current))) {
6002                    requiresAddInfoUpdate = true;
6003                    break;
6004                }
6005            }
6006            if (requiresAddInfoUpdate) {
6007                newUser.getAdditionalInfo().putAll(additionalInfosForRepositories);
6008            }
6009            String lastPasswordChange = (String)newUser.getAdditionalInfo(
6010                CmsUserSettings.ADDITIONAL_INFO_LAST_PASSWORD_CHANGE);
6011            if (lastPasswordChange == null) {
6012                requiresAddInfoUpdate = true;
6013                newUser.getAdditionalInfo().put(
6014                    CmsUserSettings.ADDITIONAL_INFO_LAST_PASSWORD_CHANGE,
6015                    "" + System.currentTimeMillis());
6016            }
6017            if (!requiresAddInfoUpdate) {
6018                dbc.setAttribute(ATTRIBUTE_LOGIN, newUser.getName());
6019            }
6020
6021            if (mode == LoginUserMode.standard) {
6022                OpenCms.getTwoFactorAuthenticationHandler().trackUserChange(dbc.getRequestContext(), userCopy, newUser);
6023                getUserDriver(dbc).writeUser(dbc, newUser);
6024            }
6025            int changes = CmsUser.FLAG_LAST_LOGIN;
6026
6027            // check if we need to update the password
6028            if (!OpenCms.getPasswordHandler().checkPassword(password, newUser.getPassword(), false)
6029                && OpenCms.getPasswordHandler().checkPassword(password, newUser.getPassword(), true)) {
6030                // the password does not check with the current hash algorithm but with the fall back, update the password
6031                getUserDriver(dbc).writePassword(dbc, userName, password, password);
6032                changes = changes | CmsUser.FLAG_CORE_DATA;
6033            }
6034
6035            // update cache
6036            m_monitor.cacheUser(newUser);
6037
6038            // invalidate all user dependent caches
6039            m_monitor.flushCache(
6040                CmsMemoryMonitor.CacheType.ACL,
6041                CmsMemoryMonitor.CacheType.GROUP,
6042                CmsMemoryMonitor.CacheType.ORG_UNIT,
6043                CmsMemoryMonitor.CacheType.USER_LIST,
6044                CmsMemoryMonitor.CacheType.PERMISSION,
6045                CmsMemoryMonitor.CacheType.RESOURCE_LIST);
6046
6047            // fire user modified event
6048            Map<String, Object> eventData = new HashMap<String, Object>();
6049            eventData.put(I_CmsEventListener.KEY_USER_ID, newUser.getId().toString());
6050            eventData.put(I_CmsEventListener.KEY_USER_NAME, newUser.getName());
6051            eventData.put(I_CmsEventListener.KEY_USER_ACTION, I_CmsEventListener.VALUE_USER_MODIFIED_ACTION_WRITE_USER);
6052            eventData.put(I_CmsEventListener.KEY_USER_CHANGES, Integer.valueOf(changes));
6053            OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_USER_MODIFIED, eventData));
6054        }
6055
6056        // return the user object read from the driver
6057        return newUser.clone();
6058    }
6059
6060    /**
6061     * Lookup and read the user or group with the given UUID.<p>
6062     *
6063     * @param dbc the current database context
6064     * @param principalId the UUID of the principal to lookup
6065     *
6066     * @return the principal (group or user) if found, otherwise <code>null</code>
6067     */
6068    public I_CmsPrincipal lookupPrincipal(CmsDbContext dbc, CmsUUID principalId) {
6069
6070        try {
6071            CmsGroup group = getUserDriver(dbc).readGroup(dbc, principalId);
6072            if (group != null) {
6073                return group;
6074            }
6075        } catch (Exception e) {
6076            // ignore this exception
6077        }
6078
6079        try {
6080            CmsUser user = readUser(dbc, principalId);
6081            if (user != null) {
6082                return user;
6083            }
6084        } catch (Exception e) {
6085            // ignore this exception
6086        }
6087
6088        return null;
6089    }
6090
6091    /**
6092     * Lookup and read the user or group with the given name.<p>
6093     *
6094     * @param dbc the current database context
6095     * @param principalName the name of the principal to lookup
6096     *
6097     * @return the principal (group or user) if found, otherwise <code>null</code>
6098     */
6099    public I_CmsPrincipal lookupPrincipal(CmsDbContext dbc, String principalName) {
6100
6101        try {
6102            CmsGroup group = getUserDriver(dbc).readGroup(dbc, principalName);
6103            if (group != null) {
6104                return group;
6105            }
6106        } catch (Exception e) {
6107            // ignore this exception
6108        }
6109
6110        try {
6111            CmsUser user = readUser(dbc, principalName);
6112            if (user != null) {
6113                return user;
6114            }
6115        } catch (Exception e) {
6116            // ignore this exception
6117        }
6118
6119        return null;
6120    }
6121
6122    /**
6123     * Mark the given resource as visited by the user.<p>
6124     *
6125     * @param dbc the database context
6126     * @param poolName the name of the database pool to use
6127     * @param resource the resource to mark as visited
6128     * @param user the user that visited the resource
6129     *
6130     * @throws CmsException if something goes wrong
6131     */
6132    public void markResourceAsVisitedBy(CmsDbContext dbc, String poolName, CmsResource resource, CmsUser user)
6133    throws CmsException {
6134
6135        getSubscriptionDriver().markResourceAsVisitedBy(dbc, poolName, resource, user);
6136    }
6137
6138    /**
6139     * Moves a resource.<p>
6140     *
6141     * You must ensure that the parent of the destination path is an absolute, valid and
6142     * existing VFS path. Relative paths from the source are not supported.<p>
6143     *
6144     * The moved resource will always be locked to the current user
6145     * after the move operation.<p>
6146     *
6147     * In case the target resource already exists, it will be overwritten with the
6148     * source resource if possible.<p>
6149     *
6150     * @param dbc the current database context
6151     * @param source the resource to move
6152     * @param destination the name of the move destination with complete path
6153     * @param internal if set nothing more than the path is modified
6154     *
6155     * @throws CmsException if something goes wrong
6156     *
6157     * @see CmsSecurityManager#moveResource(CmsRequestContext, CmsResource, String)
6158     */
6159    public void moveResource(CmsDbContext dbc, CmsResource source, String destination, boolean internal)
6160    throws CmsException {
6161
6162        CmsFolder destinationFolder = readFolder(dbc, CmsResource.getParentFolder(destination), CmsResourceFilter.ALL);
6163        m_securityManager.checkPermissions(
6164            dbc,
6165            destinationFolder,
6166            CmsPermissionSet.ACCESS_WRITE,
6167            false,
6168            CmsResourceFilter.ALL);
6169
6170        if (source.isFolder()) {
6171            m_monitor.flushCache(CmsMemoryMonitor.CacheType.HAS_ROLE, CmsMemoryMonitor.CacheType.ROLE_LIST);
6172        }
6173        getVfsDriver(dbc).moveResource(dbc, dbc.getRequestContext().getCurrentProject().getUuid(), source, destination);
6174
6175        if (!internal) {
6176            CmsResourceState newState = CmsResource.STATE_CHANGED;
6177            if (source.getState().isNew()) {
6178                newState = CmsResource.STATE_NEW;
6179            } else if (source.getState().isDeleted()) {
6180                newState = CmsResource.STATE_DELETED;
6181            }
6182            source.setState(newState);
6183            // safe since this operation always uses the ids instead of the resource path
6184            getVfsDriver(dbc).writeResourceState(
6185                dbc,
6186                dbc.currentProject(),
6187                source,
6188                CmsDriverManager.UPDATE_STRUCTURE_STATE,
6189                false);
6190            // log it
6191            log(
6192                dbc,
6193                new CmsLogEntry(
6194                    dbc,
6195                    source.getStructureId(),
6196                    CmsLogEntryType.RESOURCE_MOVED,
6197                    new String[] {source.getRootPath(), destination}),
6198                false);
6199        }
6200
6201        CmsResource destRes = readResource(dbc, destination, CmsResourceFilter.ALL);
6202        // move lock
6203        m_lockManager.moveResource(source.getRootPath(), destRes.getRootPath());
6204
6205        // flush all relevant caches
6206        m_monitor.clearAccessControlListCache();
6207        m_monitor.flushCache(
6208            CmsMemoryMonitor.CacheType.PROPERTY,
6209            CmsMemoryMonitor.CacheType.PROPERTY_LIST,
6210            CmsMemoryMonitor.CacheType.PROJECT_RESOURCES);
6211
6212        List<CmsResource> resources = new ArrayList<CmsResource>(4);
6213        // source
6214        resources.add(source);
6215        try {
6216            resources.add(readFolder(dbc, CmsResource.getParentFolder(source.getRootPath()), CmsResourceFilter.ALL));
6217        } catch (Exception e) {
6218            if (LOG.isDebugEnabled()) {
6219                LOG.debug(e.getLocalizedMessage(), e);
6220            }
6221        }
6222        // destination
6223        resources.add(destRes);
6224        resources.add(destinationFolder);
6225
6226        Map<String, Object> eventData = new HashMap<String, Object>();
6227        eventData.put(I_CmsEventListener.KEY_RESOURCES, resources);
6228        eventData.put(I_CmsEventListener.KEY_DBCONTEXT, dbc);
6229
6230        // fire the events
6231        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_MOVED, eventData));
6232    }
6233
6234    /**
6235     * Moves a resource to the "lost and found" folder.<p>
6236     *
6237     * The method can also be used to check get the name of a resource
6238     * in the "lost and found" folder only without actually moving the
6239     * the resource. To do this, the <code>returnNameOnly</code> flag
6240     * must be set to <code>true</code>.<p>
6241     *
6242     * @param dbc the current database context
6243     * @param resource the resource to apply this operation to
6244     * @param returnNameOnly if <code>true</code>, only the name of the resource in the "lost and found"
6245     *        folder is returned, the move operation is not really performed
6246     *
6247     * @return the name of the resource inside the "lost and found" folder
6248     *
6249     * @throws CmsException if something goes wrong
6250     * @throws CmsIllegalArgumentException if the <code>resourcename</code> argument is null or of length 0
6251     *
6252     * @see CmsObject#moveToLostAndFound(String)
6253     * @see CmsObject#getLostAndFoundName(String)
6254     */
6255    public String moveToLostAndFound(CmsDbContext dbc, CmsResource resource, boolean returnNameOnly)
6256    throws CmsException, CmsIllegalArgumentException {
6257
6258        String resourcename = dbc.removeSiteRoot(resource.getRootPath());
6259
6260        String siteRoot = dbc.getRequestContext().getSiteRoot();
6261        dbc.getRequestContext().setSiteRoot("");
6262        String destination = CmsDriverManager.LOST_AND_FOUND_FOLDER + resourcename;
6263        // create the required folders if necessary
6264        try {
6265            // collect all folders...
6266            String folderPath = CmsResource.getParentFolder(destination);
6267            folderPath = folderPath.substring(1, folderPath.length() - 1); // cut out leading and trailing '/'
6268            Iterator<String> folders = CmsStringUtil.splitAsList(folderPath, '/').iterator();
6269            // ...now create them....
6270            folderPath = "/";
6271            while (folders.hasNext()) {
6272                folderPath += folders.next().toString() + "/";
6273                try {
6274                    readFolder(dbc, folderPath, CmsResourceFilter.IGNORE_EXPIRATION);
6275                } catch (Exception e1) {
6276                    if (returnNameOnly) {
6277                        // we can use the original name without risk, and we do not need to recreate the parent folders
6278                        break;
6279                    }
6280                    // the folder is not existing, so create it
6281                    createResource(
6282                        dbc,
6283                        folderPath,
6284                        CmsResourceTypeFolder.RESOURCE_TYPE_ID,
6285                        null,
6286                        new ArrayList<CmsProperty>());
6287                }
6288            }
6289            // check if this resource name does already exist
6290            // if so add a postfix to the name
6291            String des = destination;
6292            int postfix = 1;
6293            boolean found = true;
6294            while (found) {
6295                try {
6296                    // try to read the file.....
6297                    found = true;
6298                    readResource(dbc, des, CmsResourceFilter.ALL);
6299                    // ....it's there, so add a postfix and try again
6300                    String path = destination.substring(0, destination.lastIndexOf('/') + 1);
6301                    String filename = destination.substring(destination.lastIndexOf('/') + 1, destination.length());
6302
6303                    des = path;
6304
6305                    if (filename.lastIndexOf('.') > 0) {
6306                        des += filename.substring(0, filename.lastIndexOf('.'));
6307                    } else {
6308                        des += filename;
6309                    }
6310                    des += "_" + postfix;
6311                    if (filename.lastIndexOf('.') > 0) {
6312                        des += filename.substring(filename.lastIndexOf('.'), filename.length());
6313                    }
6314                    postfix++;
6315                } catch (CmsException e3) {
6316                    // the file does not exist, so we can use this filename
6317                    found = false;
6318                }
6319            }
6320            destination = des;
6321
6322            if (!returnNameOnly) {
6323                // do not use the move semantic here! to prevent links pointing to the lost & found folder
6324                copyResource(dbc, resource, destination, CmsResource.COPY_AS_SIBLING);
6325                deleteResource(dbc, resource, CmsResource.DELETE_PRESERVE_SIBLINGS);
6326            }
6327        } catch (CmsException e2) {
6328            throw e2;
6329        } finally {
6330            // set the site root to the old value again
6331            dbc.getRequestContext().setSiteRoot(siteRoot);
6332        }
6333        return destination;
6334    }
6335
6336    /**
6337     * Gets a new driver instance.<p>
6338     *
6339     * @param dbc the database context
6340     * @param configurationManager the configuration manager
6341     * @param driverName the driver name
6342     * @param successiveDrivers the list of successive drivers
6343     *
6344     * @return the driver object
6345     * @throws CmsInitException if the selected driver could not be initialized
6346     */
6347    public Object newDriverInstance(
6348        CmsDbContext dbc,
6349        CmsConfigurationManager configurationManager,
6350        String driverName,
6351        List<String> successiveDrivers)
6352    throws CmsInitException {
6353
6354        Class<?> driverClass = null;
6355        I_CmsDriver driver = null;
6356
6357        try {
6358            // try to get the class
6359            driverClass = Class.forName(driverName);
6360            if (CmsLog.INIT.isInfoEnabled()) {
6361                CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_DRIVER_START_1, driverName));
6362            }
6363
6364            // try to create a instance
6365            driver = (I_CmsDriver)driverClass.newInstance();
6366            if (CmsLog.INIT.isInfoEnabled()) {
6367                CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_DRIVER_INITIALIZING_1, driverName));
6368            }
6369
6370            // invoke the init-method of this access class
6371            driver.init(dbc, configurationManager, successiveDrivers, this);
6372            if (CmsLog.INIT.isInfoEnabled()) {
6373                CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_DRIVER_INIT_FINISHED_0));
6374            }
6375
6376        } catch (Throwable t) {
6377            CmsMessageContainer message = Messages.get().container(
6378                Messages.ERR_ERROR_INITIALIZING_DRIVER_1,
6379                driverName);
6380            if (LOG.isErrorEnabled()) {
6381                LOG.error(message.key(), t);
6382            }
6383            throw new CmsInitException(message, t);
6384        }
6385
6386        return driver;
6387    }
6388
6389    /**
6390     * Method to create a new instance of a driver.<p>
6391     *
6392     * @param configuration the configurations from the propertyfile
6393     * @param driverName the class name of the driver
6394     * @param driverPoolUrl the pool url for the driver
6395     * @return an initialized instance of the driver
6396     * @throws CmsException if something goes wrong
6397     */
6398    public Object newDriverInstance(CmsParameterConfiguration configuration, String driverName, String driverPoolUrl)
6399    throws CmsException {
6400
6401        Class<?>[] initParamClasses = {CmsParameterConfiguration.class, String.class, CmsDriverManager.class};
6402        Object[] initParams = {configuration, driverPoolUrl, this};
6403
6404        Class<?> driverClass = null;
6405        Object driver = null;
6406
6407        try {
6408            // try to get the class
6409            driverClass = Class.forName(driverName);
6410            if (CmsLog.INIT.isInfoEnabled()) {
6411                CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_DRIVER_START_1, driverName));
6412            }
6413
6414            // try to create a instance
6415            driver = driverClass.newInstance();
6416            if (CmsLog.INIT.isInfoEnabled()) {
6417                CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_DRIVER_INITIALIZING_1, driverName));
6418            }
6419
6420            // invoke the init-method of this access class
6421            driver.getClass().getMethod("init", initParamClasses).invoke(driver, initParams);
6422            if (CmsLog.INIT.isInfoEnabled()) {
6423                CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_DRIVER_INIT_FINISHED_1, driverPoolUrl));
6424            }
6425
6426        } catch (Exception exc) {
6427
6428            CmsMessageContainer message = Messages.get().container(Messages.ERR_INIT_DRIVER_MANAGER_1);
6429            if (LOG.isFatalEnabled()) {
6430                LOG.fatal(message.key(), exc);
6431            }
6432            throw new CmsDbException(message, exc);
6433
6434        }
6435
6436        return driver;
6437    }
6438
6439    /**
6440     * Method to create a new instance of a pool.<p>
6441     *
6442     * @param configuration the configurations from the propertyfile
6443     * @param poolName the configuration name of the pool
6444     *
6445     * @throws CmsInitException if the pools could not be initialized
6446     */
6447    public void newPoolInstance(CmsParameterConfiguration configuration, String poolName) throws CmsInitException {
6448
6449        CmsDbPoolV11 pool;
6450
6451        try {
6452            pool = new CmsDbPoolV11(configuration, poolName);
6453        } catch (Exception e) {
6454
6455            CmsMessageContainer message = Messages.get().container(Messages.ERR_INIT_CONN_POOL_1, poolName);
6456            if (LOG.isErrorEnabled()) {
6457                LOG.error(message.key(), e);
6458            }
6459            throw new CmsInitException(message, e);
6460        }
6461        addPool(pool);
6462    }
6463
6464    /**
6465     * Publishes the given publish job.<p>
6466     *
6467     * @param cms the cms context
6468     * @param dbc the db context
6469     * @param publishList the list of resources to publish
6470     * @param report the report to write to
6471     *
6472     * @throws CmsException if something goes wrong
6473     */
6474    public void publishJob(CmsObject cms, CmsDbContext dbc, CmsPublishList publishList, I_CmsReport report)
6475    throws CmsException {
6476
6477        try {
6478            // check state and lock
6479            List<CmsResource> allResources = new ArrayList<CmsResource>(publishList.getFolderList());
6480            allResources.addAll(publishList.getDeletedFolderList());
6481            allResources.addAll(publishList.getFileList());
6482            Iterator<CmsResource> itResources = allResources.iterator();
6483            while (itResources.hasNext()) {
6484                CmsResource resource = itResources.next();
6485                try {
6486                    resource = readResource(dbc, resource.getStructureId(), CmsResourceFilter.ALL);
6487                } catch (CmsVfsResourceNotFoundException e) {
6488                    continue;
6489                }
6490                if (resource.getState().isUnchanged()) {
6491                    // remove files that were published by a concurrent job
6492                    if (LOG.isDebugEnabled()) {
6493                        LOG.debug(
6494                            Messages.get().getBundle().key(
6495                                Messages.RPT_PUBLISH_REMOVED_RESOURCE_1,
6496                                dbc.removeSiteRoot(resource.getRootPath())));
6497                    }
6498                    publishList.remove(resource);
6499                    unlockResource(dbc, resource, true, true);
6500                    continue;
6501                }
6502                CmsLock lock = m_lockManager.getLock(dbc, resource, false);
6503                if (!lock.getSystemLock().isPublish()) {
6504                    // remove files that are not locked for publishing
6505                    if (LOG.isDebugEnabled()) {
6506                        LOG.debug(
6507                            Messages.get().getBundle().key(
6508                                Messages.RPT_PUBLISH_REMOVED_RESOURCE_1,
6509                                dbc.removeSiteRoot(resource.getRootPath())));
6510                    }
6511                    publishList.remove(resource);
6512                    continue;
6513                }
6514            }
6515
6516            CmsProject onlineProject = readProject(dbc, CmsProject.ONLINE_PROJECT_ID);
6517
6518            // clear the cache
6519            m_monitor.clearCacheForPublishing();
6520
6521            int publishTag = getNextPublishTag(dbc);
6522            getProjectDriver(dbc).publishProject(dbc, report, onlineProject, publishList, publishTag);
6523
6524            // iterate the initialized module action instances
6525            Iterator<String> i = OpenCms.getModuleManager().getModuleNames().iterator();
6526            while (i.hasNext()) {
6527                CmsModule module = OpenCms.getModuleManager().getModule(i.next());
6528                if ((module != null) && (module.getActionInstance() != null)) {
6529                    module.getActionInstance().publishProject(cms, publishList, publishTag, report);
6530                }
6531            }
6532
6533            boolean temporaryProject = (cms.getRequestContext().getCurrentProject().getType() == CmsProject.PROJECT_TYPE_TEMPORARY);
6534            // the project was stored in the history tables for history
6535            // it will be deleted if the project_flag is PROJECT_TYPE_TEMPORARY
6536            if ((temporaryProject) && (!publishList.isDirectPublish())) {
6537                try {
6538                    getProjectDriver(dbc).deleteProject(dbc, dbc.currentProject());
6539                } catch (CmsException e) {
6540                    LOG.error(
6541                        Messages.get().getBundle().key(
6542                            Messages.LOG_DELETE_TEMP_PROJECT_FAILED_1,
6543                            cms.getRequestContext().getCurrentProject().getName()));
6544                }
6545                // if project was temporary set context to online project
6546                cms.getRequestContext().setCurrentProject(onlineProject);
6547            }
6548        } finally {
6549            // clear the cache again
6550            m_monitor.clearCacheForPublishing();
6551        }
6552    }
6553
6554    /**
6555     * Publishes the resources of a specified publish list.<p>
6556     *
6557     * @param cms the current request context
6558     * @param dbc the current database context
6559     * @param publishList a publish list
6560     * @param report an instance of <code>{@link I_CmsReport}</code> to print messages
6561     *
6562     * @throws CmsException if something goes wrong
6563     *
6564     * @see #fillPublishList(CmsDbContext, CmsPublishList)
6565     */
6566    public synchronized void publishProject(
6567        CmsObject cms,
6568        CmsDbContext dbc,
6569        CmsPublishList publishList,
6570        I_CmsReport report)
6571    throws CmsException {
6572
6573        // check the parent folders
6574        checkParentFolders(dbc, publishList);
6575        ensureSubResourcesOfMovedFoldersPublished(cms, dbc, publishList);
6576        OpenCms.getPublishManager().getPublishListVerifier().checkPublishList(publishList);
6577
6578        try {
6579            // fire an event that a project is to be published
6580            Map<String, Object> eventData = new HashMap<String, Object>();
6581            eventData.put(I_CmsEventListener.KEY_REPORT, report);
6582            eventData.put(I_CmsEventListener.KEY_PUBLISHLIST, publishList);
6583            eventData.put(I_CmsEventListener.KEY_PROJECTID, dbc.currentProject().getUuid());
6584            eventData.put(I_CmsEventListener.KEY_DBCONTEXT, dbc);
6585            CmsEvent beforePublishEvent = new CmsEvent(I_CmsEventListener.EVENT_BEFORE_PUBLISH_PROJECT, eventData);
6586            OpenCms.fireCmsEvent(beforePublishEvent);
6587        } catch (Throwable t) {
6588            if (report != null) {
6589                report.addError(t);
6590                report.println(t);
6591            }
6592            if (LOG.isErrorEnabled()) {
6593                LOG.error(t.getLocalizedMessage(), t);
6594            }
6595        }
6596
6597        // lock all resources with the special publish lock
6598        Iterator<CmsResource> itResources = new ArrayList<CmsResource>(publishList.getAllResources()).iterator();
6599        while (itResources.hasNext()) {
6600            CmsResource resource = itResources.next();
6601            CmsLock lock = m_lockManager.getLock(dbc, resource, false);
6602            if (lock.getSystemLock().isUnlocked() && lock.isLockableBy(dbc.currentUser())) {
6603                if (getLock(dbc, resource).getEditionLock().isNullLock()) {
6604                    lockResource(dbc, resource, CmsLockType.PUBLISH);
6605                } else {
6606                    changeLock(dbc, resource, CmsLockType.PUBLISH);
6607                }
6608            } else if (lock.getSystemLock().isPublish()) {
6609                if (LOG.isWarnEnabled()) {
6610                    LOG.warn(
6611                        Messages.get().getBundle().key(
6612                            Messages.RPT_PUBLISH_REMOVED_RESOURCE_1,
6613                            dbc.removeSiteRoot(resource.getRootPath())));
6614                }
6615                // remove files that are already waiting to be published
6616                publishList.remove(resource);
6617                continue;
6618            } else {
6619                // this is needed to fix TestPublishIsssues#testPublishScenarioE
6620                changeLock(dbc, resource, CmsLockType.PUBLISH);
6621            }
6622            // now re-check the lock state
6623            lock = m_lockManager.getLock(dbc, resource, false);
6624            if (!lock.getSystemLock().isPublish()) {
6625                if (report != null) {
6626                    report.println(
6627                        Messages.get().container(
6628                            Messages.RPT_PUBLISH_REMOVED_RESOURCE_1,
6629                            dbc.removeSiteRoot(resource.getRootPath())),
6630                        I_CmsReport.FORMAT_WARNING);
6631                }
6632                if (LOG.isWarnEnabled()) {
6633                    LOG.warn(
6634                        Messages.get().getBundle().key(
6635                            Messages.RPT_PUBLISH_REMOVED_RESOURCE_1,
6636                            dbc.removeSiteRoot(resource.getRootPath())));
6637                }
6638                // remove files that could not be locked
6639                publishList.remove(resource);
6640            }
6641        }
6642
6643        // enqueue the publish job
6644        CmsException enqueueException = null;
6645        try {
6646            m_publishEngine.enqueuePublishJob(cms, publishList, report);
6647        } catch (CmsException exc) {
6648            enqueueException = exc;
6649        }
6650
6651        // if an exception was raised, remove the publish locks
6652        // and throw the exception again
6653        if (enqueueException != null) {
6654            itResources = publishList.getAllResources().iterator();
6655            while (itResources.hasNext()) {
6656                CmsResource resource = itResources.next();
6657                CmsLock lock = m_lockManager.getLock(dbc, resource, false);
6658                if (lock.getSystemLock().isPublish()
6659                    && lock.getSystemLock().isOwnedInProjectBy(
6660                        cms.getRequestContext().getCurrentUser(),
6661                        cms.getRequestContext().getCurrentProject())) {
6662                    unlockResource(dbc, resource, true, true);
6663                }
6664            }
6665
6666            throw enqueueException;
6667        }
6668    }
6669
6670    /**
6671     * Transfers the new URL name mappings (if any) for a given resource to the online project.<p>
6672     *
6673     * @param dbc the current database context
6674     * @param res the resource whose new URL name mappings should be transferred to the online project
6675     *
6676     * @throws CmsDataAccessException if something goes wrong
6677     */
6678    public void publishUrlNameMapping(CmsDbContext dbc, CmsResource res) throws CmsDataAccessException {
6679
6680        I_CmsVfsDriver vfsDriver = getVfsDriver(dbc);
6681
6682        if (res.getState().isDeleted()) {
6683            // remove both offline and online mappings
6684            CmsUrlNameMappingFilter idFilter = CmsUrlNameMappingFilter.ALL.filterStructureId(res.getStructureId());
6685            vfsDriver.deleteUrlNameMappingEntries(dbc, true, idFilter);
6686            vfsDriver.deleteUrlNameMappingEntries(dbc, false, idFilter);
6687        } else {
6688            // copy the new entries to the online table
6689            List<CmsUrlNameMappingEntry> entries = vfsDriver.readUrlNameMappingEntries(
6690                dbc,
6691                false,
6692                CmsUrlNameMappingFilter.ALL.filterStructureId(res.getStructureId()).filterStates(
6693                    CmsUrlNameMappingEntry.MAPPING_STATUS_NEW,
6694                    CmsUrlNameMappingEntry.MAPPING_STATUS_REPLACE_ON_PUBLISH));
6695
6696            boolean isReplaceOnPublish = false;
6697            for (CmsUrlNameMappingEntry entry : entries) {
6698                isReplaceOnPublish |= entry.getState() == CmsUrlNameMappingEntry.MAPPING_STATUS_REPLACE_ON_PUBLISH;
6699            }
6700
6701            if (!entries.isEmpty()) {
6702
6703                long now = System.currentTimeMillis();
6704                if (isReplaceOnPublish) {
6705                    vfsDriver.deleteUrlNameMappingEntries(
6706                        dbc,
6707                        true,
6708                        CmsUrlNameMappingFilter.ALL.filterStructureId(res.getStructureId()));
6709                    vfsDriver.deleteUrlNameMappingEntries(
6710                        dbc,
6711                        false,
6712                        CmsUrlNameMappingFilter.ALL.filterStructureId(res.getStructureId()));
6713                }
6714
6715                for (CmsUrlNameMappingEntry entry : entries) {
6716                    CmsUrlNameMappingFilter nameFilter = CmsUrlNameMappingFilter.ALL.filterName(entry.getName());
6717                    if (!isReplaceOnPublish) { // we already handled the other case above
6718                        vfsDriver.deleteUrlNameMappingEntries(dbc, true, nameFilter);
6719                        vfsDriver.deleteUrlNameMappingEntries(dbc, false, nameFilter);
6720                    }
6721                }
6722                for (CmsUrlNameMappingEntry entry : entries) {
6723                    CmsUrlNameMappingEntry newEntry = new CmsUrlNameMappingEntry(
6724                        entry.getName(),
6725                        entry.getStructureId(),
6726                        entry.getState() == CmsUrlNameMappingEntry.MAPPING_STATUS_NEW
6727                        ? CmsUrlNameMappingEntry.MAPPING_STATUS_PUBLISHED
6728                        : CmsUrlNameMappingEntry.MAPPING_STATUS_REPLACE_ON_PUBLISH_PUBLISHED,
6729                        now,
6730                        entry.getLocale());
6731                    vfsDriver.addUrlNameMappingEntry(dbc, true, newEntry);
6732                    vfsDriver.addUrlNameMappingEntry(dbc, false, newEntry);
6733                }
6734            }
6735        }
6736    }
6737
6738    /**
6739     * Reads an access control entry from the cms.<p>
6740     *
6741     * The access control entries of a resource are readable by everyone.
6742     *
6743     * @param dbc the current database context
6744     * @param resource the resource
6745     * @param principal the id of a group or a user any other entity
6746     * @return an access control entry that defines the permissions of the entity for the given resource
6747     * @throws CmsException if something goes wrong
6748     */
6749    public CmsAccessControlEntry readAccessControlEntry(CmsDbContext dbc, CmsResource resource, CmsUUID principal)
6750    throws CmsException {
6751
6752        return getUserDriver(
6753            dbc).readAccessControlEntry(dbc, dbc.currentProject(), resource.getResourceId(), principal);
6754    }
6755
6756    /**
6757     * Finds the alias with a given path.<p>
6758     *
6759     * If no alias is found, null is returned.<p>
6760     *
6761     * @param dbc the current database context
6762     * @param project the current project
6763     * @param siteRoot the site root
6764     * @param path the path of the alias
6765     *
6766     * @return the alias with the given path
6767     *
6768     * @throws CmsException if something goes wrong
6769     */
6770
6771    public CmsAlias readAliasByPath(CmsDbContext dbc, CmsProject project, String siteRoot, String path)
6772    throws CmsException {
6773
6774        List<CmsAlias> aliases = getVfsDriver(dbc).readAliases(dbc, project, new CmsAliasFilter(siteRoot, path, null));
6775        if (aliases.isEmpty()) {
6776            return null;
6777        } else {
6778            return aliases.get(0);
6779        }
6780    }
6781
6782    /**
6783     * Reads the aliases for a given site root.<p>
6784     *
6785     * @param dbc the current database context
6786     * @param currentProject the current project
6787     * @param siteRoot the site root
6788     *
6789     * @return the list of aliases for the given site root
6790     *
6791     * @throws CmsException if something goes wrong
6792     */
6793    public List<CmsAlias> readAliasesBySite(CmsDbContext dbc, CmsProject currentProject, String siteRoot)
6794    throws CmsException {
6795
6796        return getVfsDriver(dbc).readAliases(dbc, currentProject, new CmsAliasFilter(siteRoot, null, null));
6797    }
6798
6799    /**
6800     * Reads the aliases which point to a given structure id.<p>
6801     *
6802     * @param dbc the current database context
6803     * @param project the current project
6804     * @param structureId the structure id for which we want to read the aliases
6805     *
6806     * @return the list of aliases pointing to the structure id
6807     * @throws CmsException if something goes wrong
6808     */
6809    public List<CmsAlias> readAliasesByStructureId(CmsDbContext dbc, CmsProject project, CmsUUID structureId)
6810    throws CmsException {
6811
6812        return getVfsDriver(dbc).readAliases(dbc, project, new CmsAliasFilter(null, null, structureId));
6813    }
6814
6815    /**
6816     * Reads all versions of the given resource.<br>
6817     *
6818     * This method returns a list with the history of the given resource, i.e.
6819     * the historical resource entries, independent of the project they were attached to.<br>
6820     *
6821     * The reading excludes the file content.<p>
6822     *
6823     * @param dbc the current database context
6824     * @param resource the resource to read the history for
6825     *
6826     * @return a list of file headers, as <code>{@link I_CmsHistoryResource}</code> objects
6827     *
6828     * @throws CmsException if something goes wrong
6829     */
6830    public List<I_CmsHistoryResource> readAllAvailableVersions(CmsDbContext dbc, CmsResource resource)
6831    throws CmsException {
6832
6833        // read the historical resources
6834        List<I_CmsHistoryResource> versions = getHistoryDriver(dbc).readAllAvailableVersions(
6835            dbc,
6836            resource.getStructureId());
6837        if ((versions.size() > OpenCms.getSystemInfo().getHistoryVersions())
6838            && (OpenCms.getSystemInfo().getHistoryVersions() > -1)) {
6839            return versions.subList(0, OpenCms.getSystemInfo().getHistoryVersions());
6840        }
6841        return versions;
6842    }
6843
6844    /**
6845     * Reads all property definitions for the given mapping type.<p>
6846     *
6847     * @param dbc the current database context
6848     *
6849     * @return a list with the <code>{@link CmsPropertyDefinition}</code> objects (may be empty)
6850     *
6851     * @throws CmsException if something goes wrong
6852     */
6853    public List<CmsPropertyDefinition> readAllPropertyDefinitions(CmsDbContext dbc) throws CmsException {
6854
6855        List<CmsPropertyDefinition> result = getVfsDriver(dbc).readPropertyDefinitions(
6856            dbc,
6857            dbc.currentProject().getUuid());
6858        Collections.sort(result);
6859        return result;
6860    }
6861
6862    /**
6863     * Returns all resources subscribed by the given user or group.<p>
6864     *
6865     * @param dbc the database context
6866     * @param poolName the name of the database pool to use
6867     * @param principal the principal to read the subscribed resources
6868     *
6869     * @return all resources subscribed by the given user or group
6870     *
6871     * @throws CmsException if something goes wrong
6872     */
6873    public List<CmsResource> readAllSubscribedResources(CmsDbContext dbc, String poolName, CmsPrincipal principal)
6874    throws CmsException {
6875
6876        List<CmsResource> result = getSubscriptionDriver().readAllSubscribedResources(dbc, poolName, principal);
6877        result = filterPermissions(dbc, result, CmsResourceFilter.DEFAULT);
6878        return result;
6879    }
6880
6881    /**
6882     * Selects the best url name for a given resource and locale.<p>
6883     *
6884     * @param dbc the database context
6885     * @param id the resource's structure id
6886     * @param locale the requested locale
6887     * @param defaultLocales the default locales to use if the locale isn't available
6888     *
6889     * @return the URL name which was found
6890     *
6891     * @throws CmsDataAccessException if the database operation failed
6892     */
6893    public String readBestUrlName(CmsDbContext dbc, CmsUUID id, Locale locale, List<Locale> defaultLocales)
6894    throws CmsDataAccessException {
6895
6896        List<CmsUrlNameMappingEntry> entries = getVfsDriver(dbc).readUrlNameMappingEntries(
6897            dbc,
6898            dbc.currentProject().isOnlineProject(),
6899            CmsUrlNameMappingFilter.ALL.filterStructureId(id));
6900        if (entries.isEmpty()) {
6901            return null;
6902        }
6903
6904        ArrayListMultimap<String, CmsUrlNameMappingEntry> entriesByLocale = ArrayListMultimap.create();
6905        for (CmsUrlNameMappingEntry entry : entries) {
6906            entriesByLocale.put(entry.getLocale(), entry);
6907        }
6908        List<CmsUrlNameMappingEntry> lastEntries = new ArrayList<CmsUrlNameMappingEntry>();
6909        Comparator<CmsUrlNameMappingEntry> dateChangedComparator = new UrlNameMappingComparator();
6910        for (String localeKey : entriesByLocale.keySet()) {
6911            // for each locale select the latest mapping entry
6912            CmsUrlNameMappingEntry latestEntryForLocale = Collections.max(
6913                entriesByLocale.get(localeKey),
6914                dateChangedComparator);
6915            lastEntries.add(latestEntryForLocale);
6916        }
6917        CmsLocaleManager localeManager = OpenCms.getLocaleManager();
6918        List<Locale> availableLocales = new ArrayList<Locale>();
6919        for (CmsUrlNameMappingEntry entry : lastEntries) {
6920            availableLocales.add(CmsLocaleManager.getLocale(entry.getLocale()));
6921        }
6922        Locale bestLocale = localeManager.getBestMatchingLocale(locale, defaultLocales, availableLocales);
6923        String bestLocaleStr = bestLocale.toString();
6924        for (CmsUrlNameMappingEntry entry : lastEntries) {
6925            if (entry.getLocale().equals(bestLocaleStr)) {
6926                return entry.getName();
6927            }
6928        }
6929        return null;
6930    }
6931
6932    /**
6933     * Returns the child resources of a resource, that is the resources
6934     * contained in a folder.<p>
6935     *
6936     * With the parameters <code>getFolders</code> and <code>getFiles</code>
6937     * you can control what type of resources you want in the result list:
6938     * files, folders, or both.<p>
6939     *
6940     * This method is mainly used by the workplace explorer.<p>
6941     *
6942     * @param dbc the current database context
6943     * @param resource the resource to return the child resources for
6944     * @param filter the resource filter to use
6945     * @param getFolders if true the child folders are included in the result
6946     * @param getFiles if true the child files are included in the result
6947     * @param checkPermissions if the resources should be filtered with the current user permissions
6948     *
6949     * @return a list of all child resources
6950     *
6951     * @throws CmsException if something goes wrong
6952     */
6953    public List<CmsResource> readChildResources(
6954        CmsDbContext dbc,
6955        CmsResource resource,
6956        CmsResourceFilter filter,
6957        boolean getFolders,
6958        boolean getFiles,
6959        boolean checkPermissions)
6960    throws CmsException {
6961
6962        String cacheKey = null;
6963        List<CmsResource> resourceList = null;
6964        if (m_monitor.isEnabled(CmsMemoryMonitor.CacheType.RESOURCE_LIST)) { // check this here to skip the complex cache key generation
6965            String time = "";
6966            if (checkPermissions) {
6967                // ensure correct caching if site time offset is set
6968                if ((dbc.getRequestContext() != null)
6969                    && (OpenCms.getSiteManager().getSiteForSiteRoot(dbc.getRequestContext().getSiteRoot()) != null)) {
6970                    time += OpenCms.getSiteManager().getSiteForSiteRoot(
6971                        dbc.getRequestContext().getSiteRoot()).getSiteMatcher().getTimeOffset();
6972                }
6973            }
6974            // try to get the sub resources from the cache
6975            cacheKey = getCacheKey(
6976                new String[] {
6977                    dbc.currentUser().getName(),
6978                    getFolders
6979                    ? (getFiles ? CmsCacheKey.CACHE_KEY_SUBALL : CmsCacheKey.CACHE_KEY_SUBFOLDERS)
6980                    : CmsCacheKey.CACHE_KEY_SUBFILES,
6981                    checkPermissions ? "+" + time : "-",
6982                    filter.getCacheId(),
6983                    resource.getRootPath()},
6984                dbc);
6985
6986            resourceList = m_monitor.getCachedResourceList(cacheKey);
6987        }
6988        if ((resourceList == null) || !dbc.getProjectId().isNullUUID()) {
6989            // read the result form the database
6990            resourceList = getVfsDriver(
6991                dbc).readChildResources(dbc, dbc.currentProject(), resource, getFolders, getFiles);
6992
6993            if (checkPermissions) {
6994                // apply the permission filter
6995                resourceList = filterPermissions(dbc, resourceList, filter);
6996            }
6997            // cache the sub resources
6998            if (dbc.getProjectId().isNullUUID()) {
6999                m_monitor.cacheResourceList(cacheKey, resourceList);
7000            }
7001        }
7002
7003        // we must always apply the result filter and update the context dates
7004        return updateContextDates(dbc, resourceList, filter);
7005    }
7006
7007    /**
7008     * Returns the default file for the given folder.<p>
7009     *
7010     * If the given resource is a file, then this file is returned.<p>
7011     *
7012     * Otherwise, in case of a folder:<br>
7013     * <ol>
7014     *   <li>the {@link CmsPropertyDefinition#PROPERTY_DEFAULT_FILE} is checked, and
7015     *   <li>if still no file could be found, the configured default files in the
7016     *       <code>opencms-vfs.xml</code> configuration are iterated until a match is
7017     *       found, and
7018     *   <li>if still no file could be found, <code>null</code> is retuned
7019     * </ol>
7020     *
7021     * @param dbc the database context
7022     * @param resource the folder to get the default file for
7023     * @param resourceFilter the resource filter
7024     *
7025     * @return the default file for the given folder
7026     */
7027    public CmsResource readDefaultFile(CmsDbContext dbc, CmsResource resource, CmsResourceFilter resourceFilter) {
7028
7029        // resource exists, lets check if we have a file or a folder
7030        if (resource.isFolder()) {
7031            // the resource is a folder, check if PROPERTY_DEFAULT_FILE is set on folder
7032            try {
7033                String defaultFileName = readPropertyObject(
7034                    dbc,
7035                    resource,
7036                    CmsPropertyDefinition.PROPERTY_DEFAULT_FILE,
7037                    false).getValue();
7038                // check if the default file property does not match the navigation level folder marker value
7039                if ((defaultFileName != null) && !CmsJspNavBuilder.NAVIGATION_LEVEL_FOLDER.equals(defaultFileName)) {
7040                    // property was set, so look up this file first
7041                    String folderName = CmsResource.getFolderPath(resource.getRootPath());
7042                    resource = readResource(dbc, folderName + defaultFileName, resourceFilter.addRequireFile());
7043                }
7044            } catch (CmsException e) {
7045                // ignore all other exceptions and continue the lookup process
7046                if (LOG.isDebugEnabled()) {
7047                    LOG.debug(e.getLocalizedMessage(), e);
7048                }
7049            }
7050            if (resource.isFolder()) {
7051                String folderName = CmsResource.getFolderPath(resource.getRootPath());
7052                // resource is (still) a folder, check default files specified in configuration
7053                Iterator<String> it = OpenCms.getDefaultFiles().iterator();
7054                while (it.hasNext()) {
7055                    String tmpResourceName = folderName + it.next();
7056                    try {
7057                        resource = readResource(dbc, tmpResourceName, resourceFilter.addRequireFile());
7058                        // no exception? So we have found the default file
7059                        // stop looking for default files
7060                        break;
7061                    } catch (CmsException e) {
7062                        // ignore all other exceptions and continue the lookup process
7063                        if (LOG.isDebugEnabled()) {
7064                            LOG.debug(e.getLocalizedMessage(), e);
7065                        }
7066                    }
7067                }
7068            }
7069        }
7070        if (resource.isFolder()) {
7071            // we only want files as a result for further processing
7072            resource = null;
7073        }
7074        return resource;
7075    }
7076
7077    /**
7078     * Reads all deleted (historical) resources below the given path,
7079     * including the full tree below the path, if required.<p>
7080     *
7081     * @param dbc the current db context
7082     * @param resource the parent resource to read the resources from
7083     * @param readTree <code>true</code> to read all subresources
7084     * @param isVfsManager <code>true</code> if the current user has the vfs manager role
7085     *
7086     * @return a list of <code>{@link I_CmsHistoryResource}</code> objects
7087     *
7088     * @throws CmsException if something goes wrong
7089     *
7090     * @see CmsObject#readResource(CmsUUID, int)
7091     * @see CmsObject#readResources(String, CmsResourceFilter, boolean)
7092     * @see CmsObject#readDeletedResources(String, boolean)
7093     */
7094    public List<I_CmsHistoryResource> readDeletedResources(
7095        CmsDbContext dbc,
7096        CmsResource resource,
7097        boolean readTree,
7098        boolean isVfsManager)
7099    throws CmsException {
7100
7101        Set<I_CmsHistoryResource> result = new HashSet<I_CmsHistoryResource>();
7102        List<I_CmsHistoryResource> deletedResources;
7103        dbc.getRequestContext().setAttribute("ATTR_RESOURCE_NAME", resource.getRootPath());
7104        try {
7105            deletedResources = getHistoryDriver(dbc).readDeletedResources(
7106                dbc,
7107                resource.getStructureId(),
7108                isVfsManager ? null : dbc.currentUser().getId());
7109        } finally {
7110            dbc.getRequestContext().removeAttribute("ATTR_RESOURCE_NAME");
7111        }
7112        result.addAll(deletedResources);
7113        Set<I_CmsHistoryResource> newResult = new HashSet<I_CmsHistoryResource>(result.size());
7114        I_CmsVfsDriver vfsDriver = getVfsDriver(dbc);
7115        Iterator<I_CmsHistoryResource> it = result.iterator();
7116        while (it.hasNext()) {
7117            I_CmsHistoryResource histRes = it.next();
7118            // adjust the paths
7119            try {
7120                if (vfsDriver.validateStructureIdExists(
7121                    dbc,
7122                    dbc.currentProject().getUuid(),
7123                    histRes.getStructureId())) {
7124                    newResult.add(histRes);
7125                    continue;
7126                }
7127                // adjust the path in case of deleted files
7128                String resourcePath = histRes.getRootPath();
7129                String resName = CmsResource.getName(resourcePath);
7130                String path = CmsResource.getParentFolder(resourcePath);
7131
7132                CmsUUID parentId = histRes.getParentId();
7133                try {
7134                    // first look for the path through the parent id
7135                    path = readResource(dbc, parentId, CmsResourceFilter.IGNORE_EXPIRATION).getRootPath();
7136                } catch (CmsDataAccessException e) {
7137                    // if the resource with the parent id is not found, try to get a new parent id with the path
7138                    try {
7139                        parentId = readResource(dbc, path, CmsResourceFilter.IGNORE_EXPIRATION).getStructureId();
7140                    } catch (CmsDataAccessException e1) {
7141                        // ignore, the parent folder has been completely deleted
7142                    }
7143                }
7144                resourcePath = path + resName;
7145
7146                boolean isFolder = resourcePath.endsWith("/");
7147                if (isFolder) {
7148                    newResult.add(
7149                        new CmsHistoryFolder(
7150                            histRes.getPublishTag(),
7151                            histRes.getStructureId(),
7152                            histRes.getResourceId(),
7153                            resourcePath,
7154                            histRes.getTypeId(),
7155                            histRes.getFlags(),
7156                            histRes.getProjectLastModified(),
7157                            histRes.getState(),
7158                            histRes.getDateCreated(),
7159                            histRes.getUserCreated(),
7160                            histRes.getDateLastModified(),
7161                            histRes.getUserLastModified(),
7162                            histRes.getDateReleased(),
7163                            histRes.getDateExpired(),
7164                            histRes.getVersion(),
7165                            parentId,
7166                            histRes.getResourceVersion(),
7167                            histRes.getStructureVersion()));
7168                } else {
7169                    newResult.add(
7170                        new CmsHistoryFile(
7171                            histRes.getPublishTag(),
7172                            histRes.getStructureId(),
7173                            histRes.getResourceId(),
7174                            resourcePath,
7175                            histRes.getTypeId(),
7176                            histRes.getFlags(),
7177                            histRes.getProjectLastModified(),
7178                            histRes.getState(),
7179                            histRes.getDateCreated(),
7180                            histRes.getUserCreated(),
7181                            histRes.getDateLastModified(),
7182                            histRes.getUserLastModified(),
7183                            histRes.getDateReleased(),
7184                            histRes.getDateExpired(),
7185                            histRes.getLength(),
7186                            histRes.getDateContent(),
7187                            histRes.getVersion(),
7188                            parentId,
7189                            null,
7190                            histRes.getResourceVersion(),
7191                            histRes.getStructureVersion()));
7192                }
7193            } catch (CmsDataAccessException e) {
7194                // should never happen
7195                if (LOG.isErrorEnabled()) {
7196                    LOG.error(e.getLocalizedMessage(), e);
7197                }
7198            }
7199        }
7200        if (readTree) {
7201            Iterator<I_CmsHistoryResource> itDeleted = deletedResources.iterator();
7202            while (itDeleted.hasNext()) {
7203                I_CmsHistoryResource delResource = itDeleted.next();
7204                if (delResource.isFolder()) {
7205                    newResult.addAll(readDeletedResources(dbc, (CmsFolder)delResource, readTree, isVfsManager));
7206                }
7207            }
7208            try {
7209                readResource(dbc, resource.getStructureId(), CmsResourceFilter.ALL);
7210                // resource exists, so recurse
7211                Iterator<CmsResource> itResources = readResources(
7212                    dbc,
7213                    resource,
7214                    CmsResourceFilter.ALL.addRequireFolder(),
7215                    readTree).iterator();
7216                while (itResources.hasNext()) {
7217                    CmsResource subResource = itResources.next();
7218                    if (subResource.isFolder()) {
7219                        newResult.addAll(readDeletedResources(dbc, subResource, readTree, isVfsManager));
7220                    }
7221                }
7222            } catch (Exception e) {
7223                // resource does not exists
7224                if (LOG.isDebugEnabled()) {
7225                    LOG.debug(e.getLocalizedMessage(), e);
7226                }
7227            }
7228        }
7229        List<I_CmsHistoryResource> finalRes = new ArrayList<I_CmsHistoryResource>(newResult);
7230        Collections.sort(finalRes, I_CmsResource.COMPARE_ROOT_PATH);
7231        return finalRes;
7232    }
7233
7234    /**
7235     * Reads a file resource (including it's binary content) from the VFS,
7236     * using the specified resource filter.<p>
7237     *
7238     * In case you do not need the file content,
7239     * use <code>{@link #readResource(CmsDbContext, String, CmsResourceFilter)}</code> instead.<p>
7240     *
7241     * The specified filter controls what kind of resources should be "found"
7242     * during the read operation. This will depend on the application. For example,
7243     * using <code>{@link CmsResourceFilter#DEFAULT}</code> will only return currently
7244     * "valid" resources, while using <code>{@link CmsResourceFilter#IGNORE_EXPIRATION}</code>
7245     * will ignore the date release / date expired information of the resource.<p>
7246     *
7247     * @param dbc the current database context
7248     * @param resource the base file resource (without content)
7249     * @return the file read from the VFS
7250     * @throws CmsException if operation was not successful
7251     */
7252    public CmsFile readFile(CmsDbContext dbc, CmsResource resource) throws CmsException {
7253
7254        if (resource.isFolder()) {
7255            throw new CmsVfsResourceNotFoundException(
7256                Messages.get().container(
7257                    Messages.ERR_ACCESS_FOLDER_AS_FILE_1,
7258                    dbc.removeSiteRoot(resource.getRootPath())));
7259        }
7260
7261        CmsUUID projectId = dbc.currentProject().getUuid();
7262        CmsFile file = null;
7263        if (resource instanceof I_CmsHistoryResource) {
7264            file = new CmsHistoryFile((I_CmsHistoryResource)resource);
7265            file.setContents(
7266                getHistoryDriver(dbc).readContent(
7267                    dbc,
7268                    resource.getResourceId(),
7269                    ((I_CmsHistoryResource)resource).getPublishTag()));
7270        } else {
7271            file = new CmsFile(resource);
7272            file.setContents(getVfsDriver(dbc).readContent(dbc, projectId, resource.getResourceId()));
7273        }
7274        return file;
7275    }
7276
7277    /**
7278     * Reads a folder from the VFS,
7279     * using the specified resource filter.<p>
7280     *
7281     * @param dbc the current database context
7282     * @param resourcename the name of the folder to read (full path)
7283     * @param filter the resource filter to use while reading
7284     *
7285     * @return the folder that was read
7286     *
7287     * @throws CmsDataAccessException if something goes wrong
7288     *
7289     * @see #readResource(CmsDbContext, String, CmsResourceFilter)
7290     * @see CmsObject#readFolder(String)
7291     * @see CmsObject#readFolder(String, CmsResourceFilter)
7292     */
7293    public CmsFolder readFolder(CmsDbContext dbc, String resourcename, CmsResourceFilter filter)
7294    throws CmsDataAccessException {
7295
7296        CmsResource resource = readResource(dbc, resourcename, filter);
7297
7298        return convertResourceToFolder(resource);
7299    }
7300
7301    /**
7302     * Reads the group of a project.<p>
7303     *
7304     * @param dbc the current database context
7305     * @param project the project to read from
7306     *
7307     * @return the group of a resource
7308     */
7309    public CmsGroup readGroup(CmsDbContext dbc, CmsProject project) {
7310
7311        try {
7312            return readGroup(dbc, project.getGroupId());
7313        } catch (CmsException exc) {
7314            return new CmsGroup(
7315                CmsUUID.getNullUUID(),
7316                CmsUUID.getNullUUID(),
7317                project.getGroupId() + "",
7318                "deleted group",
7319                0);
7320        }
7321    }
7322
7323    /**
7324     * Reads a group based on its id.<p>
7325     *
7326     * @param dbc the current database context
7327     * @param groupId the id of the group that is to be read
7328     *
7329     * @return the requested group
7330     *
7331     * @throws CmsException if operation was not successful
7332     */
7333    public CmsGroup readGroup(CmsDbContext dbc, CmsUUID groupId) throws CmsException {
7334
7335        CmsGroup group = null;
7336        // try to read group from cache
7337        group = m_monitor.getCachedGroup(groupId.toString());
7338        if (group == null) {
7339            group = getUserDriver(dbc).readGroup(dbc, groupId);
7340            m_monitor.cacheGroup(group);
7341        }
7342        return group;
7343    }
7344
7345    /**
7346     * Reads a group based on its name.<p>
7347     *
7348     * @param dbc the current database context
7349     * @param groupname the name of the group that is to be read
7350     *
7351     * @return the requested group
7352     *
7353     * @throws CmsDataAccessException if operation was not successful
7354     */
7355    public CmsGroup readGroup(CmsDbContext dbc, String groupname) throws CmsDataAccessException {
7356
7357        CmsGroup group = null;
7358        // try to read group from cache
7359        group = m_monitor.getCachedGroup(groupname);
7360        if (group == null) {
7361            group = getUserDriver(dbc).readGroup(dbc, groupname);
7362            m_monitor.cacheGroup(group);
7363        }
7364        return group;
7365    }
7366
7367    /**
7368     * Reads a principal (an user or group) from the historical archive based on its ID.<p>
7369     *
7370     * @param dbc the current database context
7371     * @param principalId the id of the principal to read
7372     *
7373     * @return the historical principal entry with the given id
7374     *
7375     * @throws CmsException if something goes wrong, ie. {@link CmsDbEntryNotFoundException}
7376     *
7377     * @see CmsObject#readUser(CmsUUID)
7378     * @see CmsObject#readGroup(CmsUUID)
7379     * @see CmsObject#readHistoryPrincipal(CmsUUID)
7380     */
7381    public CmsHistoryPrincipal readHistoricalPrincipal(CmsDbContext dbc, CmsUUID principalId) throws CmsException {
7382
7383        return getHistoryDriver(dbc).readPrincipal(dbc, principalId);
7384    }
7385
7386    /**
7387     * Returns the latest historical project entry with the given id.<p>
7388     *
7389     * @param dbc the current database context
7390     * @param projectId the project id
7391     *
7392     * @return the requested historical project entry
7393     *
7394     * @throws CmsException if something goes wrong
7395     */
7396    public CmsHistoryProject readHistoryProject(CmsDbContext dbc, CmsUUID projectId) throws CmsException {
7397
7398        return getHistoryDriver(dbc).readProject(dbc, projectId);
7399    }
7400
7401    /**
7402     * Returns a historical project entry.<p>
7403     *
7404     * @param dbc the current database context
7405     * @param publishTag the publish tag of the project
7406     *
7407     * @return the requested historical project entry
7408     *
7409     * @throws CmsException if something goes wrong
7410     */
7411    public CmsHistoryProject readHistoryProject(CmsDbContext dbc, int publishTag) throws CmsException {
7412
7413        return getHistoryDriver(dbc).readProject(dbc, publishTag);
7414    }
7415
7416    /**
7417     * Reads the list of all <code>{@link CmsProperty}</code> objects that belongs to the given historical resource.<p>
7418     *
7419     * @param dbc the current database context
7420     * @param historyResource the historical resource to read the properties for
7421     *
7422     * @return the list of <code>{@link CmsProperty}</code> objects
7423     *
7424     * @throws CmsException if something goes wrong
7425     */
7426    public List<CmsProperty> readHistoryPropertyObjects(CmsDbContext dbc, I_CmsHistoryResource historyResource)
7427    throws CmsException {
7428
7429        return getHistoryDriver(dbc).readProperties(dbc, historyResource);
7430    }
7431
7432    /**
7433     * Reads the structure id which is mapped to a given URL name.<p>
7434     *
7435     * @param dbc the current database context
7436     * @param name the name for which the mapped structure id should be looked up
7437     *
7438     * @return the structure id which is mapped to the given name, or null if there is no such id
7439     *
7440     * @throws CmsDataAccessException if something goes wrong
7441     */
7442    public CmsUUID readIdForUrlName(CmsDbContext dbc, String name) throws CmsDataAccessException {
7443
7444        List<CmsUrlNameMappingEntry> entries = getVfsDriver(dbc).readUrlNameMappingEntries(
7445            dbc,
7446            dbc.currentProject().isOnlineProject(),
7447            CmsUrlNameMappingFilter.ALL.filterName(name));
7448        if (entries.isEmpty()) {
7449            return null;
7450        }
7451        return entries.get(0).getStructureId();
7452    }
7453
7454    /**
7455     * Reads the locks that were saved to the database in the previous run of OpenCms.<p>
7456     *
7457     * @param dbc the current database context
7458     *
7459     * @throws CmsException if something goes wrong
7460     */
7461    public void readLocks(CmsDbContext dbc) throws CmsException {
7462
7463        m_lockManager.readLocks(dbc);
7464    }
7465
7466    /**
7467     * Reads the manager group of a project.<p>
7468     *
7469     * @param dbc the current database context
7470     * @param project the project to read from
7471     *
7472     * @return the group of a resource
7473     */
7474    public CmsGroup readManagerGroup(CmsDbContext dbc, CmsProject project) {
7475
7476        try {
7477            return readGroup(dbc, project.getManagerGroupId());
7478        } catch (CmsException exc) {
7479            // the group does not exist any more - return a dummy-group
7480            return new CmsGroup(
7481                CmsUUID.getNullUUID(),
7482                CmsUUID.getNullUUID(),
7483                project.getManagerGroupId() + "",
7484                "deleted group",
7485                0);
7486        }
7487    }
7488
7489    /**
7490     * Reads the URL name which has been most recently mapped to the given structure id, or null
7491     * if no URL name is mapped to the id.<p>
7492     *
7493     * @param dbc the current database context
7494     * @param id a structure id
7495     * @return the name which has been most recently mapped to the given structure id
7496     *
7497     * @throws CmsDataAccessException if something goes wrong
7498     */
7499    public String readNewestUrlNameForId(CmsDbContext dbc, CmsUUID id) throws CmsDataAccessException {
7500
7501        List<CmsUrlNameMappingEntry> entries = getVfsDriver(dbc).readUrlNameMappingEntries(
7502            dbc,
7503            dbc.currentProject().isOnlineProject(),
7504            CmsUrlNameMappingFilter.ALL.filterStructureId(id));
7505        if (entries.isEmpty()) {
7506            return null;
7507        }
7508
7509        Collections.sort(entries, new UrlNameMappingComparator());
7510        CmsUrlNameMappingEntry lastEntry = entries.get(entries.size() - 1);
7511        return lastEntry.getName();
7512    }
7513
7514    /**
7515     * Reads an organizational Unit based on its fully qualified name.<p>
7516     *
7517     * @param dbc the current db context
7518     * @param ouFqn the fully qualified name of the organizational Unit to be read
7519     *
7520     * @return the organizational Unit that with the provided fully qualified name
7521     *
7522     * @throws CmsException if something goes wrong
7523     */
7524    public CmsOrganizationalUnit readOrganizationalUnit(CmsDbContext dbc, String ouFqn) throws CmsException {
7525
7526        CmsOrganizationalUnit organizationalUnit = null;
7527        // try to read organizational unit from cache
7528        organizationalUnit = m_monitor.getCachedOrgUnit(ouFqn);
7529        if (organizationalUnit == null) {
7530            organizationalUnit = getUserDriver(dbc).readOrganizationalUnit(dbc, ouFqn);
7531            m_monitor.cacheOrgUnit(organizationalUnit);
7532        }
7533        return organizationalUnit;
7534    }
7535
7536    /**
7537     * Reads the owner of a project.<p>
7538     *
7539     * @param dbc the current database context
7540     * @param project the project to get the owner from
7541     *
7542     * @return the owner of a resource
7543     * @throws CmsException if something goes wrong
7544     */
7545    public CmsUser readOwner(CmsDbContext dbc, CmsProject project) throws CmsException {
7546
7547        return readUser(dbc, project.getOwnerId());
7548    }
7549
7550    /**
7551     * Reads the parent folder to a given structure id.<p>
7552     *
7553     * @param dbc the current database context
7554     * @param structureId the structure id of the child
7555     *
7556     * @return the parent folder resource
7557     *
7558     * @throws CmsDataAccessException if something goes wrong
7559     */
7560    public CmsResource readParentFolder(CmsDbContext dbc, CmsUUID structureId) throws CmsDataAccessException {
7561
7562        return getVfsDriver(dbc).readParentFolder(dbc, dbc.currentProject().getUuid(), structureId);
7563    }
7564
7565    /**
7566     * Builds a list of resources for a given path.<p>
7567     *
7568     * @param dbc the current database context
7569     * @param path the requested path
7570     * @param filter a filter object (only "includeDeleted" information is used!)
7571     *
7572     * @return list of <code>{@link CmsResource}</code>s
7573     *
7574     * @throws CmsException if something goes wrong
7575     */
7576    public List<CmsResource> readPath(CmsDbContext dbc, String path, CmsResourceFilter filter) throws CmsException {
7577
7578        // splits the path into folder and filename tokens
7579        List<String> tokens = CmsStringUtil.splitAsList(path, '/');
7580
7581        // the root folder is no token in the path but a resource which has to be added to the path
7582        int count = tokens.size() + 1;
7583        // holds the CmsResource instances in the path
7584        List<CmsResource> pathList = new ArrayList<CmsResource>(count);
7585
7586        // true if the path doesn't end with a folder
7587        boolean lastResourceIsFile = false;
7588        // number of folders in the path
7589        int folderCount = count;
7590        if (!path.endsWith("/")) {
7591            folderCount--;
7592            lastResourceIsFile = true;
7593        }
7594
7595        // read the root folder, because it's ID is required to read any sub-resources
7596        String currentResourceName = "/";
7597        StringBuffer currentPath = new StringBuffer(64);
7598        currentPath.append('/');
7599
7600        String cp = currentPath.toString();
7601        CmsUUID projectId = getProjectIdForContext(dbc);
7602
7603        // key to cache the resources
7604        String cacheKey = getCacheKey(null, false, projectId, cp);
7605        // the current resource
7606        CmsResource currentResource = m_monitor.getCachedResource(cacheKey);
7607        if ((currentResource == null) || !dbc.getProjectId().isNullUUID()) {
7608            currentResource = getVfsDriver(dbc).readFolder(dbc, projectId, cp);
7609            if (dbc.getProjectId().isNullUUID()) {
7610                m_monitor.cacheResource(cacheKey, currentResource);
7611            }
7612        }
7613
7614        pathList.add(0, currentResource);
7615
7616        if (count == 1) {
7617            // the root folder was requested- no further operations required
7618            return pathList;
7619        }
7620
7621        Iterator<String> it = tokens.iterator();
7622        currentResourceName = it.next();
7623
7624        // read the folder resources in the path /a/b/c/
7625        int i = 0;
7626        for (i = 1; i < folderCount; i++) {
7627            currentPath.append(currentResourceName);
7628            currentPath.append('/');
7629            // read the folder
7630            cp = currentPath.toString();
7631            cacheKey = getCacheKey(null, false, projectId, cp);
7632            currentResource = m_monitor.getCachedResource(cacheKey);
7633            if ((currentResource == null) || !dbc.getProjectId().isNullUUID()) {
7634                currentResource = getVfsDriver(dbc).readFolder(dbc, projectId, cp);
7635                if (dbc.getProjectId().isNullUUID()) {
7636                    m_monitor.cacheResource(cacheKey, currentResource);
7637                }
7638            }
7639
7640            pathList.add(i, currentResource);
7641
7642            if (i < (folderCount - 1)) {
7643                currentResourceName = it.next();
7644            }
7645        }
7646
7647        // read the (optional) last file resource in the path /x.html
7648        if (lastResourceIsFile) {
7649            if (it.hasNext()) {
7650                // this will only be false if a resource in the
7651                // top level root folder (e.g. "/index.html") was requested
7652                currentResourceName = it.next();
7653            }
7654            currentPath.append(currentResourceName);
7655
7656            // read the file
7657            cp = currentPath.toString();
7658            cacheKey = getCacheKey(null, false, projectId, cp);
7659            currentResource = m_monitor.getCachedResource(cacheKey);
7660            if ((currentResource == null) || !dbc.getProjectId().isNullUUID()) {
7661                currentResource = getVfsDriver(dbc).readResource(dbc, projectId, cp, filter.includeDeleted());
7662                if (dbc.getProjectId().isNullUUID()) {
7663                    m_monitor.cacheResource(cacheKey, currentResource);
7664                }
7665            }
7666
7667            pathList.add(i, currentResource);
7668        }
7669
7670        return pathList;
7671    }
7672
7673    /**
7674     * Reads a project given the projects id.<p>
7675     *
7676     * @param dbc the current database context
7677     * @param id the id of the project
7678     *
7679     * @return the project read
7680     *
7681     * @throws CmsDataAccessException if something goes wrong
7682     */
7683    public CmsProject readProject(CmsDbContext dbc, CmsUUID id) throws CmsDataAccessException {
7684
7685        CmsProject project = null;
7686        project = m_monitor.getCachedProject(id.toString());
7687        if (project == null) {
7688            project = getProjectDriver(dbc).readProject(dbc, id);
7689            m_monitor.cacheProject(project);
7690        }
7691        return project;
7692    }
7693
7694    /**
7695     * Reads a project.<p>
7696     *
7697     * Important: Since a project name can be used multiple times, this is NOT the most efficient
7698     * way to read the project. This is only a convenience for front end developing.
7699     * Reading a project by name will return the first project with that name.
7700     * All core classes must use the id version {@link #readProject(CmsDbContext, CmsUUID)} to ensure the right project is read.<p>
7701     *
7702     * @param dbc the current database context
7703     * @param name the name of the project
7704     *
7705     * @return the project read
7706     *
7707     * @throws CmsException if something goes wrong
7708     */
7709    public CmsProject readProject(CmsDbContext dbc, String name) throws CmsException {
7710
7711        CmsProject project = null;
7712        project = m_monitor.getCachedProject(name);
7713        if (project == null) {
7714            project = getProjectDriver(dbc).readProject(dbc, name);
7715            m_monitor.cacheProject(project);
7716        }
7717        return project;
7718    }
7719
7720    /**
7721     * Returns the list of all resource names that define the "view" of the given project.<p>
7722     *
7723     * @param dbc the current database context
7724     * @param project the project to get the project resources for
7725     *
7726     * @return the list of all resources, as <code>{@link String}</code> objects
7727     *              that define the "view" of the given project.
7728     *
7729     * @throws CmsException if something goes wrong
7730     */
7731    public List<String> readProjectResources(CmsDbContext dbc, CmsProject project) throws CmsException {
7732
7733        return getProjectDriver(dbc).readProjectResources(dbc, project);
7734    }
7735
7736    /**
7737     * Reads all resources of a project that match a given state from the VFS.<p>
7738     *
7739     * Possible values for the <code>state</code> parameter are:<br>
7740     * <ul>
7741     * <li><code>{@link CmsResource#STATE_CHANGED}</code>: Read all "changed" resources in the project</li>
7742     * <li><code>{@link CmsResource#STATE_NEW}</code>: Read all "new" resources in the project</li>
7743     * <li><code>{@link CmsResource#STATE_DELETED}</code>: Read all "deleted" resources in the project</li>
7744     * <li><code>{@link CmsResource#STATE_KEEP}</code>: Read all resources either "changed", "new" or "deleted" in the project</li>
7745     * </ul><p>
7746     *
7747     * @param dbc the current database context
7748     * @param projectId the id of the project to read the file resources for
7749     * @param state the resource state to match
7750     *
7751     * @return a list of <code>{@link CmsResource}</code> objects matching the filter criteria
7752     *
7753     * @throws CmsException if something goes wrong
7754     *
7755     * @see CmsObject#readProjectView(CmsUUID, CmsResourceState)
7756     */
7757    public List<CmsResource> readProjectView(CmsDbContext dbc, CmsUUID projectId, CmsResourceState state)
7758    throws CmsException {
7759
7760        List<CmsResource> resources;
7761        if (state.isNew() || state.isChanged() || state.isDeleted()) {
7762            // get all resources form the database that match the selected state
7763            resources = getVfsDriver(dbc).readResources(dbc, projectId, state, CmsDriverManager.READMODE_MATCHSTATE);
7764        } else {
7765            // get all resources form the database that are somehow changed (i.e. not unchanged)
7766            resources = getVfsDriver(
7767                dbc).readResources(dbc, projectId, CmsResource.STATE_UNCHANGED, CmsDriverManager.READMODE_UNMATCHSTATE);
7768        }
7769
7770        // filter the permissions
7771        List<CmsResource> result = filterPermissions(dbc, resources, CmsResourceFilter.ALL);
7772        // sort the result
7773        Collections.sort(result);
7774        // set the full resource names
7775        return updateContextDates(dbc, result);
7776    }
7777
7778    /**
7779     * Reads a property definition.<p>
7780     *
7781     * If no property definition with the given name is found,
7782     * <code>null</code> is returned.<p>
7783     *
7784     * @param dbc the current database context
7785     * @param name the name of the property definition to read
7786     *
7787     * @return the property definition that was read
7788     *
7789     * @throws CmsException a CmsDbEntryNotFoundException is thrown if the property definition does not exist
7790     */
7791    public CmsPropertyDefinition readPropertyDefinition(CmsDbContext dbc, String name) throws CmsException {
7792
7793        return getVfsDriver(dbc).readPropertyDefinition(dbc, name, dbc.currentProject().getUuid());
7794    }
7795
7796    /**
7797     * Reads a property object from a resource specified by a property name.<p>
7798     *
7799     * Returns <code>{@link CmsProperty#getNullProperty()}</code> if the property is not found.<p>
7800     *
7801     * @param dbc the current database context
7802     * @param resource the resource where the property is read from
7803     * @param key the property key name
7804     * @param search if <code>true</code>, the property is searched on all parent folders of the resource.
7805     *      if it's not found attached directly to the resource.
7806     *
7807     * @return the required property, or <code>{@link CmsProperty#getNullProperty()}</code> if the property was not found
7808     *
7809     * @throws CmsException if something goes wrong
7810     */
7811    public CmsProperty readPropertyObject(CmsDbContext dbc, CmsResource resource, String key, boolean search)
7812    throws CmsException {
7813
7814        // NOTE: Do not call readPropertyObject(dbc, resource, key, search, null) for performance reasons
7815
7816        // use the list reading method to obtain all properties for the resource
7817        List<CmsProperty> properties = readPropertyObjects(dbc, resource, search);
7818
7819        int i = properties.indexOf(new CmsProperty(key, null, null));
7820        if (i >= 0) {
7821            // property has been found in the map
7822            CmsProperty result = properties.get(i);
7823            // ensure the result value is not frozen
7824            return result.cloneAsProperty();
7825        }
7826        return CmsProperty.getNullProperty();
7827
7828    }
7829
7830    /**
7831     * Reads a property object from a resource specified by a property name.<p>
7832     *
7833     * Returns <code>{@link CmsProperty#getNullProperty()}</code> if the property is not found.<p>
7834     *
7835     * @param dbc the current database context
7836     * @param resource the resource where the property is read from
7837     * @param key the property key name
7838     * @param search if <code>true</code>, the property is searched on all parent folders of the resource.
7839     *      if it's not found attached directly to the resource.
7840     * @param locale the locale for which the property should be read.
7841     *
7842     * @return the required property, or <code>{@link CmsProperty#getNullProperty()}</code> if the property was not found
7843     *
7844     * @throws CmsException if something goes wrong
7845     */
7846    public CmsProperty readPropertyObject(
7847        CmsDbContext dbc,
7848        CmsResource resource,
7849        String key,
7850        boolean search,
7851        Locale locale)
7852    throws CmsException {
7853
7854        // use the list reading method to obtain all properties for the resource
7855        List<CmsProperty> properties = readPropertyObjects(dbc, resource, search);
7856        // create a lookup property object and look this up in the result map
7857        CmsProperty result = null;
7858        // handle the case without locale separately to improve performance
7859        for (String localizedKey : CmsLocaleManager.getLocaleVariants(key, locale, true, false)) {
7860            int i = properties.indexOf(new CmsProperty(localizedKey, null, null));
7861            if (i >= 0) {
7862                // property has been found in the map
7863                result = properties.get(i);
7864                // ensure the result value is not frozen
7865                return result.cloneAsProperty();
7866            }
7867        }
7868        return CmsProperty.getNullProperty();
7869    }
7870
7871    /**
7872     * Reads all property objects mapped to a specified resource from the database.<p>
7873     *
7874     * All properties in the result List will be in frozen (read only) state, so you can't change the values.<p>
7875     *
7876     * Returns an empty list if no properties are found at all.<p>
7877     *
7878     * @param dbc the current database context
7879     * @param resource the resource where the properties are read from
7880     * @param search true, if the properties should be searched on all parent folders  if not found on the resource
7881     *
7882     * @return a list of CmsProperty objects containing the structure and/or resource value
7883     *
7884     * @throws CmsException if something goes wrong
7885     *
7886     * @see CmsObject#readPropertyObjects(String, boolean)
7887     */
7888    public List<CmsProperty> readPropertyObjects(CmsDbContext dbc, CmsResource resource, boolean search)
7889    throws CmsException {
7890
7891        // check if we have the result already cached
7892        CmsUUID projectId = getProjectIdForContext(dbc);
7893        String cacheKey = getCacheKey(CACHE_ALL_PROPERTIES, search, projectId, resource.getRootPath());
7894
7895        List<CmsProperty> properties = m_monitor.getCachedPropertyList(cacheKey);
7896
7897        if ((properties == null) || !dbc.getProjectId().isNullUUID()) {
7898            // result not cached, let's look it up in the DB
7899            if (search) {
7900                boolean cont;
7901                properties = new ArrayList<CmsProperty>();
7902                List<CmsProperty> parentProperties = null;
7903
7904                do {
7905                    try {
7906                        parentProperties = readPropertyObjects(dbc, resource, false);
7907
7908                        // make sure properties from lower folders "overwrite" properties from upper folders
7909                        parentProperties.removeAll(properties);
7910                        parentProperties.addAll(properties);
7911
7912                        properties.clear();
7913                        properties.addAll(parentProperties);
7914
7915                        cont = resource.getRootPath().length() > 1;
7916                    } catch (CmsSecurityException se) {
7917                        // a security exception (probably no read permission) we return the current result
7918                        cont = false;
7919                    }
7920                    if (cont) {
7921                        // no permission check on parent folder is required since we must have "read"
7922                        // permissions to read the child resource anyway
7923                        resource = readResource(
7924                            dbc,
7925                            CmsResource.getParentFolder(resource.getRootPath()),
7926                            CmsResourceFilter.ALL);
7927                    }
7928                } while (cont);
7929            } else {
7930                properties = getVfsDriver(dbc).readPropertyObjects(dbc, dbc.currentProject(), resource);
7931                //                for (CmsProperty prop : properties) {
7932                //                    prop.setOrigin(resource.getRootPath());
7933                //                }
7934            }
7935
7936            // set all properties in the result list as frozen
7937            CmsProperty.setFrozen(properties);
7938            if (dbc.getProjectId().isNullUUID()) {
7939                // store the result in the cache if needed
7940                m_monitor.cachePropertyList(cacheKey, properties);
7941            }
7942        }
7943
7944        return new ArrayList<CmsProperty>(properties);
7945    }
7946
7947    /**
7948     * Reads the resources that were published in a publish task for a given publish history ID.<p>
7949     *
7950     * @param dbc the current database context
7951     * @param publishHistoryId unique int ID to identify each publish task in the publish history
7952     *
7953     * @return a list of <code>{@link org.opencms.db.CmsPublishedResource}</code> objects
7954     *
7955     * @throws CmsException if something goes wrong
7956     */
7957    public List<CmsPublishedResource> readPublishedResources(CmsDbContext dbc, CmsUUID publishHistoryId)
7958    throws CmsException {
7959
7960        String cacheKey = publishHistoryId.toString();
7961        List<CmsPublishedResource> resourceList = m_monitor.getCachedPublishedResources(cacheKey);
7962        if ((resourceList == null) || !dbc.getProjectId().isNullUUID()) {
7963            resourceList = getProjectDriver(dbc).readPublishedResources(dbc, publishHistoryId);
7964            // store the result in the cache
7965            if (dbc.getProjectId().isNullUUID()) {
7966                m_monitor.cachePublishedResources(cacheKey, resourceList);
7967            }
7968        }
7969        return resourceList;
7970    }
7971
7972    /**
7973     * Reads a single publish job identified by its publish history id.<p>
7974     *
7975     * @param dbc the current database context
7976     * @param publishHistoryId unique id to identify the publish job in the publish history
7977     * @return an object of type <code>{@link CmsPublishJobInfoBean}</code>
7978     *
7979     * @throws CmsException if something goes wrong
7980     */
7981    public CmsPublishJobInfoBean readPublishJob(CmsDbContext dbc, CmsUUID publishHistoryId) throws CmsException {
7982
7983        return getProjectDriver(dbc).readPublishJob(dbc, publishHistoryId);
7984    }
7985
7986    /**
7987     * Reads all available publish jobs.<p>
7988     *
7989     * @param dbc the current database context
7990     * @param startTime the start of the time range for finish time
7991     * @param endTime the end of the time range for finish time
7992     * @return a list of objects of type <code>{@link CmsPublishJobInfoBean}</code>
7993     *
7994     * @throws CmsException if something goes wrong
7995     */
7996    public List<CmsPublishJobInfoBean> readPublishJobs(CmsDbContext dbc, long startTime, long endTime)
7997    throws CmsException {
7998
7999        return getProjectDriver(dbc).readPublishJobs(dbc, startTime, endTime);
8000    }
8001
8002    /**
8003     * Reads the publish list assigned to a publish job.<p>
8004     *
8005     * @param dbc the current database context
8006     * @param publishHistoryId the history id identifying the publish job
8007     * @return the assigned publish list
8008     * @throws CmsException if something goes wrong
8009     */
8010    public CmsPublishList readPublishList(CmsDbContext dbc, CmsUUID publishHistoryId) throws CmsException {
8011
8012        return getProjectDriver(dbc).readPublishList(dbc, publishHistoryId);
8013    }
8014
8015    /**
8016     * Reads the publish report assigned to a publish job.<p>
8017     *
8018     * @param dbc the current database context
8019     * @param publishHistoryId the history id identifying the publish job
8020     * @return the content of the assigned publish report
8021     * @throws CmsException if something goes wrong
8022     */
8023    public byte[] readPublishReportContents(CmsDbContext dbc, CmsUUID publishHistoryId) throws CmsException {
8024
8025        return getProjectDriver(dbc).readPublishReportContents(dbc, publishHistoryId);
8026    }
8027
8028    /**
8029     * Reads an historical resource entry for the given resource and with the given version number.<p>
8030     *
8031     * @param dbc the current db context
8032     * @param resource the resource to be read
8033     * @param version the version number to retrieve
8034     *
8035     * @return the resource that was read
8036     *
8037     * @throws CmsException if the resource could not be read for any reason
8038     *
8039     * @see CmsObject#restoreResourceVersion(CmsUUID, int)
8040     * @see CmsObject#readResource(CmsUUID, int)
8041     */
8042    public I_CmsHistoryResource readResource(CmsDbContext dbc, CmsResource resource, int version) throws CmsException {
8043
8044        Iterator<I_CmsHistoryResource> itVersions = getHistoryDriver(dbc).readAllAvailableVersions(
8045            dbc,
8046            resource.getStructureId()).iterator();
8047        while (itVersions.hasNext()) {
8048            I_CmsHistoryResource histRes = itVersions.next();
8049            if (histRes.getVersion() == version) {
8050                return histRes;
8051            }
8052        }
8053        throw new CmsVfsResourceNotFoundException(
8054            org.opencms.db.generic.Messages.get().container(
8055                org.opencms.db.generic.Messages.ERR_HISTORY_FILE_NOT_FOUND_1,
8056                resource.getStructureId()));
8057    }
8058
8059    /**
8060     * Reads a resource from the VFS, using the specified resource filter.<p>
8061     *
8062     * @param dbc the current database context
8063     * @param structureID the structure id of the resource to read
8064     * @param filter the resource filter to use while reading
8065     *
8066     * @return the resource that was read
8067     *
8068     * @throws CmsDataAccessException if something goes wrong
8069     *
8070     * @see CmsObject#readResource(CmsUUID, CmsResourceFilter)
8071     * @see CmsObject#readResource(CmsUUID)
8072     */
8073    public CmsResource readResource(CmsDbContext dbc, CmsUUID structureID, CmsResourceFilter filter)
8074    throws CmsDataAccessException {
8075
8076        CmsUUID projectId = getProjectIdForContext(dbc);
8077        // please note: the filter will be applied in the security manager later
8078        CmsResource resource = getVfsDriver(dbc).readResource(dbc, projectId, structureID, filter.includeDeleted());
8079
8080        // context dates need to be updated
8081        updateContextDates(dbc, resource);
8082
8083        // return the resource
8084        return resource;
8085    }
8086
8087    /**
8088     * Reads a resource from the VFS, using the specified resource filter.<p>
8089     *
8090     * @param dbc the current database context
8091     * @param resourcePath the name of the resource to read (full path)
8092     * @param filter the resource filter to use while reading
8093     *
8094     * @return the resource that was read
8095     *
8096     * @throws CmsDataAccessException if something goes wrong
8097     *
8098     * @see CmsObject#readResource(String, CmsResourceFilter)
8099     * @see CmsObject#readResource(String)
8100     * @see CmsObject#readFile(CmsResource)
8101     */
8102    public CmsResource readResource(CmsDbContext dbc, String resourcePath, CmsResourceFilter filter)
8103    throws CmsDataAccessException {
8104
8105        CmsUUID projectId = getProjectIdForContext(dbc);
8106        // please note: the filter will be applied in the security manager later
8107        CmsResource resource = getVfsDriver(dbc).readResource(dbc, projectId, resourcePath, filter.includeDeleted());
8108
8109        // context dates need to be updated
8110        updateContextDates(dbc, resource);
8111
8112        // return the resource
8113        return resource;
8114    }
8115
8116    /**
8117     * Reads all resources below the given path matching the filter criteria,
8118     * including the full tree below the path only in case the <code>readTree</code>
8119     * parameter is <code>true</code>.<p>
8120     *
8121     * @param dbc the current database context
8122     * @param parent the parent path to read the resources from
8123     * @param filter the filter
8124     * @param readTree <code>true</code> to read all subresources
8125     *
8126     * @return a list of <code>{@link CmsResource}</code> objects matching the filter criteria
8127     *
8128     * @throws CmsDataAccessException if the bare reading of the resources fails
8129     * @throws CmsException if security and permission checks for the resources read fail
8130     */
8131    public List<CmsResource> readResources(
8132        CmsDbContext dbc,
8133        CmsResource parent,
8134        CmsResourceFilter filter,
8135        boolean readTree)
8136    throws CmsException, CmsDataAccessException {
8137
8138        // try to get the sub resources from the cache
8139        String cacheKey = getCacheKey(
8140            new String[] {dbc.currentUser().getName(), filter.getCacheId(), readTree ? "+" : "-", parent.getRootPath()},
8141            dbc);
8142
8143        List<CmsResource> resourceList = m_monitor.getCachedResourceList(cacheKey);
8144        if ((resourceList == null) || !dbc.getProjectId().isNullUUID()) {
8145            // read the result from the database
8146            resourceList = getVfsDriver(dbc).readResourceTree(
8147                dbc,
8148                dbc.currentProject().getUuid(),
8149                (readTree ? parent.getRootPath() : parent.getStructureId().toString()),
8150                filter.getType(),
8151                filter.getState(),
8152                filter.getModifiedAfter(),
8153                filter.getModifiedBefore(),
8154                filter.getReleaseAfter(),
8155                filter.getReleaseBefore(),
8156                filter.getExpireAfter(),
8157                filter.getExpireBefore(),
8158                (readTree ? CmsDriverManager.READMODE_INCLUDE_TREE : CmsDriverManager.READMODE_EXCLUDE_TREE)
8159                    | (filter.excludeType() ? CmsDriverManager.READMODE_EXCLUDE_TYPE : 0)
8160                    | (filter.excludeState() ? CmsDriverManager.READMODE_EXCLUDE_STATE : 0)
8161                    | ((filter.getOnlyFolders() != null)
8162                    ? (filter.getOnlyFolders().booleanValue()
8163                    ? CmsDriverManager.READMODE_ONLY_FOLDERS
8164                    : CmsDriverManager.READMODE_ONLY_FILES)
8165                    : 0));
8166
8167            // HACK: do not take care of permissions if reading organizational units
8168            if (!parent.getRootPath().startsWith("/system/orgunits/")) {
8169                // apply permission filter
8170                resourceList = filterPermissions(dbc, resourceList, filter);
8171            }
8172            // store the result in the resourceList cache
8173            if (dbc.getProjectId().isNullUUID()) {
8174                m_monitor.cacheResourceList(cacheKey, resourceList);
8175            }
8176        }
8177        // we must always apply the result filter and update the context dates
8178        return updateContextDates(dbc, resourceList, filter);
8179    }
8180
8181    /**
8182     * Returns the resources that were visited by a user set in the filter.<p>
8183     *
8184     * @param dbc the database context
8185     * @param poolName the name of the database pool to use
8186     * @param filter the filter that is used to get the visited resources
8187     *
8188     * @return the resources that were visited by a user set in the filter
8189     *
8190     * @throws CmsException if something goes wrong
8191     */
8192    public List<CmsResource> readResourcesVisitedBy(CmsDbContext dbc, String poolName, CmsVisitedByFilter filter)
8193    throws CmsException {
8194
8195        List<CmsResource> result = getSubscriptionDriver().readResourcesVisitedBy(dbc, poolName, filter);
8196        result = filterPermissions(dbc, result, CmsResourceFilter.DEFAULT);
8197        return result;
8198    }
8199
8200    /**
8201     * Reads all resources that have a value (containing the given value string) set
8202     * for the specified property (definition) in the given path.<p>
8203     *
8204     * Both individual and shared properties of a resource are checked.<p>
8205     *
8206     * If the <code>value</code> parameter is <code>null</code>, all resources having the
8207     * given property set are returned.<p>
8208     *
8209     * @param dbc the current database context
8210     * @param folder the folder to get the resources with the property from
8211     * @param propertyDefinition the name of the property (definition) to check for
8212     * @param value the string to search in the value of the property
8213     * @param filter the resource filter to apply to the result set
8214     *
8215     * @return a list of all <code>{@link CmsResource}</code> objects
8216     *          that have a value set for the specified property.
8217     *
8218     * @throws CmsException if something goes wrong
8219     */
8220    public List<CmsResource> readResourcesWithProperty(
8221        CmsDbContext dbc,
8222        CmsResource folder,
8223        String propertyDefinition,
8224        String value,
8225        CmsResourceFilter filter)
8226    throws CmsException {
8227
8228        String cacheKey;
8229        if (value == null) {
8230            cacheKey = getCacheKey(
8231                new String[] {
8232                    dbc.currentUser().getName(),
8233                    folder.getRootPath(),
8234                    propertyDefinition,
8235                    filter.getCacheId()},
8236                dbc);
8237        } else {
8238            cacheKey = getCacheKey(
8239                new String[] {
8240                    dbc.currentUser().getName(),
8241                    folder.getRootPath(),
8242                    propertyDefinition,
8243                    value,
8244                    filter.getCacheId()},
8245                dbc);
8246        }
8247        List<CmsResource> resourceList = m_monitor.getCachedResourceList(cacheKey);
8248        if ((resourceList == null) || !dbc.getProjectId().isNullUUID()) {
8249
8250            CmsPropertyDefinition propDef = null;
8251            try {
8252                // first read the property definition
8253                propDef = readPropertyDefinition(dbc, propertyDefinition);
8254            } catch (CmsDbEntryNotFoundException e) {
8255                LOG.debug(e.getLocalizedMessage(), e);
8256            }
8257            if (propDef != null) {
8258                // now read the list of resources that have a value set for the property definition
8259                resourceList = getVfsDriver(dbc).readResourcesWithProperty(
8260                    dbc,
8261                    dbc.currentProject().getUuid(),
8262                    propDef.getId(),
8263                    folder.getRootPath(),
8264                    value);
8265                // apply permission filter
8266                resourceList = filterPermissions(dbc, resourceList, filter);
8267            } else {
8268                resourceList = new ArrayList<>();
8269            }
8270            // store the result in the resourceList cache
8271            if (dbc.getProjectId().isNullUUID()) {
8272                m_monitor.cacheResourceList(cacheKey, resourceList);
8273            }
8274        }
8275        // we must always apply the result filter and update the context dates
8276        return updateContextDates(dbc, resourceList, filter);
8277    }
8278
8279    /**
8280     * Returns the set of users that are responsible for a specific resource.<p>
8281     *
8282     * @param dbc the current database context
8283     * @param resource the resource to get the responsible users from
8284     *
8285     * @return the set of users that are responsible for a specific resource
8286     *
8287     * @throws CmsException if something goes wrong
8288     */
8289    public Set<I_CmsPrincipal> readResponsiblePrincipals(CmsDbContext dbc, CmsResource resource) throws CmsException {
8290
8291        Set<I_CmsPrincipal> result = new HashSet<I_CmsPrincipal>();
8292        Iterator<CmsAccessControlEntry> aces = getAccessControlEntries(dbc, resource, true).iterator();
8293        while (aces.hasNext()) {
8294            CmsAccessControlEntry ace = aces.next();
8295            if (ace.isResponsible()) {
8296                I_CmsPrincipal p = lookupPrincipal(dbc, ace.getPrincipal());
8297                if (p != null) {
8298                    result.add(p);
8299                }
8300            }
8301        }
8302        return result;
8303    }
8304
8305    /**
8306     * Returns the set of users that are responsible for a specific resource.<p>
8307     *
8308     * @param dbc the current database context
8309     * @param resource the resource to get the responsible users from
8310     *
8311     * @return the set of users that are responsible for a specific resource
8312     *
8313     * @throws CmsException if something goes wrong
8314     */
8315    public Set<CmsUser> readResponsibleUsers(CmsDbContext dbc, CmsResource resource) throws CmsException {
8316
8317        Set<CmsUser> result = new HashSet<CmsUser>();
8318        Iterator<I_CmsPrincipal> principals = readResponsiblePrincipals(dbc, resource).iterator();
8319        while (principals.hasNext()) {
8320            I_CmsPrincipal principal = principals.next();
8321            if (principal.isGroup()) {
8322                try {
8323                    result.addAll(getUsersOfGroup(dbc, principal.getName(), true, false, false));
8324                } catch (CmsException e) {
8325                    if (LOG.isInfoEnabled()) {
8326                        LOG.info(e.getLocalizedMessage(), e);
8327                    }
8328                }
8329            } else {
8330                result.add((CmsUser)principal);
8331            }
8332        }
8333        return result;
8334    }
8335
8336    /**
8337     * Returns a List of all siblings of the specified resource,
8338     * the specified resource being always part of the result set.<p>
8339     *
8340     * The result is a list of <code>{@link CmsResource}</code> objects.<p>
8341     *
8342     * @param dbc the current database context
8343     * @param resource the resource to read the siblings for
8344     * @param filter a filter object
8345     *
8346     * @return a list of <code>{@link CmsResource}</code> Objects that
8347     *          are siblings to the specified resource,
8348     *          including the specified resource itself
8349     *
8350     * @throws CmsException if something goes wrong
8351     */
8352    public List<CmsResource> readSiblings(CmsDbContext dbc, CmsResource resource, CmsResourceFilter filter)
8353    throws CmsException {
8354
8355        List<CmsResource> siblings = getVfsDriver(
8356            dbc).readSiblings(dbc, dbc.currentProject().getUuid(), resource, filter.includeDeleted());
8357
8358        // important: there is no permission check done on the returned list of siblings
8359        // this is because of possible issues with the "publish all siblings" option,
8360        // moreover the user has read permission for the content through
8361        // the selected sibling anyway
8362        return updateContextDates(dbc, siblings, filter);
8363    }
8364
8365    /**
8366     * Returns the parameters of a resource in the table of all published template resources.<p>
8367     *
8368     * @param dbc the current database context
8369     * @param rfsName the rfs name of the resource
8370     *
8371     * @return the parameter string of the requested resource
8372     *
8373     * @throws CmsException if something goes wrong
8374     */
8375    public String readStaticExportPublishedResourceParameters(CmsDbContext dbc, String rfsName) throws CmsException {
8376
8377        return getProjectDriver(dbc).readStaticExportPublishedResourceParameters(dbc, rfsName);
8378    }
8379
8380    /**
8381     * Returns a list of all template resources which must be processed during a static export.<p>
8382     *
8383     * @param dbc the current database context
8384     * @param parameterResources flag for reading resources with parameters (1) or without (0)
8385     * @param timestamp for reading the data from the db
8386     *
8387     * @return a list of template resources as <code>{@link String}</code> objects
8388     *
8389     * @throws CmsException if something goes wrong
8390     */
8391    public List<String> readStaticExportResources(CmsDbContext dbc, int parameterResources, long timestamp)
8392    throws CmsException {
8393
8394        return getProjectDriver(dbc).readStaticExportResources(dbc, parameterResources, timestamp);
8395    }
8396
8397    /**
8398     * Returns the subscribed history resources that were deleted.<p>
8399     *
8400     * @param dbc the database context
8401     * @param poolName the name of the database pool to use
8402     * @param user the user that subscribed to the resource
8403     * @param groups the groups to check subscribed resources for
8404     * @param parent the parent resource (folder) of the deleted resources, if <code>null</code> all deleted resources will be returned
8405     * @param includeSubFolders indicates if the sub folders of the specified folder path should be considered, too
8406     * @param deletedFrom the time stamp from which the resources should have been deleted
8407     *
8408     * @return the subscribed history resources that were deleted
8409     *
8410     * @throws CmsException if something goes wrong
8411     */
8412    public List<I_CmsHistoryResource> readSubscribedDeletedResources(
8413        CmsDbContext dbc,
8414        String poolName,
8415        CmsUser user,
8416        List<CmsGroup> groups,
8417        CmsResource parent,
8418        boolean includeSubFolders,
8419        long deletedFrom)
8420    throws CmsException {
8421
8422        List<I_CmsHistoryResource> result = getSubscriptionDriver().readSubscribedDeletedResources(
8423            dbc,
8424            poolName,
8425            user,
8426            groups,
8427            parent,
8428            includeSubFolders,
8429            deletedFrom);
8430
8431        return result;
8432    }
8433
8434    /**
8435     * Returns the resources that were subscribed by a user or group set in the filter.<p>
8436     *
8437     * @param dbc the database context
8438     * @param poolName the name of the database pool to use
8439     * @param filter the filter that is used to get the subscribed resources
8440     *
8441     * @return the resources that were subscribed by a user or group set in the filter
8442     *
8443     * @throws CmsException if something goes wrong
8444     */
8445    public List<CmsResource> readSubscribedResources(CmsDbContext dbc, String poolName, CmsSubscriptionFilter filter)
8446    throws CmsException {
8447
8448        List<CmsResource> result = getSubscriptionDriver().readSubscribedResources(dbc, poolName, filter);
8449
8450        result = filterPermissions(dbc, result, CmsResourceFilter.DEFAULT);
8451        return result;
8452    }
8453
8454    /**
8455     * Reads URL name mapping entries which match the given filter.<p>
8456     *
8457     * @param dbc the database context
8458     * @param online if true, read online URL name mappings, else offline ones
8459     * @param filter the filter for matching the URL name entries
8460     *
8461     * @return the list of URL name mapping entries which match the given filter
8462     *
8463     * @throws CmsDataAccessException if something goes wrong
8464     */
8465    public List<CmsUrlNameMappingEntry> readUrlNameMappingEntries(
8466        CmsDbContext dbc,
8467        boolean online,
8468        CmsUrlNameMappingFilter filter)
8469    throws CmsDataAccessException {
8470
8471        I_CmsVfsDriver vfsDriver = getVfsDriver(dbc);
8472        return vfsDriver.readUrlNameMappingEntries(dbc, online, filter);
8473    }
8474
8475    /**
8476     * Reads the URL name mappings matching the given filter.<p>
8477     *
8478     * @param dbc the DB context to use
8479     * @param filter the filter used to select the mapping entries
8480     * @return the entries matching the given filter
8481     *
8482     * @throws CmsDataAccessException if something goes wrong
8483     */
8484    public List<CmsUrlNameMappingEntry> readUrlNameMappings(CmsDbContext dbc, CmsUrlNameMappingFilter filter)
8485    throws CmsDataAccessException {
8486
8487        List<CmsUrlNameMappingEntry> entries = getVfsDriver(dbc).readUrlNameMappingEntries(
8488            dbc,
8489            dbc.currentProject().isOnlineProject(),
8490            filter);
8491        return entries;
8492    }
8493
8494    /**
8495     * Reads the newest URL names of a resource for all locales.<p>
8496     *
8497     * @param dbc the database context
8498     * @param id the resource's structure id
8499     *
8500     * @return the url names for the locales
8501     *
8502     * @throws CmsDataAccessException if the database operation failed
8503     */
8504    public List<String> readUrlNamesForAllLocales(CmsDbContext dbc, CmsUUID id) throws CmsDataAccessException {
8505
8506        List<String> result = new ArrayList<String>();
8507        List<CmsUrlNameMappingEntry> entries = getVfsDriver(dbc).readUrlNameMappingEntries(
8508            dbc,
8509            dbc.currentProject().isOnlineProject(),
8510            CmsUrlNameMappingFilter.ALL.filterStructureId(id));
8511        ArrayListMultimap<String, CmsUrlNameMappingEntry> entriesByLocale = ArrayListMultimap.create();
8512        for (CmsUrlNameMappingEntry entry : entries) {
8513            String localeKey = entry.getLocale();
8514            entriesByLocale.put(localeKey, entry);
8515        }
8516
8517        for (String localeKey : entriesByLocale.keySet()) {
8518            List<CmsUrlNameMappingEntry> entrs = entriesByLocale.get(localeKey);
8519            CmsUrlNameMappingEntry maxEntryForLocale = Collections.max(entrs, new UrlNameMappingComparator());
8520            result.add(maxEntryForLocale.getName());
8521        }
8522        return result;
8523    }
8524
8525    /**
8526     * Returns a user object based on the id of a user.<p>
8527     *
8528     * @param dbc the current database context
8529     * @param id the id of the user to read
8530     *
8531     * @return the user read
8532     *
8533     * @throws CmsException if something goes wrong
8534     */
8535    public CmsUser readUser(CmsDbContext dbc, CmsUUID id) throws CmsException {
8536
8537        CmsUser user = m_monitor.getCachedUser(id.toString());
8538        if (user == null) {
8539            user = getUserDriver(dbc).readUser(dbc, id);
8540            m_monitor.cacheUser(user);
8541        }
8542        // important: do not return the cached user object, but a clone to avoid unwanted changes on cached objects
8543        return user.clone();
8544    }
8545
8546    /**
8547     * Returns a user object.<p>
8548     *
8549     * @param dbc the current database context
8550     * @param username the name of the user that is to be read
8551     *
8552     * @return user read
8553     *
8554     * @throws CmsDataAccessException if operation was not successful
8555     */
8556    public CmsUser readUser(CmsDbContext dbc, String username) throws CmsDataAccessException {
8557
8558        CmsUser user = m_monitor.getCachedUser(username);
8559        if (user == null) {
8560            user = getUserDriver(dbc).readUser(dbc, username);
8561            m_monitor.cacheUser(user);
8562        }
8563        // important: do not return the cached user object, but a clone to avoid unwanted changes on cached objects
8564        return user.clone();
8565    }
8566
8567    /**
8568     * Returns a user object if the password for the user is correct.<p>
8569     *
8570     * If the user/pwd pair is not valid a <code>{@link CmsException}</code> is thrown.<p>
8571     *
8572     * @param dbc the current database context
8573     * @param username the username of the user that is to be read
8574     * @param password the password of the user that is to be read
8575     *
8576     * @return user read
8577     *
8578     * @throws CmsException if operation was not successful
8579     */
8580    public CmsUser readUser(CmsDbContext dbc, String username, String password) throws CmsException {
8581
8582        // don't read user from cache here because password may have changed
8583        CmsUser user = getUserDriver(dbc).readUser(dbc, username, password, null);
8584        m_monitor.cacheUser(user);
8585        return user;
8586    }
8587
8588    /**
8589     * Removes an access control entry for a given resource and principal.<p>
8590     *
8591     * @param dbc the current database context
8592     * @param resource the resource
8593     * @param principal the id of the principal to remove the the access control entry for
8594     *
8595     * @throws CmsException if something goes wrong
8596     */
8597    public void removeAccessControlEntry(CmsDbContext dbc, CmsResource resource, CmsUUID principal)
8598    throws CmsException {
8599
8600        // remove the ace
8601        getUserDriver(dbc).removeAccessControlEntry(dbc, dbc.currentProject(), resource.getResourceId(), principal);
8602
8603        // log it
8604        log(
8605            dbc,
8606            new CmsLogEntry(
8607                dbc,
8608                resource.getStructureId(),
8609                CmsLogEntryType.RESOURCE_PERMISSIONS,
8610                new String[] {resource.getRootPath()}),
8611            false);
8612
8613        // update the "last modified" information
8614        setDateLastModified(dbc, resource, resource.getDateLastModified());
8615
8616        // clear the cache
8617        m_monitor.clearAccessControlListCache();
8618
8619        // fire a resource modification event
8620        Map<String, Object> data = new HashMap<String, Object>(2);
8621        data.put(I_CmsEventListener.KEY_RESOURCE, resource);
8622        data.put(I_CmsEventListener.KEY_CHANGE, Integer.valueOf(CHANGED_ACCESSCONTROL));
8623        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_MODIFIED, data));
8624    }
8625
8626    /**
8627     * Removes a resource from the given organizational unit.<p>
8628     *
8629     * @param dbc the current db context
8630     * @param orgUnit the organizational unit to remove the resource from
8631     * @param resource the resource that is to be removed from the organizational unit
8632     *
8633     * @throws CmsException if something goes wrong
8634     *
8635     * @see org.opencms.security.CmsOrgUnitManager#addResourceToOrgUnit(CmsObject, String, String)
8636     * @see org.opencms.security.CmsOrgUnitManager#addResourceToOrgUnit(CmsObject, String, String)
8637     */
8638    public void removeResourceFromOrgUnit(CmsDbContext dbc, CmsOrganizationalUnit orgUnit, CmsResource resource)
8639    throws CmsException {
8640
8641        m_monitor.flushCache(CmsMemoryMonitor.CacheType.HAS_ROLE, CmsMemoryMonitor.CacheType.ROLE_LIST);
8642        getUserDriver(dbc).removeResourceFromOrganizationalUnit(dbc, orgUnit, resource);
8643    }
8644
8645    /**
8646     * Removes a resource from the current project of the user.<p>
8647     *
8648     * @param dbc the current database context
8649     * @param resource the resource to apply this operation to
8650     *
8651     * @throws CmsException if something goes wrong
8652     *
8653     * @see CmsObject#copyResourceToProject(String)
8654     * @see I_CmsResourceType#copyResourceToProject(CmsObject, CmsSecurityManager, CmsResource)
8655     */
8656    public void removeResourceFromProject(CmsDbContext dbc, CmsResource resource) throws CmsException {
8657
8658        // remove the resource to the project only if the resource is already in the project
8659        if (isInsideCurrentProject(dbc, resource.getRootPath())) {
8660            // check if there are already any subfolders of this resource
8661            I_CmsProjectDriver projectDriver = getProjectDriver(dbc);
8662            if (resource.isFolder()) {
8663                List<String> projectResources = projectDriver.readProjectResources(dbc, dbc.currentProject());
8664                for (int i = 0; i < projectResources.size(); i++) {
8665                    String resname = projectResources.get(i);
8666                    if (resname.startsWith(resource.getRootPath())) {
8667                        // delete the existing project resource first
8668                        projectDriver.deleteProjectResource(dbc, dbc.currentProject().getUuid(), resname);
8669                    }
8670                }
8671            }
8672            try {
8673                projectDriver.deleteProjectResource(dbc, dbc.currentProject().getUuid(), resource.getRootPath());
8674            } catch (CmsException exc) {
8675                // if the subfolder exists already - all is ok
8676            } finally {
8677                m_monitor.flushCache(CmsMemoryMonitor.CacheType.PROJECT_RESOURCES);
8678
8679                OpenCms.fireCmsEvent(
8680                    new CmsEvent(
8681                        I_CmsEventListener.EVENT_PROJECT_MODIFIED,
8682                        Collections.<String, Object> singletonMap("project", dbc.currentProject())));
8683            }
8684        }
8685    }
8686
8687    /**
8688     * Removes the given resource to the given user's publish list.<p>
8689     *
8690     * @param dbc the database context
8691     * @param userId the user's id
8692     * @param structureIds the collection of structure IDs to remove
8693     *
8694     * @throws CmsDataAccessException if something goes wrong
8695     */
8696    public void removeResourceFromUsersPubList(CmsDbContext dbc, CmsUUID userId, Collection<CmsUUID> structureIds)
8697    throws CmsDataAccessException {
8698
8699        for (CmsUUID structureId : structureIds) {
8700            CmsLogEntry entry = new CmsLogEntry(
8701                userId,
8702                System.currentTimeMillis(),
8703                structureId,
8704                CmsLogEntryType.RESOURCE_HIDDEN,
8705                new String[] {readResource(dbc, structureId, CmsResourceFilter.ALL).getRootPath()});
8706            log(dbc, entry, true);
8707        }
8708    }
8709
8710    /**
8711     * Removes a user from a group.<p>
8712     *
8713     * @param dbc the current database context
8714     * @param username the name of the user that is to be removed from the group
8715     * @param groupname the name of the group
8716     * @param readRoles if to read roles or groups
8717     *
8718     * @throws CmsException if operation was not successful
8719     * @throws CmsIllegalArgumentException if the given user was not member in the given group
8720     * @throws CmsDbEntryNotFoundException if the given group was not found
8721     * @throws CmsSecurityException if the given user was <b>read as 'null' from the database</b>
8722     *
8723     * @see #addUserToGroup(CmsDbContext, String, String, boolean)
8724     */
8725    public void removeUserFromGroup(CmsDbContext dbc, String username, String groupname, boolean readRoles)
8726    throws CmsException, CmsIllegalArgumentException, CmsDbEntryNotFoundException, CmsSecurityException {
8727
8728        CmsGroup group = readGroup(dbc, groupname);
8729        //check if group exists
8730        if (group == null) {
8731            // the group does not exists
8732            throw new CmsDbEntryNotFoundException(Messages.get().container(Messages.ERR_UNKNOWN_GROUP_1, groupname));
8733        }
8734        if (group.isVirtual() && !readRoles) {
8735            // if removing a user from a virtual role treat it as removing the user from the role
8736            removeUserFromGroup(dbc, username, CmsRole.valueOf(group).getGroupName(), true);
8737            return;
8738        }
8739        if (group.isVirtual()) {
8740            // this is an hack so to prevent a unlimited recursive calls
8741            readRoles = false;
8742        }
8743        if ((readRoles && !group.isRole()) || (!readRoles && group.isRole())) {
8744            // we want a role but we got a group, or the other way
8745            throw new CmsDbEntryNotFoundException(Messages.get().container(Messages.ERR_UNKNOWN_GROUP_1, groupname));
8746        }
8747
8748        boolean skipRemove = false;
8749        // test if this user is existing in the group
8750        if (!userInGroup(dbc, username, groupname, readRoles)) {
8751            if (readRoles) {
8752                // Sometimes users can end up with the default groups corresponding to roles (Administrators, Users) without the actual roles.
8753                // 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
8754                // want to throw an exception then, because it would prevent the code that actually removes the user from the group from running.
8755                LOG.warn(
8756                    "Trying to remove user from role that they are not a member of (user: "
8757                        + username
8758                        + ", group: "
8759                        + groupname
8760                        + ")");
8761                skipRemove = true;
8762            } else {
8763                // user is not in the group, throw exception
8764                throw new CmsIllegalArgumentException(
8765                    Messages.get().container(Messages.ERR_USER_NOT_IN_GROUP_2, username, groupname));
8766            }
8767        }
8768
8769        CmsUser user = readUser(dbc, username);
8770        //check if the user exists
8771        if (user == null) {
8772            // the user does not exists
8773            throw new CmsIllegalArgumentException(
8774                Messages.get().container(Messages.ERR_USER_NOT_IN_GROUP_2, username, groupname));
8775        }
8776
8777        if (readRoles) {
8778            CmsRole role = CmsRole.valueOf(group);
8779            // update virtual groups
8780            Iterator<CmsGroup> it = getVirtualGroupsForRole(dbc, role).iterator();
8781            while (it.hasNext()) {
8782                CmsGroup virtualGroup = it.next();
8783                if (userInGroup(dbc, username, virtualGroup.getName(), false)) {
8784                    // here we say readroles = true, to prevent an unlimited recursive calls
8785                    removeUserFromGroup(dbc, username, virtualGroup.getName(), true);
8786                }
8787            }
8788        }
8789        if (!skipRemove) {
8790            getUserDriver(dbc).deleteUserInGroup(dbc, user.getId(), group.getId());
8791        }
8792
8793        // flush relevant caches
8794        if (readRoles) {
8795            m_monitor.flushCache(CmsMemoryMonitor.CacheType.HAS_ROLE, CmsMemoryMonitor.CacheType.ROLE_LIST);
8796        }
8797        m_monitor.flushUserGroups(user.getId());
8798        m_monitor.flushCache(CmsMemoryMonitor.CacheType.USER_LIST);
8799
8800        if (!dbc.getProjectId().isNullUUID()) {
8801            // user modified event is not needed
8802            return;
8803        }
8804        // fire user modified event
8805        Map<String, Object> eventData = new HashMap<String, Object>();
8806        eventData.put(I_CmsEventListener.KEY_USER_ID, user.getId().toString());
8807        eventData.put(I_CmsEventListener.KEY_USER_NAME, user.getName());
8808        eventData.put(I_CmsEventListener.KEY_GROUP_ID, group.getId().toString());
8809        eventData.put(I_CmsEventListener.KEY_GROUP_NAME, group.getName());
8810        eventData.put(
8811            I_CmsEventListener.KEY_USER_ACTION,
8812            I_CmsEventListener.VALUE_USER_MODIFIED_ACTION_REMOVE_USER_FROM_GROUP);
8813        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_USER_MODIFIED, eventData));
8814
8815    }
8816
8817    /**
8818     * Repairs broken categories.<p>
8819     *
8820     * @param dbc the database context
8821     * @param projectId the project id
8822     * @param resource the resource to repair the categories for
8823     *
8824     * @throws CmsException if something goes wrong
8825     */
8826    public void repairCategories(CmsDbContext dbc, CmsUUID projectId, CmsResource resource) throws CmsException {
8827
8828        CmsObject cms = OpenCms.initCmsObject(new CmsObject(getSecurityManager(), dbc.getRequestContext()));
8829        cms.getRequestContext().setSiteRoot("");
8830        cms.getRequestContext().setCurrentProject(readProject(dbc, projectId));
8831        CmsCategoryService.getInstance().repairRelations(cms, resource);
8832    }
8833
8834    /**
8835     * Replaces the content, type and properties of a resource.<p>
8836     *
8837     * @param dbc the current database context
8838     * @param resource the name of the resource to apply this operation to
8839     * @param type the new type of the resource
8840     * @param content the new content of the resource
8841     * @param properties the new properties of the resource
8842     *
8843     * @throws CmsException if something goes wrong
8844     *
8845     * @see CmsObject#replaceResource(String, int, byte[], List)
8846     * @see I_CmsResourceType#replaceResource(CmsObject, CmsSecurityManager, CmsResource, int, byte[], List)
8847     */
8848    @SuppressWarnings("javadoc")
8849    public void replaceResource(
8850        CmsDbContext dbc,
8851        CmsResource resource,
8852        int type,
8853        byte[] content,
8854        List<CmsProperty> properties)
8855    throws CmsException {
8856
8857        // replace the existing with the new file content
8858        getVfsDriver(dbc).replaceResource(dbc, resource, content, type);
8859
8860        if ((properties != null) && !properties.isEmpty()) {
8861            // write the properties
8862            getVfsDriver(dbc).writePropertyObjects(dbc, dbc.currentProject(), resource, properties);
8863            m_monitor.flushCache(CmsMemoryMonitor.CacheType.PROPERTY, CmsMemoryMonitor.CacheType.PROPERTY_LIST);
8864        }
8865
8866        // update the resource state
8867        if (resource.getState().isUnchanged()) {
8868            resource.setState(CmsResource.STATE_CHANGED);
8869        }
8870        resource.setUserLastModified(dbc.currentUser().getId());
8871
8872        // log it
8873        log(
8874            dbc,
8875            new CmsLogEntry(
8876                dbc,
8877                resource.getStructureId(),
8878                CmsLogEntryType.RESOURCE_CONTENT_MODIFIED,
8879                new String[] {resource.getRootPath()}),
8880            false);
8881
8882        setDateLastModified(dbc, resource, System.currentTimeMillis());
8883
8884        getVfsDriver(dbc).writeResourceState(dbc, dbc.currentProject(), resource, UPDATE_RESOURCE, false);
8885
8886        deleteRelationsWithSiblings(dbc, resource);
8887
8888        // clear the cache
8889        m_monitor.clearResourceCache();
8890
8891        if ((properties != null) && !properties.isEmpty()) {
8892            // resource and properties were modified
8893            OpenCms.fireCmsEvent(
8894                new CmsEvent(
8895                    I_CmsEventListener.EVENT_RESOURCE_AND_PROPERTIES_MODIFIED,
8896                    Collections.<String, Object> singletonMap(I_CmsEventListener.KEY_RESOURCE, resource)));
8897        } else {
8898            // only the resource was modified
8899            Map<String, Object> data = new HashMap<String, Object>(2);
8900            data.put(I_CmsEventListener.KEY_RESOURCE, resource);
8901            data.put(I_CmsEventListener.KEY_CHANGE, Integer.valueOf(CHANGED_RESOURCE | CHANGED_CONTENT));
8902            OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_MODIFIED, data));
8903        }
8904    }
8905
8906    /**
8907     * Resets the password for a specified user.<p>
8908     *
8909     * @param dbc the current database context
8910     * @param username the name of the user
8911     * @param oldPassword the old password
8912     * @param secondFactor the second factor data used for 2FA
8913     * @param newPassword the new password
8914     *
8915     * @throws CmsException if the user data could not be read from the database
8916     * @throws CmsSecurityException if the specified username and old password could not be verified
8917     */
8918    public void resetPassword(
8919        CmsDbContext dbc,
8920        String username,
8921        String oldPassword,
8922        CmsSecondFactorInfo secondFactor,
8923        String newPassword)
8924    throws CmsException, CmsSecurityException {
8925
8926        if ((oldPassword != null) && (newPassword != null)) {
8927
8928            CmsUser user = null;
8929
8930            if (dbc.getRequestContext().getAttribute(CmsUserDriver.REQ_ATTR_DONT_DIGEST_PASSWORD) == null) {
8931                validatePassword(newPassword);
8932            }
8933
8934            // read the user as a system user to verify that the specified old password is correct
8935            try {
8936                user = getUserDriver(dbc).readUser(dbc, username, oldPassword, null);
8937            } catch (CmsDbEntryNotFoundException e) {
8938                throw new CmsDataAccessException(Messages.get().container(Messages.ERR_RESET_PASSWORD_1, username), e);
8939            }
8940
8941            if ((user == null) || user.isManaged()) {
8942                throw new CmsDataAccessException(Messages.get().container(Messages.ERR_RESET_PASSWORD_1, username));
8943            }
8944
8945            CmsTwoFactorAuthenticationHandler twoFactorHandler = OpenCms.getTwoFactorAuthenticationHandler();
8946            if (twoFactorHandler.needsTwoFactorAuthentication(user) && twoFactorHandler.hasSecondFactor(user)) {
8947                if (!twoFactorHandler.verifySecondFactor(user, secondFactor)) {
8948                    throw new CmsDataAccessException(
8949                        Messages.get().container(Messages.ERR_RESET_PASSWORD_1, username),
8950                        new RuntimeException("Verification code mismatch"));
8951                }
8952            }
8953
8954            getUserDriver(dbc).writePassword(dbc, username, oldPassword, newPassword);
8955            user.getAdditionalInfo().put(
8956                CmsUserSettings.ADDITIONAL_INFO_LAST_PASSWORD_CHANGE,
8957                "" + System.currentTimeMillis());
8958            user.deleteAdditionalInfo(CmsUserSettings.ADDITIONAL_INFO_PASSWORD_RESET);
8959            getUserDriver(dbc).writeUser(dbc, user);
8960
8961            if (!dbc.getProjectId().isNullUUID()) {
8962                // user modified event is not needed
8963                return;
8964            }
8965            // fire user modified event
8966            Map<String, Object> eventData = new HashMap<String, Object>();
8967            eventData.put(I_CmsEventListener.KEY_USER_ID, user.getId().toString());
8968            eventData.put(
8969                I_CmsEventListener.KEY_USER_ACTION,
8970                I_CmsEventListener.VALUE_USER_MODIFIED_ACTION_RESET_PASSWORD);
8971            eventData.put(
8972                I_CmsEventListener.KEY_USER_CHANGES,
8973                Integer.valueOf(CmsUser.FLAG_CORE_DATA | CmsUser.FLAG_CORE_DATA));
8974            OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_USER_MODIFIED, eventData));
8975
8976        } else if (CmsStringUtil.isEmpty(oldPassword)) {
8977            throw new CmsDataAccessException(Messages.get().container(Messages.ERR_PWD_OLD_MISSING_0));
8978        } else if (CmsStringUtil.isEmpty(newPassword)) {
8979            throw new CmsDataAccessException(Messages.get().container(Messages.ERR_PWD_NEW_MISSING_0));
8980        }
8981    }
8982
8983    /**
8984     * Restores a deleted resource identified by its structure id from the historical archive.<p>
8985     *
8986     * @param dbc the current database context
8987     * @param structureId the structure id of the resource to restore
8988     *
8989     * @throws CmsException if something goes wrong
8990     *
8991     * @see CmsObject#restoreDeletedResource(CmsUUID)
8992     */
8993    public void restoreDeletedResource(CmsDbContext dbc, CmsUUID structureId) throws CmsException {
8994
8995        // get the last version, which should be the deleted one
8996        int version = getHistoryDriver(dbc).readLastVersion(dbc, structureId);
8997        // get that version
8998        I_CmsHistoryResource histRes = getHistoryDriver(dbc).readResource(dbc, structureId, version);
8999
9000        // check the parent path
9001        CmsResource parent;
9002        try {
9003            // try to read the parent resource by id
9004            parent = getVfsDriver(dbc).readResource(dbc, dbc.currentProject().getUuid(), histRes.getParentId(), true);
9005        } catch (CmsVfsResourceNotFoundException e) {
9006            // if not found try to read the parent resource by name
9007            try {
9008                // try to read the parent resource by id
9009                parent = getVfsDriver(dbc).readResource(
9010                    dbc,
9011                    dbc.currentProject().getUuid(),
9012                    CmsResource.getParentFolder(histRes.getRootPath()),
9013                    true);
9014            } catch (CmsVfsResourceNotFoundException e1) {
9015                // if not found try to restore the parent resource
9016                restoreDeletedResource(dbc, histRes.getParentId());
9017                parent = readResource(dbc, histRes.getParentId(), CmsResourceFilter.IGNORE_EXPIRATION);
9018            }
9019        }
9020        // check write permissions
9021        m_securityManager.checkPermissions(
9022            dbc,
9023            parent,
9024            CmsPermissionSet.ACCESS_WRITE,
9025            false,
9026            CmsResourceFilter.IGNORE_EXPIRATION);
9027
9028        // check the name
9029        String path = parent.getRootPath();
9030        String resName = CmsResource.getName(histRes.getRootPath()); // name
9031        String ext = "";
9032        if (resName.charAt(resName.length() - 1) == '/') {
9033            resName = resName.substring(0, resName.length() - 1);
9034        } else {
9035            ext = CmsFileUtil.getExtension(resName); // extension
9036        }
9037        String nameWOExt = resName.substring(0, resName.length() - ext.length()); // name without extension
9038        for (int i = 1; true; i++) {
9039            try {
9040                readResource(dbc, path + resName, CmsResourceFilter.ALL);
9041                resName = nameWOExt + "_" + i + ext;
9042                // try the next resource name with following schema: path/name_{i}.ext
9043            } catch (CmsVfsResourceNotFoundException e) {
9044                // ok, we found a not used resource name
9045                break;
9046            }
9047        }
9048
9049        // check structure id
9050        CmsUUID id = structureId;
9051        if (getVfsDriver(dbc).validateStructureIdExists(dbc, dbc.currentProject().getUuid(), structureId)) {
9052            // should never happen, but if already exists create a new one
9053            id = new CmsUUID();
9054        }
9055
9056        byte[] contents = null;
9057        boolean isFolder = true;
9058
9059        // do we need the contents?
9060        if (histRes instanceof CmsFile) {
9061            contents = ((CmsFile)histRes).getContents();
9062            if ((contents == null) || (contents.length == 0)) {
9063                contents = getHistoryDriver(dbc).readContent(dbc, histRes.getResourceId(), histRes.getPublishTag());
9064            }
9065            isFolder = false;
9066        }
9067
9068        // now read the historical properties
9069        List<CmsProperty> properties = getHistoryDriver(dbc).readProperties(dbc, histRes);
9070
9071        // create the object to create
9072        CmsResource newResource = new CmsResource(
9073            id,
9074            histRes.getResourceId(),
9075            path + resName,
9076            histRes.getTypeId(),
9077            isFolder,
9078            histRes.getFlags(),
9079            dbc.currentProject().getUuid(),
9080            CmsResource.STATE_NEW,
9081            histRes.getDateCreated(),
9082            histRes.getUserCreated(),
9083            histRes.getDateLastModified(),
9084            dbc.currentUser().getId(),
9085            histRes.getDateReleased(),
9086            histRes.getDateExpired(),
9087            histRes.getSiblingCount(),
9088            histRes.getLength(),
9089            histRes.getDateContent(),
9090            histRes.getVersion());
9091
9092        // log it
9093        log(
9094            dbc,
9095            new CmsLogEntry(
9096                dbc,
9097                newResource.getStructureId(),
9098                CmsLogEntryType.RESOURCE_RESTORE_DELETED,
9099                new String[] {newResource.getRootPath()}),
9100            false);
9101
9102        // prevent the date last modified is set to the current time
9103        newResource.setDateLastModified(newResource.getDateLastModified());
9104        // restore the resource!
9105        CmsResource resource = createResource(dbc, path + resName, newResource, contents, properties, true);
9106        // set resource state to changed
9107        newResource.setState(CmsResource.STATE_CHANGED);
9108        getVfsDriver(dbc).writeResourceState(dbc, dbc.currentProject(), newResource, UPDATE_RESOURCE_STATE, false);
9109        newResource.setState(CmsResource.STATE_NEW);
9110        // fire the event
9111        Map<String, Object> data = new HashMap<String, Object>(2);
9112        data.put(I_CmsEventListener.KEY_RESOURCE, resource);
9113        data.put(I_CmsEventListener.KEY_CHANGE, Integer.valueOf(CHANGED_RESOURCE | CHANGED_CONTENT));
9114        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_MODIFIED, data));
9115    }
9116
9117    /**
9118     * Restores a resource in the current project with a version from the historical archive.<p>
9119     *
9120     * @param dbc the current database context
9121     * @param resource the resource to restore from the archive
9122     * @param version the version number to restore from the archive
9123     *
9124     * @throws CmsException if something goes wrong
9125     *
9126     * @see CmsObject#restoreResourceVersion(CmsUUID, int)
9127     * @see I_CmsResourceType#restoreResource(CmsObject, CmsSecurityManager, CmsResource, int)
9128     */
9129    public void restoreResource(CmsDbContext dbc, CmsResource resource, int version) throws CmsException {
9130
9131        I_CmsHistoryResource historyResource = readResource(dbc, resource, version);
9132        CmsResourceState state = CmsResource.STATE_CHANGED;
9133        if (resource.getState().isNew()) {
9134            state = CmsResource.STATE_NEW;
9135        }
9136        int newVersion = resource.getVersion();
9137        if (resource.getState().isUnchanged()) {
9138            newVersion++;
9139        }
9140        CmsResource newResource = null;
9141        // is the resource a file?
9142        if (historyResource instanceof CmsFile) {
9143            // get the historical up flags
9144            int flags = historyResource.getFlags();
9145            if (resource.isLabeled()) {
9146                // set the flag for labeled links on the restored file
9147                flags |= CmsResource.FLAG_LABELED;
9148            }
9149            CmsFile newFile = new CmsFile(
9150                resource.getStructureId(),
9151                resource.getResourceId(),
9152                resource.getRootPath(),
9153                historyResource.getTypeId(),
9154                flags,
9155                dbc.currentProject().getUuid(),
9156                state,
9157                resource.getDateCreated(),
9158                historyResource.getUserCreated(),
9159                resource.getDateLastModified(),
9160                dbc.currentUser().getId(),
9161                historyResource.getDateReleased(),
9162                historyResource.getDateExpired(),
9163                resource.getSiblingCount(),
9164                historyResource.getLength(),
9165                historyResource.getDateContent(),
9166                newVersion,
9167                readFile(dbc, (CmsHistoryFile)historyResource).getContents());
9168
9169            // log it
9170            log(
9171                dbc,
9172                new CmsLogEntry(
9173                    dbc,
9174                    newFile.getStructureId(),
9175                    CmsLogEntryType.RESOURCE_HISTORY,
9176                    new String[] {newFile.getRootPath()}),
9177                false);
9178
9179            newResource = writeFile(dbc, newFile);
9180        } else {
9181            // it is a folder!
9182            newResource = new CmsFolder(
9183                resource.getStructureId(),
9184                resource.getResourceId(),
9185                resource.getRootPath(),
9186                historyResource.getTypeId(),
9187                historyResource.getFlags(),
9188                dbc.currentProject().getUuid(),
9189                state,
9190                resource.getDateCreated(),
9191                historyResource.getUserCreated(),
9192                resource.getDateLastModified(),
9193                dbc.currentUser().getId(),
9194                historyResource.getDateReleased(),
9195                historyResource.getDateExpired(),
9196                newVersion);
9197
9198            // log it
9199            log(
9200                dbc,
9201                new CmsLogEntry(
9202                    dbc,
9203                    newResource.getStructureId(),
9204                    CmsLogEntryType.RESOURCE_HISTORY,
9205                    new String[] {newResource.getRootPath()}),
9206                false);
9207
9208            writeResource(dbc, newResource);
9209        }
9210        if (newResource != null) {
9211            // now read the historical properties
9212            List<CmsProperty> historyProperties = getHistoryDriver(dbc).readProperties(dbc, historyResource);
9213            // remove all properties
9214            deleteAllProperties(dbc, newResource.getRootPath());
9215            // write them to the restored resource
9216            writePropertyObjects(dbc, newResource, historyProperties, false);
9217
9218            m_monitor.clearResourceCache();
9219        }
9220
9221        Map<String, Object> data = new HashMap<String, Object>(2);
9222        data.put(I_CmsEventListener.KEY_RESOURCE, resource);
9223        data.put(I_CmsEventListener.KEY_CHANGE, Integer.valueOf(CHANGED_RESOURCE | CHANGED_CONTENT));
9224        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_MODIFIED, data));
9225    }
9226
9227    /**
9228     * Saves a list of aliases for the same structure id, replacing any aliases for the same structure id.<p>
9229     *
9230     * @param dbc the current database context
9231     * @param project the current project
9232     * @param structureId the structure id for which the aliases should be saved
9233     * @param aliases the list of aliases to save
9234     *
9235     * @throws CmsException if something goes wrong
9236     */
9237    public void saveAliases(CmsDbContext dbc, CmsProject project, CmsUUID structureId, List<CmsAlias> aliases)
9238    throws CmsException {
9239
9240        for (CmsAlias alias : aliases) {
9241            if (!structureId.equals(alias.getStructureId())) {
9242                throw new IllegalArgumentException("Aliases to replace must have the same structure id!");
9243            }
9244        }
9245        I_CmsVfsDriver vfsDriver = getVfsDriver(dbc);
9246        vfsDriver.deleteAliases(dbc, project, new CmsAliasFilter(null, null, structureId));
9247        for (CmsAlias alias : aliases) {
9248            String aliasPath = alias.getAliasPath();
9249            if (CmsAlias.ALIAS_PATTERN.matcher(aliasPath).matches()) {
9250                vfsDriver.insertAlias(dbc, project, alias);
9251            } else {
9252                LOG.error("Invalid alias path: " + aliasPath);
9253            }
9254        }
9255    }
9256
9257    /**
9258     * Replaces the complete list of rewrite aliases for a given site root.<p>
9259     *
9260     * @param dbc the current database context
9261     * @param siteRoot the site root for which the rewrite aliases should be replaced
9262     * @param newAliases the new aliases for the given site root
9263     * @throws CmsException if something goes wrong
9264     */
9265    public void saveRewriteAliases(CmsDbContext dbc, String siteRoot, List<CmsRewriteAlias> newAliases)
9266    throws CmsException {
9267
9268        CmsRewriteAliasFilter filter = new CmsRewriteAliasFilter().setSiteRoot(siteRoot);
9269        getVfsDriver(dbc).deleteRewriteAliases(dbc, filter);
9270        getVfsDriver(dbc).insertRewriteAliases(dbc, newAliases);
9271    }
9272
9273    /**
9274     * Searches for users which fit the given criteria.<p>
9275     *
9276     * @param dbc the database context
9277     * @param searchParams the search criteria
9278     *
9279     * @return the users which fit the search criteria
9280     *
9281     * @throws CmsDataAccessException if something goes wrong
9282     */
9283    public List<CmsUser> searchUsers(CmsDbContext dbc, CmsUserSearchParameters searchParams
9284
9285    ) throws CmsDataAccessException {
9286
9287        return getUserDriver(dbc).searchUsers(dbc, searchParams);
9288    }
9289
9290    /**
9291     * Changes the "expire" date of a resource.<p>
9292     *
9293     * @param dbc the current database context
9294     * @param resource the resource to touch
9295     * @param dateExpired the new expire date of the resource
9296     *
9297     * @throws CmsDataAccessException if something goes wrong
9298     *
9299     * @see CmsObject#setDateExpired(String, long, boolean)
9300     * @see I_CmsResourceType#setDateExpired(CmsObject, CmsSecurityManager, CmsResource, long, boolean)
9301     */
9302    public void setDateExpired(CmsDbContext dbc, CmsResource resource, long dateExpired) throws CmsDataAccessException {
9303
9304        resource.setDateExpired(dateExpired);
9305        if (resource.getState().isUnchanged()) {
9306            resource.setState(CmsResource.STATE_CHANGED);
9307        }
9308        getVfsDriver(dbc).writeResourceState(dbc, dbc.currentProject(), resource, UPDATE_STRUCTURE, false);
9309
9310        // modify the last modified project reference
9311        getVfsDriver(dbc).writeResourceState(dbc, dbc.currentProject(), resource, UPDATE_RESOURCE_PROJECT, false);
9312        // log
9313        log(
9314            dbc,
9315            new CmsLogEntry(
9316                dbc,
9317                resource.getStructureId(),
9318                CmsLogEntryType.RESOURCE_DATE_EXPIRED,
9319                new String[] {resource.getRootPath()}),
9320            false);
9321
9322        // clear the cache
9323        m_monitor.clearResourceCache();
9324
9325        // fire the event
9326        Map<String, Object> data = new HashMap<String, Object>(2);
9327        data.put(I_CmsEventListener.KEY_RESOURCE, resource);
9328        data.put(I_CmsEventListener.KEY_CHANGE, Integer.valueOf(CHANGED_TIMEFRAME));
9329        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_MODIFIED, data));
9330    }
9331
9332    /**
9333     * Changes the "last modified" timestamp of a resource.<p>
9334     *
9335     * @param dbc the current database context
9336     * @param resource the resource to touch
9337     * @param dateLastModified the new last modified date of the resource
9338     *
9339     * @throws CmsDataAccessException if something goes wrong
9340     *
9341     * @see CmsObject#setDateLastModified(String, long, boolean)
9342     * @see I_CmsResourceType#setDateLastModified(CmsObject, CmsSecurityManager, CmsResource, long, boolean)
9343     */
9344    public void setDateLastModified(CmsDbContext dbc, CmsResource resource, long dateLastModified)
9345    throws CmsDataAccessException {
9346
9347        // modify the last modification date
9348        resource.setDateLastModified(dateLastModified);
9349        if (resource.getState().isUnchanged()) {
9350            resource.setState(CmsResource.STATE_CHANGED);
9351        } else if (resource.getState().isNew() && (resource.getSiblingCount() > 1)) {
9352            // in case of new resources with siblings make sure the state is correct
9353            resource.setState(CmsResource.STATE_CHANGED);
9354        }
9355        resource.setUserLastModified(dbc.currentUser().getId());
9356        getVfsDriver(dbc).writeResourceState(dbc, dbc.currentProject(), resource, UPDATE_RESOURCE, false);
9357
9358        log(
9359            dbc,
9360            new CmsLogEntry(
9361                dbc,
9362                resource.getStructureId(),
9363                CmsLogEntryType.RESOURCE_TOUCHED,
9364                new String[] {resource.getRootPath()}),
9365            false);
9366
9367        // clear the cache
9368        m_monitor.clearResourceCache();
9369
9370        // fire the event
9371        Map<String, Object> data = new HashMap<String, Object>(2);
9372        data.put(I_CmsEventListener.KEY_RESOURCE, resource);
9373        data.put(I_CmsEventListener.KEY_CHANGE, Integer.valueOf(CHANGED_LASTMODIFIED));
9374        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_MODIFIED, data));
9375    }
9376
9377    /**
9378     * Changes the "release" date of a resource.<p>
9379     *
9380     * @param dbc the current database context
9381     * @param resource the resource to touch
9382     * @param dateReleased the new release date of the resource
9383     *
9384     * @throws CmsDataAccessException if something goes wrong
9385     *
9386     * @see CmsObject#setDateReleased(String, long, boolean)
9387     * @see I_CmsResourceType#setDateReleased(CmsObject, CmsSecurityManager, CmsResource, long, boolean)
9388     */
9389    public void setDateReleased(CmsDbContext dbc, CmsResource resource, long dateReleased)
9390    throws CmsDataAccessException {
9391
9392        // modify the last modification date
9393        resource.setDateReleased(dateReleased);
9394        if (resource.getState().isUnchanged()) {
9395            resource.setState(CmsResource.STATE_CHANGED);
9396        }
9397        getVfsDriver(dbc).writeResourceState(dbc, dbc.currentProject(), resource, UPDATE_STRUCTURE, false);
9398
9399        // modify the last modified project reference
9400        getVfsDriver(dbc).writeResourceState(dbc, dbc.currentProject(), resource, UPDATE_RESOURCE_PROJECT, false);
9401        // log it
9402        log(
9403            dbc,
9404            new CmsLogEntry(
9405                dbc,
9406                resource.getStructureId(),
9407                CmsLogEntryType.RESOURCE_DATE_RELEASED,
9408                new String[] {resource.getRootPath()}),
9409            false);
9410
9411        // clear the cache
9412        m_monitor.clearResourceCache();
9413
9414        // fire the event
9415        Map<String, Object> data = new HashMap<String, Object>(2);
9416        data.put(I_CmsEventListener.KEY_RESOURCE, resource);
9417        data.put(I_CmsEventListener.KEY_CHANGE, Integer.valueOf(CHANGED_TIMEFRAME));
9418        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_MODIFIED, data));
9419    }
9420
9421    /**
9422     * Sets a new parent group for an already existing group.<p>
9423     *
9424     * @param dbc the current database context
9425     * @param groupName the name of the group that should be written
9426     * @param parentGroupName the name of the parent group to set,
9427     *                      or <code>null</code> if the parent
9428     *                      group should be deleted.
9429     *
9430     * @throws CmsException if operation was not successful
9431     * @throws CmsDataAccessException if the group with <code>groupName</code> could not be read from VFS
9432     */
9433    public void setParentGroup(CmsDbContext dbc, String groupName, String parentGroupName)
9434    throws CmsException, CmsDataAccessException {
9435
9436        CmsGroup group = readGroup(dbc, groupName);
9437        CmsUUID parentGroupId = CmsUUID.getNullUUID();
9438
9439        // if the group exists, use its id, else set to unknown.
9440        if (parentGroupName != null) {
9441            parentGroupId = readGroup(dbc, parentGroupName).getId();
9442        }
9443
9444        group.setParentId(parentGroupId);
9445
9446        // write the changes to the cms
9447        writeGroup(dbc, group);
9448    }
9449
9450    /**
9451     * Sets the password for a user.<p>
9452     *
9453     * @param dbc the current database context
9454     * @param username the name of the user
9455     * @param newPassword the new password
9456     *
9457     * @throws CmsException if operation was not successful
9458     * @throws CmsIllegalArgumentException if the user with the <code>username</code> was not found
9459     */
9460    public void setPassword(CmsDbContext dbc, String username, String newPassword)
9461    throws CmsException, CmsIllegalArgumentException {
9462
9463        if (dbc.getRequestContext().getAttribute(CmsUserDriver.REQ_ATTR_DONT_DIGEST_PASSWORD) == null) {
9464            validatePassword(newPassword);
9465        }
9466
9467        // read the user as a system user to verify that the specified old password is correct
9468        CmsUser user = getUserDriver(dbc).readUser(dbc, username);
9469        // only continue if not found and read user from web might succeed
9470        getUserDriver(dbc).writePassword(dbc, username, null, newPassword);
9471        user.getAdditionalInfo().put(
9472            CmsUserSettings.ADDITIONAL_INFO_LAST_PASSWORD_CHANGE,
9473            "" + System.currentTimeMillis());
9474        getUserDriver(dbc).writeUser(dbc, user);
9475
9476        // fire user modified event
9477        Map<String, Object> eventData = new HashMap<String, Object>();
9478        eventData.put(I_CmsEventListener.KEY_USER_ID, user.getId().toString());
9479        eventData.put(I_CmsEventListener.KEY_USER_ACTION, I_CmsEventListener.VALUE_USER_MODIFIED_ACTION_RESET_PASSWORD);
9480        eventData.put(
9481            I_CmsEventListener.KEY_USER_CHANGES,
9482            Integer.valueOf(CmsUser.FLAG_CORE_DATA | CmsUser.FLAG_CORE_DATA));
9483        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_USER_MODIFIED, eventData));
9484    }
9485
9486    /**
9487     * Marks a subscribed resource as deleted.<p>
9488     *
9489     * @param dbc the database context
9490     * @param poolName the name of the database pool to use
9491     * @param resource the subscribed resource to mark as deleted
9492     *
9493     * @throws CmsException if something goes wrong
9494     */
9495    public void setSubscribedResourceAsDeleted(CmsDbContext dbc, String poolName, CmsResource resource)
9496    throws CmsException {
9497
9498        getSubscriptionDriver().setSubscribedResourceAsDeleted(dbc, poolName, resource);
9499    }
9500
9501    /**
9502     * Moves an user to the given organizational unit.<p>
9503     *
9504     * @param dbc the current db context
9505     * @param orgUnit the organizational unit to add the resource to
9506     * @param user the user that is to be moved to the organizational unit
9507     *
9508     * @throws CmsException if something goes wrong
9509     *
9510     * @see org.opencms.security.CmsOrgUnitManager#setUsersOrganizationalUnit(CmsObject, String, String)
9511     */
9512    public void setUsersOrganizationalUnit(CmsDbContext dbc, CmsOrganizationalUnit orgUnit, CmsUser user)
9513    throws CmsException {
9514
9515        if (!getGroupsOfUser(dbc, user.getName(), false).isEmpty()) {
9516            throw new CmsDbConsistencyException(
9517                Messages.get().container(Messages.ERR_ORGUNIT_MOVE_USER_2, orgUnit.getName(), user.getName()));
9518        }
9519
9520        // move the principal
9521        getUserDriver(dbc).setUsersOrganizationalUnit(dbc, orgUnit, user);
9522        // remove the principal from cache
9523        m_monitor.clearUserCache(user);
9524
9525        if (!dbc.getProjectId().isNullUUID()) {
9526            // user modified event is not needed
9527            return;
9528        }
9529        // fire user modified event
9530        Map<String, Object> eventData = new HashMap<String, Object>();
9531        eventData.put(I_CmsEventListener.KEY_USER_ID, user.getId().toString());
9532        eventData.put(I_CmsEventListener.KEY_OU_NAME, user.getOuFqn());
9533        eventData.put(I_CmsEventListener.KEY_USER_ACTION, I_CmsEventListener.VALUE_USER_MODIFIED_ACTION_SET_OU);
9534        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_USER_MODIFIED, eventData));
9535    }
9536
9537    /**
9538     * Subscribes the user or group to the resource.<p>
9539     *
9540     * @param dbc the database context
9541     * @param poolName the name of the database pool to use
9542     * @param principal the principal that subscribes to the resource
9543     * @param resource the resource to subscribe to
9544     *
9545     * @throws CmsException if something goes wrong
9546     */
9547    public void subscribeResourceFor(CmsDbContext dbc, String poolName, CmsPrincipal principal, CmsResource resource)
9548    throws CmsException {
9549
9550        getSubscriptionDriver().subscribeResourceFor(dbc, poolName, principal, resource);
9551    }
9552
9553    /**
9554     * Undelete the resource.<p>
9555     *
9556     * @param dbc the current database context
9557     * @param resource the name of the resource to apply this operation to
9558     *
9559     * @throws CmsException if something goes wrong
9560     *
9561     * @see CmsObject#undeleteResource(String, boolean)
9562     * @see I_CmsResourceType#undelete(CmsObject, CmsSecurityManager, CmsResource, boolean)
9563     */
9564    public void undelete(CmsDbContext dbc, CmsResource resource) throws CmsException {
9565
9566        if (!resource.getState().isDeleted()) {
9567            throw new CmsVfsException(
9568                Messages.get().container(
9569                    Messages.ERR_UNDELETE_FOR_RESOURCE_DELETED_1,
9570                    dbc.removeSiteRoot(resource.getRootPath())));
9571        }
9572
9573        // set the state to changed
9574        resource.setState(CmsResourceState.STATE_CHANGED);
9575        // perform the changes
9576        updateState(dbc, resource, false);
9577        // log it
9578        log(
9579            dbc,
9580            new CmsLogEntry(
9581                dbc,
9582                resource.getStructureId(),
9583                CmsLogEntryType.RESOURCE_UNDELETED,
9584                new String[] {resource.getRootPath()}),
9585            false);
9586        // clear the cache
9587        m_monitor.clearResourceCache();
9588
9589        // fire change event
9590        Map<String, Object> data = new HashMap<String, Object>(2);
9591        data.put(I_CmsEventListener.KEY_RESOURCE, resource);
9592        data.put(I_CmsEventListener.KEY_CHANGE, Integer.valueOf(CHANGED_RESOURCE));
9593        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_MODIFIED, data));
9594    }
9595
9596    /**
9597     * Undos all changes in the resource by restoring the version from the
9598     * online project to the current offline project.<p>
9599     *
9600     * @param dbc the current database context
9601     * @param resource the name of the resource to apply this operation to
9602     * @param mode the undo mode, one of the <code>{@link org.opencms.file.CmsResource.CmsResourceUndoMode}#UNDO_XXX</code> constants
9603     *      please note that the recursive flag is ignored at this level
9604     *
9605     * @throws CmsException if something goes wrong
9606     *
9607     * @see CmsObject#undoChanges(String, CmsResource.CmsResourceUndoMode)
9608     * @see I_CmsResourceType#undoChanges(CmsObject, CmsSecurityManager, CmsResource, CmsResource.CmsResourceUndoMode)
9609     */
9610    public void undoChanges(CmsDbContext dbc, CmsResource resource, CmsResource.CmsResourceUndoMode mode)
9611    throws CmsException {
9612
9613        if (resource.getState().isNew()) {
9614            // undo changes is impossible on a new resource
9615            throw new CmsVfsException(Messages.get().container(Messages.ERR_UNDO_CHANGES_FOR_RESOURCE_NEW_0));
9616        }
9617
9618        // we need this for later use
9619        CmsProject onlineProject = readProject(dbc, CmsProject.ONLINE_PROJECT_ID);
9620        // read the resource from the online project
9621        CmsResource onlineResource = getVfsDriver(
9622            dbc).readResource(dbc, CmsProject.ONLINE_PROJECT_ID, resource.getStructureId(), true);
9623
9624        CmsResource onlineResourceByPath = null;
9625        try {
9626            // this is needed to figure out if a moved resource overwrote a deleted one
9627            onlineResourceByPath = getVfsDriver(
9628                dbc).readResource(dbc, CmsProject.ONLINE_PROJECT_ID, resource.getRootPath(), true);
9629
9630            // force undo move operation if needed
9631            if (!mode.isUndoMove() && !onlineResourceByPath.getRootPath().equals(onlineResource.getRootPath())) {
9632                mode = mode.includeMove();
9633            }
9634        } catch (Exception e) {
9635            // ok
9636        }
9637
9638        boolean moved = !onlineResource.getRootPath().equals(resource.getRootPath());
9639        // undo move operation if required
9640        if (moved && mode.isUndoMove()) {
9641            moveResource(dbc, resource, onlineResource.getRootPath(), true);
9642            if ((onlineResourceByPath != null)
9643                && !onlineResourceByPath.getRootPath().equals(onlineResource.getRootPath())) {
9644                // was moved over deleted, so the deleted file has to be undone
9645                undoContentChanges(dbc, onlineProject, null, onlineResourceByPath, CmsResource.STATE_UNCHANGED, true);
9646            }
9647        }
9648        // undo content changes
9649        CmsResourceState newState = CmsResource.STATE_UNCHANGED;
9650        if (moved && !mode.isUndoMove()) {
9651            newState = CmsResource.STATE_CHANGED;
9652        }
9653        undoContentChanges(dbc, onlineProject, resource, onlineResource, newState, moved && mode.isUndoMove());
9654        // because undoContentChanges deletes the offline resource internally, we have
9655        // to write an entry to the log table to prevent the resource from appearing in the
9656        // user's publish list.
9657        log(
9658            dbc,
9659            new CmsLogEntry(
9660                dbc,
9661                resource.getStructureId(),
9662                CmsLogEntryType.RESOURCE_CHANGES_UNDONE,
9663                new String[] {resource.getRootPath()}),
9664            true);
9665
9666    }
9667
9668    /**
9669     * Unlocks all resources in the given project.<p>
9670     *
9671     * @param project the project to unlock the resources in
9672     */
9673    public void unlockProject(CmsProject project) {
9674
9675        // unlock all resources in the project
9676        m_lockManager.removeResourcesInProject(project.getUuid(), false);
9677        m_monitor.clearResourceCache();
9678        m_monitor.flushCache(CmsMemoryMonitor.CacheType.PROJECT, CmsMemoryMonitor.CacheType.PERMISSION);
9679    }
9680
9681    /**
9682     * Unlocks a resource.<p>
9683     *
9684     * @param dbc the current database context
9685     * @param resource the resource to unlock
9686     * @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
9687     * @param removeSystemLock <code>true</code>, if you also want to remove system locks
9688     *
9689     * @throws CmsException if something goes wrong
9690     *
9691     * @see CmsObject#unlockResource(String)
9692     * @see I_CmsResourceType#unlockResource(CmsObject, CmsSecurityManager, CmsResource)
9693     */
9694    public void unlockResource(CmsDbContext dbc, CmsResource resource, boolean force, boolean removeSystemLock)
9695    throws CmsException {
9696
9697        // update the resource cache
9698        m_monitor.clearResourceCache();
9699
9700        // now update lock status
9701        m_lockManager.removeResource(dbc, resource, force, removeSystemLock);
9702
9703        // we must also clear the permission cache
9704        m_monitor.flushCache(CmsMemoryMonitor.CacheType.PERMISSION);
9705
9706        // fire resource modification event
9707        Map<String, Object> data = new HashMap<String, Object>(2);
9708        data.put(I_CmsEventListener.KEY_RESOURCE, resource);
9709        data.put(I_CmsEventListener.KEY_CHANGE, Integer.valueOf(NOTHING_CHANGED));
9710        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_MODIFIED, data));
9711    }
9712
9713    /**
9714     * Unsubscribes all deleted resources that were deleted before the specified time stamp.<p>
9715     *
9716     * @param dbc the database context
9717     * @param poolName the name of the database pool to use
9718     * @param deletedTo the time stamp to which the resources have been deleted
9719     *
9720     * @throws CmsException if something goes wrong
9721     */
9722    public void unsubscribeAllDeletedResources(CmsDbContext dbc, String poolName, long deletedTo) throws CmsException {
9723
9724        getSubscriptionDriver().unsubscribeAllDeletedResources(dbc, poolName, deletedTo);
9725    }
9726
9727    /**
9728     * Unsubscribes the principal from all resources.<p>
9729     *
9730     * @param dbc the database context
9731     * @param poolName the name of the database pool to use
9732     * @param principal the principal that unsubscribes from all resources
9733     *
9734     * @throws CmsException if something goes wrong
9735     */
9736    public void unsubscribeAllResourcesFor(CmsDbContext dbc, String poolName, CmsPrincipal principal)
9737    throws CmsException {
9738
9739        getSubscriptionDriver().unsubscribeAllResourcesFor(dbc, poolName, principal);
9740
9741    }
9742
9743    /**
9744     * Unsubscribes the principal from the resource.<p>
9745     *
9746     * @param dbc the database context
9747     * @param poolName the name of the database pool to use
9748     * @param principal the principal that unsubscribes from the resource
9749     * @param resource the resource to unsubscribe from
9750     *
9751     * @throws CmsException if something goes wrong
9752     */
9753    public void unsubscribeResourceFor(CmsDbContext dbc, String poolName, CmsPrincipal principal, CmsResource resource)
9754    throws CmsException {
9755
9756        getSubscriptionDriver().unsubscribeResourceFor(dbc, poolName, principal, resource);
9757    }
9758
9759    /**
9760     * Unsubscribes all groups and users from the resource.<p>
9761     *
9762     * @param dbc the database context
9763     * @param poolName the name of the database pool to use
9764     * @param resource the resource to unsubscribe all groups and users from
9765     *
9766     * @throws CmsException if something goes wrong
9767     */
9768    public void unsubscribeResourceForAll(CmsDbContext dbc, String poolName, CmsResource resource) throws CmsException {
9769
9770        getSubscriptionDriver().unsubscribeResourceForAll(dbc, poolName, resource);
9771    }
9772
9773    /**
9774     * Update the export points.<p>
9775     *
9776     * All files and folders "inside" an export point are written.<p>
9777     *
9778     * @param dbc the current database context
9779     */
9780    public void updateExportPoints(CmsDbContext dbc) {
9781
9782        try {
9783            // read the export points and return immediately if there are no export points at all
9784            Set<CmsExportPoint> exportPoints = new HashSet<CmsExportPoint>();
9785            exportPoints.addAll(OpenCms.getExportPoints());
9786            exportPoints.addAll(OpenCms.getModuleManager().getExportPoints());
9787            if (exportPoints.size() == 0) {
9788                if (LOG.isWarnEnabled()) {
9789                    LOG.warn(Messages.get().getBundle().key(Messages.LOG_NO_EXPORT_POINTS_CONFIGURED_0));
9790                }
9791                return;
9792            }
9793
9794            // create the driver to write the export points
9795            I_CmsExportPointDriver exportPointDriver = OpenCms.getImportExportManager().createExportPointDriver(
9796                exportPoints);
9797
9798            // the export point hash table contains RFS export paths keyed by their internal VFS paths
9799            Iterator<String> i = exportPointDriver.getExportPointPaths().iterator();
9800            I_CmsVfsDriver vfsDriver = getVfsDriver(dbc);
9801            while (i.hasNext()) {
9802                String currentExportPoint = i.next();
9803
9804                // print some report messages
9805                if (LOG.isInfoEnabled()) {
9806                    LOG.info(Messages.get().getBundle().key(Messages.LOG_WRITE_EXPORT_POINT_1, currentExportPoint));
9807                }
9808
9809                try {
9810                    CmsResourceFilter filter = CmsResourceFilter.DEFAULT;
9811                    List<CmsResource> resources = vfsDriver.readResourceTree(
9812                        dbc,
9813                        CmsProject.ONLINE_PROJECT_ID,
9814                        currentExportPoint,
9815                        filter.getType(),
9816                        filter.getState(),
9817                        filter.getModifiedAfter(),
9818                        filter.getModifiedBefore(),
9819                        filter.getReleaseAfter(),
9820                        filter.getReleaseBefore(),
9821                        filter.getExpireAfter(),
9822                        filter.getExpireBefore(),
9823                        CmsDriverManager.READMODE_INCLUDE_TREE
9824                            | (filter.excludeType() ? CmsDriverManager.READMODE_EXCLUDE_TYPE : 0)
9825                            | (filter.excludeState() ? CmsDriverManager.READMODE_EXCLUDE_STATE : 0));
9826
9827                    Iterator<CmsResource> j = resources.iterator();
9828                    while (j.hasNext()) {
9829                        CmsResource currentResource = j.next();
9830
9831                        if (currentResource.isFolder()) {
9832                            // export the folder
9833                            exportPointDriver.createFolder(currentResource.getRootPath(), currentExportPoint);
9834                        } else {
9835                            // try to create the exportpoint folder
9836                            exportPointDriver.createFolder(currentExportPoint, currentExportPoint);
9837                            byte[] onlineContent = vfsDriver.readContent(
9838                                dbc,
9839                                CmsProject.ONLINE_PROJECT_ID,
9840                                currentResource.getResourceId());
9841                            // export the file content online
9842                            exportPointDriver.writeFile(
9843                                currentResource.getRootPath(),
9844                                currentExportPoint,
9845                                onlineContent);
9846                        }
9847                    }
9848                } catch (CmsException e) {
9849                    // there might exist export points without corresponding resources in the VFS
9850                    // -> ignore exceptions which are not "resource not found" exception quiet here
9851                    if (e instanceof CmsVfsResourceNotFoundException) {
9852                        if (LOG.isErrorEnabled()) {
9853                            LOG.error(Messages.get().getBundle().key(Messages.LOG_UPDATE_EXORT_POINTS_ERROR_0), e);
9854                        }
9855                    }
9856                }
9857            }
9858        } catch (Exception e) {
9859            if (LOG.isErrorEnabled()) {
9860                LOG.error(Messages.get().getBundle().key(Messages.LOG_UPDATE_EXORT_POINTS_ERROR_0), e);
9861            }
9862        }
9863    }
9864
9865    /**
9866     * Updates the last login date on the given user to the current time.<p>
9867     *
9868     * @param dbc the current database context
9869     * @param user the user to be updated
9870     *
9871     * @throws CmsException if operation was not successful
9872     */
9873    public void updateLastLoginDate(CmsDbContext dbc, CmsUser user) throws CmsException {
9874
9875        m_monitor.clearUserCache(user);
9876        // set the last login time to the current time
9877        user.setLastlogin(System.currentTimeMillis());
9878        dbc.setAttribute(ATTRIBUTE_LOGIN, user.getName());
9879        getUserDriver(dbc).writeUser(dbc, user);
9880        // update cache
9881        m_monitor.cacheUser(user);
9882
9883        // invalidate all user dependent caches
9884        m_monitor.flushCache(
9885            CmsMemoryMonitor.CacheType.ACL,
9886            CmsMemoryMonitor.CacheType.GROUP,
9887            CmsMemoryMonitor.CacheType.ORG_UNIT,
9888            CmsMemoryMonitor.CacheType.USER_LIST,
9889            CmsMemoryMonitor.CacheType.PERMISSION,
9890            CmsMemoryMonitor.CacheType.RESOURCE_LIST);
9891
9892        // fire user modified event
9893        Map<String, Object> eventData = new HashMap<String, Object>();
9894        eventData.put(I_CmsEventListener.KEY_USER_ID, user.getId().toString());
9895        eventData.put(I_CmsEventListener.KEY_USER_NAME, user.getName());
9896        eventData.put(I_CmsEventListener.KEY_USER_ACTION, I_CmsEventListener.VALUE_USER_MODIFIED_ACTION_WRITE_USER);
9897        eventData.put(I_CmsEventListener.KEY_USER_CHANGES, Integer.valueOf(CmsUser.FLAG_LAST_LOGIN));
9898        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_USER_MODIFIED, eventData));
9899    }
9900
9901    /**
9902     * Logs everything that has not been written to DB jet.<p>
9903     *
9904     * @param dbc the current db context
9905     *
9906     * @throws CmsDataAccessException if something goes wrong
9907     */
9908    public void updateLog(CmsDbContext dbc) throws CmsDataAccessException {
9909
9910        synchronized (m_publishListUpdateLock) {
9911
9912            if (m_log.isEmpty()) {
9913                return;
9914            }
9915
9916            List<CmsLogEntry> log = new ArrayList<CmsLogEntry>(m_log);
9917            m_log.clear();
9918            String logTableEnabledStr = (String)OpenCms.getRuntimeProperty(PARAM_LOG_TABLE_ENABLED);
9919            if (Boolean.parseBoolean(logTableEnabledStr)) { // defaults to 'false' if value not set
9920                m_projectDriver.log(dbc, log);
9921            }
9922            A_CmsLogPublishListConverter converter = null;
9923            switch (OpenCms.getPublishManager().getPublishListRemoveMode()) {
9924                case currentUser:
9925                    converter = new CmsLogPublishListConverterCurrentUser();
9926                    break;
9927                case allUsers:
9928                default:
9929                    converter = new CmsLogPublishListConverterAllUsers();
9930                    break;
9931            }
9932            for (CmsLogEntry entry : log) {
9933                converter.add(entry);
9934            }
9935            converter.writeChangesToDatabase(dbc, m_projectDriver);
9936        }
9937    }
9938
9939    /**
9940     * Updates/Creates the given relations for the given resource.<p>
9941     *
9942     * @param dbc the db context
9943     * @param resource the resource to update the relations for
9944     * @param links the links to consider for updating
9945     *
9946     * @throws CmsException if something goes wrong
9947     *
9948     * @see CmsSecurityManager#updateRelationsForResource(CmsRequestContext, CmsResource, List)
9949     */
9950    public void updateRelationsForResource(CmsDbContext dbc, CmsResource resource, List<CmsLink> links)
9951    throws CmsException {
9952
9953        deleteRelationsWithSiblings(dbc, resource);
9954
9955        // build the links again only if needed
9956        if ((links == null) || links.isEmpty()) {
9957            return;
9958        }
9959        // the set of written relations
9960        Set<CmsRelation> writtenRelations = new HashSet<CmsRelation>();
9961
9962        // create new relation information
9963        I_CmsVfsDriver vfsDriver = getVfsDriver(dbc);
9964        Iterator<CmsLink> itLinks = links.iterator();
9965        while (itLinks.hasNext()) {
9966            CmsLink link = itLinks.next();
9967            if (link.isInternal()) { // only update internal links
9968                if (CmsStringUtil.isEmptyOrWhitespaceOnly(link.getTarget())) {
9969                    // only an anchor
9970                    continue;
9971                }
9972                CmsUUID targetId = link.getStructureId();
9973                String destPath = link.getTarget();
9974
9975                if (targetId != null) {
9976                    // the link target may not be a VFS path even if the link id is a structure id,
9977                    // so if possible, we read the resource for the id and set the relation target to its
9978                    // real root path.
9979                    try {
9980                        CmsResource destRes = readResource(dbc, targetId, CmsResourceFilter.ALL);
9981                        destPath = destRes.getRootPath();
9982                    } catch (CmsVfsResourceNotFoundException e) {
9983                        // ignore
9984                    }
9985                }
9986
9987                CmsRelation originalRelation = new CmsRelation(
9988                    resource.getStructureId(),
9989                    resource.getRootPath(),
9990                    link.getStructureId(),
9991                    destPath,
9992                    link.getType());
9993
9994                // do not write twice the same relation
9995                if (writtenRelations.contains(originalRelation)) {
9996                    continue;
9997                }
9998                writtenRelations.add(originalRelation);
9999
10000                // TODO: it would be good to have the link locale to make the relation just to the right sibling
10001                // create the relations in content for all siblings
10002                Iterator<CmsResource> itSiblings = readSiblings(dbc, resource, CmsResourceFilter.ALL).iterator();
10003                while (itSiblings.hasNext()) {
10004                    CmsResource sibling = itSiblings.next();
10005                    CmsRelation relation = new CmsRelation(
10006                        sibling.getStructureId(),
10007                        sibling.getRootPath(),
10008                        originalRelation.getTargetId(),
10009                        originalRelation.getTargetPath(),
10010                        link.getType());
10011                    vfsDriver.createRelation(dbc, dbc.currentProject().getUuid(), relation);
10012                }
10013            }
10014        }
10015    }
10016
10017    /**
10018     * Returns <code>true</code> if a user is member of the given group.<p>
10019     *
10020     * @param dbc the current database context
10021     * @param username the name of the user to check
10022     * @param groupname the name of the group to check
10023     * @param readRoles if to read roles or groups
10024     *
10025     * @return <code>true</code>, if the user is in the group, <code>false</code> otherwise
10026     *
10027     * @throws CmsException if something goes wrong
10028     */
10029    public boolean userInGroup(CmsDbContext dbc, String username, String groupname, boolean readRoles)
10030    throws CmsException {
10031
10032        List<CmsGroup> groups = getGroupsOfUser(dbc, username, readRoles);
10033        for (int i = 0; i < groups.size(); i++) {
10034            CmsGroup group = groups.get(i);
10035            if (groupname.equals(group.getName()) || groupname.substring(1).equals(group.getName())) {
10036                return true;
10037            }
10038        }
10039        return false;
10040    }
10041
10042    /**
10043     * This method checks if a new password follows the rules for
10044     * new passwords, which are defined by a Class implementing the
10045     * <code>{@link org.opencms.security.I_CmsPasswordHandler}</code>
10046     * interface and configured in the opencms.properties file.<p>
10047     *
10048     * If this method throws no exception the password is valid.<p>
10049     *
10050     * @param password the new password that has to be checked
10051     *
10052     * @throws CmsSecurityException if the password is not valid
10053     */
10054    public void validatePassword(String password) throws CmsSecurityException {
10055
10056        OpenCms.getPasswordHandler().validatePassword(password);
10057    }
10058
10059    /**
10060     * Validates the relations for the given resources.<p>
10061     *
10062     * @param dbc the database context
10063     * @param publishList the resources to validate during publishing
10064     * @param report a report to write the messages to
10065     *
10066     * @return a map with lists of invalid links
10067     *          (<code>{@link org.opencms.relations.CmsRelation}}</code> objects)
10068     *          keyed by root paths
10069     *
10070     * @throws Exception if something goes wrong
10071     */
10072    public Map<String, List<CmsRelation>> validateRelations(
10073        CmsDbContext dbc,
10074        CmsPublishList publishList,
10075        I_CmsReport report)
10076    throws Exception {
10077
10078        return m_htmlLinkValidator.validateResources(dbc, publishList, report);
10079    }
10080
10081    /**
10082     * Writes an access control entries to a given resource.<p>
10083     *
10084     * @param dbc the current database context
10085     * @param resource the resource
10086     * @param ace the entry to write
10087     *
10088     * @throws CmsException if something goes wrong
10089     */
10090    public void writeAccessControlEntry(CmsDbContext dbc, CmsResource resource, CmsAccessControlEntry ace)
10091    throws CmsException {
10092
10093        // write the new ace
10094        getUserDriver(dbc).writeAccessControlEntry(dbc, dbc.currentProject(), ace);
10095
10096        // log it
10097        log(
10098            dbc,
10099            new CmsLogEntry(
10100                dbc,
10101                resource.getStructureId(),
10102                CmsLogEntryType.RESOURCE_PERMISSIONS,
10103                new String[] {resource.getRootPath()}),
10104            false);
10105
10106        // update the "last modified" information
10107        setDateLastModified(dbc, resource, resource.getDateLastModified());
10108
10109        // clear the cache
10110        m_monitor.clearAccessControlListCache();
10111
10112        // fire a resource modification event
10113        Map<String, Object> data = new HashMap<String, Object>(2);
10114        data.put(I_CmsEventListener.KEY_RESOURCE, resource);
10115        data.put(I_CmsEventListener.KEY_CHANGE, Integer.valueOf(CHANGED_ACCESSCONTROL));
10116        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_MODIFIED, data));
10117    }
10118
10119    /**
10120     * Writes all export points into the file system for the publish task
10121     * specified by trhe given publish history ID.<p>
10122     *
10123     * @param dbc the current database context
10124     * @param report an I_CmsReport instance to print output message, or null to write messages to the log file
10125     * @param publishHistoryId ID to identify the publish task in the publish history
10126     */
10127    public void writeExportPoints(CmsDbContext dbc, I_CmsReport report, CmsUUID publishHistoryId) {
10128
10129        boolean printReportHeaders = false;
10130        List<CmsPublishedResource> publishedResources = null;
10131        try {
10132            // read the "published resources" for the specified publish history ID
10133            publishedResources = getProjectDriver(dbc).readPublishedResources(dbc, publishHistoryId);
10134        } catch (CmsException e) {
10135            if (LOG.isErrorEnabled()) {
10136                LOG.error(
10137                    Messages.get().getBundle().key(Messages.ERR_READ_PUBLISHED_RESOURCES_FOR_ID_1, publishHistoryId),
10138                    e);
10139            }
10140        }
10141        if ((publishedResources == null) || publishedResources.isEmpty()) {
10142            if (LOG.isWarnEnabled()) {
10143                LOG.warn(Messages.get().getBundle().key(Messages.LOG_EMPTY_PUBLISH_HISTORY_1, publishHistoryId));
10144            }
10145            return;
10146        }
10147
10148        // read the export points and return immediately if there are no export points at all
10149        Set<CmsExportPoint> exportPoints = new HashSet<CmsExportPoint>();
10150        exportPoints.addAll(OpenCms.getExportPoints());
10151        exportPoints.addAll(OpenCms.getModuleManager().getExportPoints());
10152        if (exportPoints.size() == 0) {
10153            if (LOG.isWarnEnabled()) {
10154                LOG.warn(Messages.get().getBundle().key(Messages.LOG_NO_EXPORT_POINTS_CONFIGURED_0));
10155            }
10156            return;
10157        }
10158
10159        // create the driver to write the export points
10160        I_CmsExportPointDriver exportPointDriver = OpenCms.getImportExportManager().createExportPointDriver(
10161            exportPoints);
10162
10163        // the report may be null if the export point write was started by an event
10164        if (report == null) {
10165            if (dbc.getRequestContext() != null) {
10166                report = new CmsLogReport(dbc.getRequestContext().getLocale(), getClass());
10167            } else {
10168                report = new CmsLogReport(CmsLocaleManager.getDefaultLocale(), getClass());
10169            }
10170        }
10171
10172        // iterate over all published resources to export them
10173        I_CmsVfsDriver vfsDriver = getVfsDriver(dbc);
10174        Iterator<CmsPublishedResource> i = publishedResources.iterator();
10175        while (i.hasNext()) {
10176            CmsPublishedResource currentPublishedResource = i.next();
10177            String currentExportPoint = exportPointDriver.getExportPoint(currentPublishedResource.getRootPath());
10178
10179            if (currentExportPoint != null) {
10180                if (!printReportHeaders) {
10181                    report.println(
10182                        Messages.get().container(Messages.RPT_EXPORT_POINTS_WRITE_BEGIN_0),
10183                        I_CmsReport.FORMAT_HEADLINE);
10184                    printReportHeaders = true;
10185                }
10186
10187                // print report message
10188                if (currentPublishedResource.getState().isDeleted()) {
10189                    report.print(
10190                        Messages.get().container(Messages.RPT_EXPORT_POINTS_DELETE_0),
10191                        I_CmsReport.FORMAT_NOTE);
10192                } else {
10193                    report.print(Messages.get().container(Messages.RPT_EXPORT_POINTS_WRITE_0), I_CmsReport.FORMAT_NOTE);
10194                }
10195                report.print(
10196                    org.opencms.report.Messages.get().container(
10197                        org.opencms.report.Messages.RPT_ARGUMENT_1,
10198                        currentPublishedResource.getRootPath()));
10199                report.print(org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_DOTS_0));
10200
10201                if (currentPublishedResource.isFolder()) {
10202                    // export the folder
10203                    if (currentPublishedResource.getState().isDeleted()) {
10204                        exportPointDriver.deleteResource(currentPublishedResource.getRootPath(), currentExportPoint);
10205                    } else {
10206                        exportPointDriver.createFolder(currentPublishedResource.getRootPath(), currentExportPoint);
10207                    }
10208                    report.println(
10209                        org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_OK_0),
10210                        I_CmsReport.FORMAT_OK);
10211                } else {
10212                    // export the file
10213                    try {
10214                        if (currentPublishedResource.getState().isDeleted()) {
10215                            exportPointDriver.deleteResource(
10216                                currentPublishedResource.getRootPath(),
10217                                currentExportPoint);
10218                        } else {
10219                            // read the file content online
10220                            byte[] onlineContent = vfsDriver.readContent(
10221                                dbc,
10222                                CmsProject.ONLINE_PROJECT_ID,
10223                                currentPublishedResource.getResourceId());
10224                            exportPointDriver.writeFile(
10225                                currentPublishedResource.getRootPath(),
10226                                currentExportPoint,
10227                                onlineContent);
10228                        }
10229                        report.println(
10230                            org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_OK_0),
10231                            I_CmsReport.FORMAT_OK);
10232                    } catch (CmsException e) {
10233                        if (LOG.isErrorEnabled()) {
10234                            LOG.error(
10235                                Messages.get().getBundle().key(
10236                                    Messages.LOG_WRITE_EXPORT_POINT_ERROR_1,
10237                                    currentPublishedResource.getRootPath()),
10238                                e);
10239                        }
10240                        report.println(
10241                            org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_FAILED_0),
10242                            I_CmsReport.FORMAT_ERROR);
10243                    }
10244                }
10245            }
10246        }
10247        if (printReportHeaders) {
10248            report.println(
10249                Messages.get().container(Messages.RPT_EXPORT_POINTS_WRITE_END_0),
10250                I_CmsReport.FORMAT_HEADLINE);
10251        }
10252    }
10253
10254    /**
10255     * Writes a resource to the OpenCms VFS, including it's content.<p>
10256     *
10257     * Applies only to resources of type <code>{@link CmsFile}</code>
10258     * i.e. resources that have a binary content attached.<p>
10259     *
10260     * Certain resource types might apply content validation or transformation rules
10261     * before the resource is actually written to the VFS. The returned result
10262     * might therefore be a modified version from the provided original.<p>
10263     *
10264     * @param dbc the current database context
10265     * @param resource the resource to apply this operation to
10266     *
10267     * @return the written resource (may have been modified)
10268     *
10269     * @throws CmsException if something goes wrong
10270     *
10271     * @see CmsObject#writeFile(CmsFile)
10272     * @see I_CmsResourceType#writeFile(CmsObject, CmsSecurityManager, CmsFile)
10273     */
10274    public CmsFile writeFile(CmsDbContext dbc, CmsFile resource) throws CmsException {
10275
10276        resource.setUserLastModified(dbc.currentUser().getId());
10277        resource.setContents(resource.getContents()); // to be sure the content date is updated
10278
10279        getVfsDriver(dbc).writeResource(dbc, dbc.currentProject().getUuid(), resource, UPDATE_RESOURCE_STATE);
10280
10281        byte[] contents = resource.getContents();
10282        getVfsDriver(dbc).writeContent(dbc, resource.getResourceId(), contents);
10283        // log it
10284        log(
10285            dbc,
10286            new CmsLogEntry(
10287                dbc,
10288                resource.getStructureId(),
10289                CmsLogEntryType.RESOURCE_CONTENT_MODIFIED,
10290                new String[] {resource.getRootPath()}),
10291            false);
10292
10293        // read the file back from db
10294        resource = new CmsFile(readResource(dbc, resource.getStructureId(), CmsResourceFilter.ALL));
10295        resource.setContents(contents);
10296
10297        deleteRelationsWithSiblings(dbc, resource);
10298
10299        // update the cache
10300        m_monitor.clearResourceCache();
10301
10302        Map<String, Object> data = new HashMap<String, Object>(2);
10303        data.put(I_CmsEventListener.KEY_RESOURCE, resource);
10304        data.put(I_CmsEventListener.KEY_CHANGE, Integer.valueOf(CHANGED_CONTENT));
10305        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_MODIFIED, data));
10306
10307        return resource;
10308    }
10309
10310    /**
10311     * Writes an already existing group.<p>
10312     *
10313     * The group id has to be a valid OpenCms group id.<br>
10314     *
10315     * The group with the given id will be completely overridden
10316     * by the given data.<p>
10317     *
10318     * @param dbc the current database context
10319     * @param group the group that should be written
10320     *
10321     * @throws CmsException if operation was not successful
10322     */
10323    public void writeGroup(CmsDbContext dbc, CmsGroup group) throws CmsException {
10324
10325        CmsGroup oldGroup = readGroup(dbc, group.getName());
10326        m_monitor.uncacheGroup(oldGroup);
10327        getUserDriver(dbc).writeGroup(dbc, group);
10328        m_monitor.cacheGroup(group);
10329
10330        if (!dbc.getProjectId().isNullUUID()) {
10331            // group modified event is not needed
10332            return;
10333        }
10334        // fire group modified event
10335        Map<String, Object> eventData = new HashMap<String, Object>();
10336        eventData.put(I_CmsEventListener.KEY_GROUP_ID, group.getId().toString());
10337        eventData.put(I_CmsEventListener.KEY_GROUP_NAME, oldGroup.getName());
10338        eventData.put(I_CmsEventListener.KEY_USER_ACTION, I_CmsEventListener.VALUE_GROUP_MODIFIED_ACTION_WRITE);
10339        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_GROUP_MODIFIED, eventData));
10340    }
10341
10342    /**
10343     * Creates an historical entry of the current project.<p>
10344     *
10345     * @param dbc the current database context
10346     * @param publishTag the version
10347     * @param publishDate the date of publishing
10348     *
10349     * @throws CmsDataAccessException if operation was not successful
10350     */
10351    public void writeHistoryProject(CmsDbContext dbc, int publishTag, long publishDate) throws CmsDataAccessException {
10352
10353        getHistoryDriver(dbc).writeProject(dbc, publishTag, publishDate);
10354    }
10355
10356    /**
10357     * Writes the locks that are currently stored in-memory to the database to allow restoring them
10358     * in future server startups.<p>
10359     *
10360     * This overwrites the locks previously stored in the underlying database table.<p>
10361     *
10362     * @param dbc the current database context
10363     *
10364     * @throws CmsException if something goes wrong
10365     */
10366    public void writeLocks(CmsDbContext dbc) throws CmsException {
10367
10368        m_lockManager.writeLocks(dbc);
10369    }
10370
10371    /**
10372     * Writes an already existing organizational unit.<p>
10373     *
10374     * The organizational unit id has to be a valid OpenCms organizational unit id.<br>
10375     *
10376     * The organizational unit with the given id will be completely overridden
10377     * by the given data.<p>
10378     *
10379     * @param dbc the current db context
10380     * @param organizationalUnit the organizational unit that should be written
10381     *
10382     * @throws CmsException if operation was not successful
10383     *
10384     * @see org.opencms.security.CmsOrgUnitManager#writeOrganizationalUnit(CmsObject, CmsOrganizationalUnit)
10385     */
10386    public void writeOrganizationalUnit(CmsDbContext dbc, CmsOrganizationalUnit organizationalUnit)
10387    throws CmsException {
10388
10389        m_monitor.uncacheOrgUnit(organizationalUnit);
10390        getUserDriver(dbc).writeOrganizationalUnit(dbc, organizationalUnit);
10391
10392        // create a publish list for the 'virtual' publish event
10393        CmsResource ouRes = readResource(dbc, organizationalUnit.getId(), CmsResourceFilter.DEFAULT);
10394        CmsPublishList pl = new CmsPublishList(ouRes, false);
10395        pl.add(ouRes, false);
10396
10397        getProjectDriver(dbc).writePublishHistory(
10398            dbc,
10399            pl.getPublishHistoryId(),
10400            new CmsPublishedResource(ouRes, -1, CmsResourceState.STATE_NEW));
10401
10402        // fire the 'virtual' publish event
10403        Map<String, Object> eventData = new HashMap<String, Object>();
10404        eventData.put(I_CmsEventListener.KEY_PUBLISHID, pl.getPublishHistoryId().toString());
10405        eventData.put(I_CmsEventListener.KEY_PROJECTID, dbc.currentProject().getUuid());
10406        eventData.put(I_CmsEventListener.KEY_DBCONTEXT, dbc);
10407        CmsEvent afterPublishEvent = new CmsEvent(I_CmsEventListener.EVENT_PUBLISH_PROJECT, eventData);
10408        OpenCms.fireCmsEvent(afterPublishEvent);
10409
10410        m_monitor.cacheOrgUnit(organizationalUnit);
10411    }
10412
10413    /**
10414     * Writes an already existing project.<p>
10415     *
10416     * The project id has to be a valid OpenCms project id.<br>
10417     *
10418     * The project with the given id will be completely overridden
10419     * by the given data.<p>
10420     *
10421     * @param dbc the current database context
10422     * @param project the project that should be written
10423     *
10424     * @throws CmsException if operation was not successful
10425     */
10426    public void writeProject(CmsDbContext dbc, CmsProject project) throws CmsException {
10427
10428        m_monitor.uncacheProject(project);
10429        getProjectDriver(dbc).writeProject(dbc, project);
10430        m_monitor.cacheProject(project);
10431    }
10432
10433    /**
10434     * Writes a new project into the PROJECT_LASTMODIFIED field of a resource record.<p>
10435     *
10436     * @param dbc the current database context
10437     * @param resource the resource which should be modified
10438     * @param projectId the project id to write
10439     *
10440     * @throws CmsDataAccessException if the database access fails
10441     */
10442    public void writeProjectLastModified(CmsDbContext dbc, CmsResource resource, CmsUUID projectId)
10443    throws CmsDataAccessException {
10444
10445        I_CmsVfsDriver vfsDriver = getVfsDriver(dbc);
10446        vfsDriver.writeLastModifiedProjectId(dbc, dbc.currentProject(), projectId, resource);
10447    }
10448
10449    /**
10450     * Writes a property for a specified resource.<p>
10451     *
10452     * @param dbc the current database context
10453     * @param resource the resource to write the property for
10454     * @param property the property to write
10455     *
10456     * @throws CmsException if something goes wrong
10457     *
10458     * @see CmsObject#writePropertyObject(String, CmsProperty)
10459     * @see I_CmsResourceType#writePropertyObject(CmsObject, CmsSecurityManager, CmsResource, CmsProperty)
10460     */
10461    public void writePropertyObject(CmsDbContext dbc, CmsResource resource, CmsProperty property) throws CmsException {
10462
10463        try {
10464            if (property == CmsProperty.getNullProperty()) {
10465                // skip empty or null properties
10466                return;
10467            }
10468
10469            // test if and what state should be updated
10470            // 0: none, 1: structure, 2: resource
10471            int updateState = getUpdateState(dbc, resource, Collections.singletonList(property));
10472
10473            // write the property
10474            getVfsDriver(dbc).writePropertyObject(dbc, dbc.currentProject(), resource, property);
10475
10476            if (updateState > 0) {
10477                updateState(dbc, resource, updateState == 2);
10478            }
10479            // log it
10480            log(
10481                dbc,
10482                new CmsLogEntry(
10483                    dbc,
10484                    resource.getStructureId(),
10485                    CmsLogEntryType.RESOURCE_PROPERTIES,
10486                    new String[] {resource.getRootPath()}),
10487                false);
10488
10489        } finally {
10490            // update the driver manager cache
10491            m_monitor.clearResourceCache();
10492            m_monitor.flushCache(CmsMemoryMonitor.CacheType.PROPERTY, CmsMemoryMonitor.CacheType.PROPERTY_LIST);
10493
10494            // fire an event that a property of a resource has been modified
10495            Map<String, Object> data = new HashMap<String, Object>();
10496            data.put(I_CmsEventListener.KEY_RESOURCE, resource);
10497            data.put("property", property);
10498            OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_PROPERTY_MODIFIED, data));
10499        }
10500    }
10501
10502    /**
10503     * Writes a list of properties for a specified resource.<p>
10504     *
10505     * Code calling this method has to ensure that the no properties
10506     * <code>a, b</code> are contained in the specified list so that <code>a.equals(b)</code>,
10507     * otherwise an exception is thrown.<p>
10508     *
10509     * @param dbc the current database context
10510     * @param resource the resource to write the properties for
10511     * @param properties the list of properties to write
10512     * @param updateState if <code>true</code> the state of the resource will be updated
10513     *
10514     * @throws CmsException if something goes wrong
10515     *
10516     * @see CmsObject#writePropertyObjects(String, List)
10517     * @see I_CmsResourceType#writePropertyObjects(CmsObject, CmsSecurityManager, CmsResource, List)
10518     */
10519    public void writePropertyObjects(
10520        CmsDbContext dbc,
10521        CmsResource resource,
10522        List<CmsProperty> properties,
10523        boolean updateState)
10524    throws CmsException {
10525
10526        if ((properties == null) || (properties.size() == 0)) {
10527            // skip empty or null lists
10528            return;
10529        }
10530
10531        try {
10532            // the specified list must not contain two or more equal property objects
10533            for (int i = 0, n = properties.size(); i < n; i++) {
10534                Set<String> keyValidationSet = new HashSet<String>();
10535                CmsProperty property = properties.get(i);
10536                if (!keyValidationSet.contains(property.getName())) {
10537                    keyValidationSet.add(property.getName());
10538                } else {
10539                    throw new CmsVfsException(
10540                        Messages.get().container(Messages.ERR_VFS_INVALID_PROPERTY_LIST_1, property.getName()));
10541                }
10542            }
10543
10544            // test if and what state should be updated
10545            // 0: none, 1: structure, 2: resource
10546            int updateStateValue = 0;
10547            if (updateState) {
10548                updateStateValue = getUpdateState(dbc, resource, properties);
10549            }
10550            I_CmsVfsDriver vfsDriver = getVfsDriver(dbc);
10551            for (int i = 0; i < properties.size(); i++) {
10552                // write the property
10553                CmsProperty property = properties.get(i);
10554                vfsDriver.writePropertyObject(dbc, dbc.currentProject(), resource, property);
10555            }
10556
10557            if (updateStateValue > 0) {
10558                // update state
10559                updateState(dbc, resource, (updateStateValue == 2));
10560            }
10561
10562            if (updateState) {
10563                // log it
10564                log(
10565                    dbc,
10566                    new CmsLogEntry(
10567                        dbc,
10568                        resource.getStructureId(),
10569                        CmsLogEntryType.RESOURCE_PROPERTIES,
10570                        new String[] {resource.getRootPath()}),
10571                    false);
10572            }
10573        } finally {
10574            // update the driver manager cache
10575            m_monitor.clearResourceCache();
10576            m_monitor.flushCache(CmsMemoryMonitor.CacheType.PROPERTY, CmsMemoryMonitor.CacheType.PROPERTY_LIST);
10577
10578            // fire an event that the properties of a resource have been modified
10579            OpenCms.fireCmsEvent(
10580                new CmsEvent(
10581                    I_CmsEventListener.EVENT_RESOURCE_AND_PROPERTIES_MODIFIED,
10582                    Collections.<String, Object> singletonMap(I_CmsEventListener.KEY_RESOURCE, resource)));
10583        }
10584    }
10585
10586    /**
10587     * Updates a publish job.<p>
10588     *
10589     * @param dbc the current database context
10590     * @param publishJob the publish job to update
10591     *
10592     * @throws CmsException if something goes wrong
10593     */
10594    public void writePublishJob(CmsDbContext dbc, CmsPublishJobInfoBean publishJob) throws CmsException {
10595
10596        getProjectDriver(dbc).writePublishJob(dbc, publishJob);
10597    }
10598
10599    /**
10600     * Writes the publish report for a publish job.<p>
10601     *
10602     * @param dbc the current database context
10603     * @param publishJob the publish job
10604     * @throws CmsException if something goes wrong
10605     */
10606    public void writePublishReport(CmsDbContext dbc, CmsPublishJobInfoBean publishJob) throws CmsException {
10607
10608        CmsPublishReport report = (CmsPublishReport)publishJob.removePublishReport();
10609
10610        if (report != null) {
10611            getProjectDriver(dbc).writePublishReport(dbc, publishJob.getPublishHistoryId(), report.getContents());
10612        }
10613    }
10614
10615    /**
10616     * Writes a resource to the OpenCms VFS.<p>
10617     *
10618     * @param dbc the current database context
10619     * @param resource the resource to write
10620     *
10621     * @throws CmsException if something goes wrong
10622     */
10623    public void writeResource(CmsDbContext dbc, CmsResource resource) throws CmsException {
10624
10625        // access was granted - write the resource
10626        resource.setUserLastModified(dbc.currentUser().getId());
10627        CmsUUID projectId = ((dbc.getProjectId() == null) || dbc.getProjectId().isNullUUID())
10628        ? dbc.currentProject().getUuid()
10629        : dbc.getProjectId();
10630
10631        getVfsDriver(dbc).writeResource(dbc, projectId, resource, UPDATE_RESOURCE_STATE);
10632
10633        // make sure the written resource has the state correctly set
10634        if (resource.getState().isUnchanged()) {
10635            resource.setState(CmsResource.STATE_CHANGED);
10636        }
10637
10638        // delete in content relations if the new type is not parseable
10639        if (!(OpenCms.getResourceManager().getResourceType(resource.getTypeId()) instanceof I_CmsLinkParseable)) {
10640            deleteRelationsWithSiblings(dbc, resource);
10641        }
10642
10643        // update the cache
10644        m_monitor.clearResourceCache();
10645        Map<String, Object> data = new HashMap<String, Object>(2);
10646        data.put(I_CmsEventListener.KEY_RESOURCE, resource);
10647        data.put(I_CmsEventListener.KEY_CHANGE, Integer.valueOf(CHANGED_RESOURCE));
10648        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_MODIFIED, data));
10649    }
10650
10651    /**
10652     * Inserts an entry in the published resource table.<p>
10653     *
10654     * This is done during static export.<p>
10655     *
10656     * @param dbc the current database context
10657     * @param resourceName The name of the resource to be added to the static export
10658     * @param linkType the type of resource exported (0= non-parameter, 1=parameter)
10659     * @param linkParameter the parameters added to the resource
10660     * @param timestamp a time stamp for writing the data into the db
10661     *
10662     * @throws CmsException if something goes wrong
10663     */
10664    public void writeStaticExportPublishedResource(
10665        CmsDbContext dbc,
10666        String resourceName,
10667        int linkType,
10668        String linkParameter,
10669        long timestamp)
10670    throws CmsException {
10671
10672        getProjectDriver(dbc).writeStaticExportPublishedResource(dbc, resourceName, linkType, linkParameter, timestamp);
10673    }
10674
10675    /**
10676     * Adds a new url name mapping for a structure id.<p>
10677     *
10678     * Instead of taking the name directly, this method takes an iterator of strings
10679     * which generates candidate URL names on-the-fly. The first generated name which is
10680     * not already mapped to another structure id will be chosen for the new URL name mapping.
10681     *
10682     * @param dbc the current database context
10683     * @param nameSeq the sequence of URL name candidates
10684     * @param structureId the structure id to which the url name should be mapped
10685     * @param locale the locale for which the mapping should be written
10686     * @param replaceOnPublish name mappings for which this is set will replace all other mappings for the same resource on publishing
10687     *
10688     * @return the actual name which was mapped to the structure id
10689     *
10690     * @throws CmsDataAccessException if something goes wrong
10691     */
10692    public String writeUrlNameMapping(
10693        CmsDbContext dbc,
10694        Iterator<String> nameSeq,
10695        CmsUUID structureId,
10696        String locale,
10697        boolean replaceOnPublish)
10698    throws CmsDataAccessException {
10699
10700        String bestName = findBestNameForUrlNameMapping(dbc, nameSeq, structureId, locale);
10701        addOrReplaceUrlNameMapping(dbc, bestName, structureId, locale, replaceOnPublish);
10702        return bestName;
10703    }
10704
10705    /**
10706     * Updates the user information. <p>
10707     *
10708     * The user id has to be a valid OpenCms user id.<br>
10709     *
10710     * The user with the given id will be completely overridden
10711     * by the given data.<p>
10712     *
10713     * @param dbc the current database context
10714     * @param user the user to be updated
10715     *
10716     * @throws CmsException if operation was not successful
10717     */
10718    public void writeUser(CmsDbContext dbc, CmsUser user) throws CmsException {
10719
10720        CmsUser oldUser = readUser(dbc, user.getId());
10721        m_monitor.clearUserCache(oldUser);
10722        getUserDriver(dbc).writeUser(dbc, user);
10723        m_monitor.flushCache(CmsMemoryMonitor.CacheType.USER_LIST);
10724
10725        if (!dbc.getProjectId().isNullUUID()) {
10726            // user modified event is not needed
10727            return;
10728        }
10729        // fire user modified event
10730        Map<String, Object> eventData = new HashMap<String, Object>();
10731        eventData.put(I_CmsEventListener.KEY_USER_ID, user.getId().toString());
10732        eventData.put(I_CmsEventListener.KEY_USER_NAME, oldUser.getName());
10733        eventData.put(I_CmsEventListener.KEY_USER_ACTION, I_CmsEventListener.VALUE_USER_MODIFIED_ACTION_WRITE_USER);
10734        eventData.put(I_CmsEventListener.KEY_USER_CHANGES, Integer.valueOf(user.getChanges(oldUser)));
10735        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_USER_MODIFIED, eventData));
10736        OpenCms.getTwoFactorAuthenticationHandler().trackUserChange(dbc.getRequestContext(), oldUser, user);
10737    }
10738
10739    /**
10740     * Adds or replaces a new url name mapping in the offline project.<p>
10741     *
10742     * @param dbc the current database context
10743     * @param name the URL name of the mapping
10744     * @param structureId the structure id of the mapping
10745     * @param locale the locale of the mapping
10746     * @param replaceOnPublish if the mapping shoudl replace previous URL name mappings when published
10747     *
10748     * @throws CmsDataAccessException if something goes wrong
10749     */
10750    protected void addOrReplaceUrlNameMapping(
10751        CmsDbContext dbc,
10752        String name,
10753        CmsUUID structureId,
10754        String locale,
10755        boolean replaceOnPublish)
10756    throws CmsDataAccessException {
10757
10758        getVfsDriver(dbc).deleteUrlNameMappingEntries(
10759            dbc,
10760            false,
10761            CmsUrlNameMappingFilter.ALL.filterStructureId(structureId).filterLocale(locale).filterStates(
10762                CmsUrlNameMappingEntry.MAPPING_STATUS_NEW,
10763                CmsUrlNameMappingEntry.MAPPING_STATUS_REPLACE_ON_PUBLISH));
10764        CmsUrlNameMappingEntry newEntry = new CmsUrlNameMappingEntry(
10765            name,
10766            structureId,
10767            replaceOnPublish
10768            ? CmsUrlNameMappingEntry.MAPPING_STATUS_REPLACE_ON_PUBLISH
10769            : CmsUrlNameMappingEntry.MAPPING_STATUS_NEW,
10770            System.currentTimeMillis(),
10771            locale);
10772        getVfsDriver(dbc).addUrlNameMappingEntry(dbc, false, newEntry);
10773    }
10774
10775    /**
10776     * Converts a resource to a folder (if possible).<p>
10777     *
10778     * @param resource the resource to convert
10779     * @return the converted resource
10780     *
10781     * @throws CmsVfsResourceNotFoundException if the resource is not a folder
10782     */
10783    protected CmsFolder convertResourceToFolder(CmsResource resource) throws CmsVfsResourceNotFoundException {
10784
10785        if (resource.isFolder()) {
10786            return new CmsFolder(resource);
10787        }
10788
10789        throw new CmsVfsResourceNotFoundException(
10790            Messages.get().container(Messages.ERR_ACCESS_FILE_AS_FOLDER_1, resource.getRootPath()));
10791    }
10792
10793    /**
10794     * Helper method for creating a driver from configuration data.<p>
10795     *
10796     * @param dbc the db context
10797     * @param configManager the configuration manager
10798     * @param config the configuration
10799     * @param driverChainKey the configuration key under which the driver chain is stored
10800     * @param suffix the suffix to append to a driver chain entry to get the key for the driver class
10801     *
10802     * @return the newly created driver
10803     */
10804    protected Object createDriver(
10805        CmsDbContext dbc,
10806        CmsConfigurationManager configManager,
10807        CmsParameterConfiguration config,
10808        String driverChainKey,
10809        String suffix) {
10810
10811        // read the vfs driver class properties and initialize a new instance
10812        List<String> drivers = config.getList(driverChainKey);
10813        String driverKey = drivers.get(0) + suffix;
10814        String driverName = config.get(driverKey);
10815        drivers = (drivers.size() > 1) ? drivers.subList(1, drivers.size()) : null;
10816        if (driverName == null) {
10817            CmsLog.INIT.error(Messages.get().getBundle().key(Messages.INIT_DRIVER_FAILED_1, driverKey));
10818        }
10819        Object result = newDriverInstance(dbc, configManager, driverName, drivers);
10820        if ("true".equalsIgnoreCase(System.getProperty("opencms.profile.drivers"))) {
10821            result = wrapDriverInProfilingProxy(result);
10822        }
10823        return result;
10824    }
10825
10826    /**
10827     * Deletes all relations for the given resource and all its siblings.<p>
10828     *
10829     * @param dbc the current database context
10830     * @param resource the resource to delete the resource for
10831     *
10832     * @throws CmsException if something goes wrong
10833     */
10834    protected void deleteRelationsWithSiblings(CmsDbContext dbc, CmsResource resource) throws CmsException {
10835
10836        // get all siblings
10837        List<CmsResource> siblings;
10838        if (resource.getSiblingCount() > 1) {
10839            siblings = readSiblings(dbc, resource, CmsResourceFilter.ALL);
10840        } else {
10841            siblings = new ArrayList<CmsResource>();
10842            siblings.add(resource);
10843        }
10844        // clean the relations in content for all siblings
10845        I_CmsVfsDriver vfsDriver = getVfsDriver(dbc);
10846        Iterator<CmsResource> it = siblings.iterator();
10847        while (it.hasNext()) {
10848            CmsResource sibling = it.next();
10849            // clean the relation information for this sibling
10850            vfsDriver.deleteRelations(
10851                dbc,
10852                dbc.currentProject().getUuid(),
10853                sibling,
10854                CmsRelationFilter.TARGETS.filterDefinedInContent());
10855        }
10856    }
10857
10858    /**
10859     * Tries to add sub-resources of moved folders to the publish list and throws an exception if the publish list still does
10860     * not contain some  sub-resources of the moved folders.<p>
10861     *
10862     * @param cms the current CMS context
10863     * @param dbc the current database context
10864     * @param pubList the publish list
10865     * @throws CmsException if something goes wrong
10866     */
10867    protected void ensureSubResourcesOfMovedFoldersPublished(CmsObject cms, CmsDbContext dbc, CmsPublishList pubList)
10868    throws CmsException {
10869
10870        List<CmsResource> topMovedFolders = pubList.getTopMovedFolders(cms);
10871        Iterator<CmsResource> folderIt = topMovedFolders.iterator();
10872        while (folderIt.hasNext()) {
10873            CmsResource folder = folderIt.next();
10874            addSubResources(dbc, pubList, folder, resource -> !resource.getState().isNew());
10875        }
10876        List<CmsResource> missingSubResources = pubList.getMissingSubResources(cms, topMovedFolders);
10877        if (missingSubResources.isEmpty()) {
10878            return;
10879        }
10880
10881        StringBuffer pathBuffer = new StringBuffer();
10882
10883        for (CmsResource missing : missingSubResources) {
10884            pathBuffer.append(missing.getRootPath());
10885            pathBuffer.append(" ");
10886        }
10887        throw new CmsVfsException(
10888            Messages.get().container(Messages.RPT_CHILDREN_OF_MOVED_FOLDER_NOT_PUBLISHED_1, pathBuffer.toString()));
10889
10890    }
10891
10892    /**
10893     * Tries to find the best name for an URL name mapping for the given structure id.<p>
10894     *
10895     * @param dbc the database context
10896     * @param nameSeq the sequence of name candidates
10897     * @param structureId the structure id to which an URL name should be mapped
10898     * @param locale the locale for which the URL name should be mapped
10899     *
10900     * @return the selected URL name candidate
10901     *
10902     * @throws CmsDataAccessException if something goes wrong
10903     */
10904    protected String findBestNameForUrlNameMapping(
10905        CmsDbContext dbc,
10906        Iterator<String> nameSeq,
10907        CmsUUID structureId,
10908        String locale)
10909    throws CmsDataAccessException {
10910
10911        String newName;
10912        boolean alreadyInUse;
10913        do {
10914            newName = nameSeq.next();
10915            alreadyInUse = false;
10916            CmsUrlNameMappingFilter filter = CmsUrlNameMappingFilter.ALL.filterName(newName);
10917            List<CmsUrlNameMappingEntry> entriesWithSameName = getVfsDriver(dbc).readUrlNameMappingEntries(
10918                dbc,
10919                false,
10920                filter);
10921            for (CmsUrlNameMappingEntry entry : entriesWithSameName) {
10922                boolean sameId = entry.getStructureId().equals(structureId);
10923                if (!sameId) {
10924                    // name already used for other resource, or for different locale of the same resource
10925                    alreadyInUse = true;
10926                    break;
10927                }
10928            }
10929        } while (alreadyInUse);
10930        return newName;
10931    }
10932
10933    /**
10934     * Helper method for finding the 'best' URL name to use for a new URL name mapping.<p>
10935     *
10936     * Since the name given as a parameter may be already used, this method will try to append numeric suffixes
10937     * to the name to find a mapping name which is not used.<p>
10938     *
10939     * @param dbc the current database context
10940     * @param name the name of the mapping
10941     * @param structureId the structure id to which the name is mapped
10942     *
10943     * @return the best name which was found for the new mapping
10944     *
10945     * @throws CmsDataAccessException if something goes wrong
10946     */
10947    protected String findBestNameForUrlNameMapping(CmsDbContext dbc, String name, CmsUUID structureId)
10948    throws CmsDataAccessException {
10949
10950        List<CmsUrlNameMappingEntry> entriesStartingWithName = getVfsDriver(dbc).readUrlNameMappingEntries(
10951            dbc,
10952            false,
10953            CmsUrlNameMappingFilter.ALL.filterNamePattern(name + "%").filterRejectStructureId(structureId));
10954        Set<String> usedNames = new HashSet<String>();
10955        for (CmsUrlNameMappingEntry entry : entriesStartingWithName) {
10956            usedNames.add(entry.getName());
10957        }
10958        int counter = 0;
10959        String numberedName;
10960        do {
10961            numberedName = getNumberedName(name, counter);
10962            counter += 1;
10963        } while (usedNames.contains(numberedName));
10964        return numberedName;
10965    }
10966
10967    /**
10968     * Returns the lock manager instance.<p>
10969     *
10970     * @return the lock manager instance
10971     */
10972    protected CmsLockManager getLockManager() {
10973
10974        return m_lockManager;
10975    }
10976
10977    /**
10978     * Adds a numeric suffix to the end of a string, unless the number passed as a parameter is 0.<p>
10979     *
10980     * @param name the base name
10981     * @param number the number from which to form the suffix
10982     *
10983     * @return the concatenation of the base name and possibly the numeric suffix
10984     */
10985    protected String getNumberedName(String name, int number) {
10986
10987        if (number == 0) {
10988            return name;
10989        }
10990        PrintfFormat fmt = new PrintfFormat("%0.6d");
10991        return name + "_" + fmt.sprintf(number);
10992    }
10993
10994    /**
10995     * Resets the resources in a project to their online state.<p>
10996     *
10997     * @param dbc the database context
10998     * @param projectId the project id
10999     * @param modifiedFiles the modified files
11000     * @param modifiedFolders the modified folders
11001     * @throws CmsException if something goes wrong
11002     * @throws CmsSecurityException if we don't have the permissions
11003     * @throws CmsDataAccessException if something goes wrong with the database
11004     */
11005    protected void resetResourcesInProject(
11006        CmsDbContext dbc,
11007        CmsUUID projectId,
11008        List<CmsResource> modifiedFiles,
11009        List<CmsResource> modifiedFolders)
11010    throws CmsException, CmsSecurityException, CmsDataAccessException {
11011
11012        // all resources inside the project have to be be reset to their online state.
11013        // 1. step: delete all new files
11014        for (int i = 0; i < modifiedFiles.size(); i++) {
11015            CmsResource currentFile = modifiedFiles.get(i);
11016            if (currentFile.getState().isNew()) {
11017                CmsLock lock = getLock(dbc, currentFile);
11018                if (lock.isNullLock()) {
11019                    // lock the resource
11020                    lockResource(dbc, currentFile, CmsLockType.EXCLUSIVE);
11021                } else if (!lock.isOwnedBy(dbc.currentUser()) || !lock.isInProject(dbc.currentProject())) {
11022                    changeLock(dbc, currentFile, CmsLockType.EXCLUSIVE);
11023                }
11024                // delete the properties
11025                getVfsDriver(dbc).deletePropertyObjects(
11026                    dbc,
11027                    projectId,
11028                    currentFile,
11029                    CmsProperty.DELETE_OPTION_DELETE_STRUCTURE_AND_RESOURCE_VALUES);
11030                // delete the file
11031                getVfsDriver(dbc).removeFile(dbc, dbc.currentProject().getUuid(), currentFile);
11032                // remove the access control entries
11033                getUserDriver(dbc).removeAccessControlEntries(dbc, dbc.currentProject(), currentFile.getResourceId());
11034                // fire the corresponding event
11035                OpenCms.fireCmsEvent(
11036                    new CmsEvent(
11037                        I_CmsEventListener.EVENT_RESOURCE_AND_PROPERTIES_MODIFIED,
11038                        Collections.<String, Object> singletonMap(I_CmsEventListener.KEY_RESOURCE, currentFile)));
11039            }
11040        }
11041
11042        // 2. step: delete all new folders
11043        for (int i = 0; i < modifiedFolders.size(); i++) {
11044            CmsResource currentFolder = modifiedFolders.get(i);
11045            if (currentFolder.getState().isNew()) {
11046                // delete the properties
11047                getVfsDriver(dbc).deletePropertyObjects(
11048                    dbc,
11049                    projectId,
11050                    currentFolder,
11051                    CmsProperty.DELETE_OPTION_DELETE_STRUCTURE_AND_RESOURCE_VALUES);
11052                // delete the folder
11053                getVfsDriver(dbc).removeFolder(dbc, dbc.currentProject(), currentFolder);
11054                // remove the access control entries
11055                getUserDriver(dbc).removeAccessControlEntries(dbc, dbc.currentProject(), currentFolder.getResourceId());
11056                // fire the corresponding event
11057                OpenCms.fireCmsEvent(
11058                    new CmsEvent(
11059                        I_CmsEventListener.EVENT_RESOURCE_AND_PROPERTIES_MODIFIED,
11060                        Collections.<String, Object> singletonMap(I_CmsEventListener.KEY_RESOURCE, currentFolder)));
11061            }
11062        }
11063
11064        // 3. step: undo changes on all changed or deleted folders
11065        for (int i = 0; i < modifiedFolders.size(); i++) {
11066            CmsResource currentFolder = modifiedFolders.get(i);
11067            if ((currentFolder.getState().isChanged()) || (currentFolder.getState().isDeleted())) {
11068                CmsLock lock = getLock(dbc, currentFolder);
11069                if (lock.isNullLock()) {
11070                    // lock the resource
11071                    lockResource(dbc, currentFolder, CmsLockType.EXCLUSIVE);
11072                } else if (!lock.isOwnedBy(dbc.currentUser()) || !lock.isInProject(dbc.currentProject())) {
11073                    changeLock(dbc, currentFolder, CmsLockType.EXCLUSIVE);
11074                }
11075                // undo all changes in the folder
11076                undoChanges(dbc, currentFolder, CmsResource.UNDO_CONTENT);
11077                // fire the corresponding event
11078                OpenCms.fireCmsEvent(
11079                    new CmsEvent(
11080                        I_CmsEventListener.EVENT_RESOURCE_AND_PROPERTIES_MODIFIED,
11081                        Collections.<String, Object> singletonMap(I_CmsEventListener.KEY_RESOURCE, currentFolder)));
11082            }
11083        }
11084
11085        // 4. step: undo changes on all changed or deleted files
11086        for (int i = 0; i < modifiedFiles.size(); i++) {
11087            CmsResource currentFile = modifiedFiles.get(i);
11088            if (currentFile.getState().isChanged() || currentFile.getState().isDeleted()) {
11089                CmsLock lock = getLock(dbc, currentFile);
11090                if (lock.isNullLock()) {
11091                    // lock the resource
11092                    lockResource(dbc, currentFile, CmsLockType.EXCLUSIVE);
11093                } else if (!lock.isOwnedInProjectBy(dbc.currentUser(), dbc.currentProject())) {
11094                    if (lock.isLockableBy(dbc.currentUser())) {
11095                        changeLock(dbc, currentFile, CmsLockType.EXCLUSIVE);
11096                    }
11097                }
11098                // undo all changes in the file
11099                undoChanges(dbc, currentFile, CmsResource.UNDO_CONTENT);
11100                // fire the corresponding event
11101                OpenCms.fireCmsEvent(
11102                    new CmsEvent(
11103                        I_CmsEventListener.EVENT_RESOURCE_AND_PROPERTIES_MODIFIED,
11104                        Collections.<String, Object> singletonMap(I_CmsEventListener.KEY_RESOURCE, currentFile)));
11105            }
11106        }
11107    }
11108
11109    /**
11110     * Counts the total number of users which fit the given criteria.<p>
11111     *
11112     * @param dbc the database context
11113     * @param searchParams the user search criteria
11114     *
11115     * @return the total number of users matching the criteria
11116     *
11117     * @throws CmsDataAccessException if something goes wrong
11118     */
11119    long countUsers(CmsDbContext dbc, CmsUserSearchParameters searchParams) throws CmsDataAccessException {
11120
11121        return getUserDriver(dbc).countUsers(dbc, searchParams);
11122    }
11123
11124    /**
11125     * Adds a pool to the static pool map.<p>
11126     *
11127     * @param pool the pool to add
11128     */
11129    private void addPool(CmsDbPoolV11 pool) {
11130
11131        m_pools.put(pool.getPoolUrl(), pool);
11132    }
11133
11134    /**
11135     * Adds all sub-resources of the given resource to the publish list.<p>
11136     *
11137     * @param dbc the database context
11138     * @param publishList the publish list
11139     * @param directPublishResource the resource to get the sub-resources for
11140     * @param additionalFilter an additional test for resources to pass before they are added to the publish list
11141     *
11142     * @throws CmsDataAccessException if something goes wrong accessing the database
11143     */
11144    private void addSubResources(
11145        CmsDbContext dbc,
11146        CmsPublishList publishList,
11147        CmsResource directPublishResource,
11148        Predicate<CmsResource> additionalFilter)
11149    throws CmsDataAccessException {
11150
11151        int flags = CmsDriverManager.READMODE_INCLUDE_TREE | CmsDriverManager.READMODE_EXCLUDE_STATE;
11152        if (!directPublishResource.getState().isDeleted()) {
11153            // fix for org.opencms.file.TestPublishIssues#testPublishFolderWithDeletedFileFromOtherProject
11154            flags = flags | CmsDriverManager.READMODE_INCLUDE_PROJECT;
11155        }
11156
11157        // add all sub resources of the folder
11158        List<CmsResource> folderList = getVfsDriver(dbc).readResourceTree(
11159            dbc,
11160            dbc.currentProject().getUuid(),
11161            directPublishResource.getRootPath(),
11162            CmsDriverManager.READ_IGNORE_TYPE,
11163            CmsResource.STATE_UNCHANGED,
11164            CmsDriverManager.READ_IGNORE_TIME,
11165            CmsDriverManager.READ_IGNORE_TIME,
11166            CmsDriverManager.READ_IGNORE_TIME,
11167            CmsDriverManager.READ_IGNORE_TIME,
11168            CmsDriverManager.READ_IGNORE_TIME,
11169            CmsDriverManager.READ_IGNORE_TIME,
11170            flags | CmsDriverManager.READMODE_ONLY_FOLDERS);
11171
11172        publishList.addAll(
11173            filterResources(dbc, publishList, folderList).stream().filter(additionalFilter).collect(
11174                Collectors.toList()),
11175            true);
11176
11177        List<CmsResource> fileList = getVfsDriver(dbc).readResourceTree(
11178            dbc,
11179            dbc.currentProject().getUuid(),
11180            directPublishResource.getRootPath(),
11181            CmsDriverManager.READ_IGNORE_TYPE,
11182            CmsResource.STATE_UNCHANGED,
11183            CmsDriverManager.READ_IGNORE_TIME,
11184            CmsDriverManager.READ_IGNORE_TIME,
11185            CmsDriverManager.READ_IGNORE_TIME,
11186            CmsDriverManager.READ_IGNORE_TIME,
11187            CmsDriverManager.READ_IGNORE_TIME,
11188            CmsDriverManager.READ_IGNORE_TIME,
11189            flags | CmsDriverManager.READMODE_ONLY_FILES);
11190
11191        publishList.addAll(
11192            filterResources(dbc, publishList, fileList).stream().filter(additionalFilter).collect(Collectors.toList()),
11193            true);
11194    }
11195
11196    /**
11197     * Helper method to check whether we should bother with reading the group for a given role in a given OU.<p>
11198     *
11199     * 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.
11200     *
11201     * @param ou the OU
11202     * @param role the role
11203     * @return true if we should read the role in the OU
11204     */
11205    private boolean canReadRoleInOu(CmsOrganizationalUnit ou, CmsRole role) {
11206
11207        if (ou.hasFlagWebuser() && !role.getRoleName().equals(CmsRole.ACCOUNT_MANAGER.getRoleName())) {
11208            return false;
11209        }
11210        return true;
11211    }
11212
11213    /**
11214     * Checks the parent of a resource during publishing.<p>
11215     *
11216     * @param dbc the current database context
11217     * @param deletedFolders a list of deleted folders
11218     * @param res a resource to check the parent for
11219     *
11220     * @return <code>true</code> if the parent resource will be deleted during publishing
11221     */
11222    private boolean checkDeletedParentFolder(CmsDbContext dbc, List<CmsResource> deletedFolders, CmsResource res) {
11223
11224        String parentPath = CmsResource.getParentFolder(res.getRootPath());
11225
11226        if (parentPath == null) {
11227            // resource has no parent
11228            return false;
11229        }
11230
11231        CmsResource parent;
11232        try {
11233            parent = readResource(dbc, parentPath, CmsResourceFilter.ALL);
11234        } catch (Exception e) {
11235            // failure: if we cannot read the parent, we should not publish the resource
11236            return false;
11237        }
11238
11239        if (!parent.getState().isDeleted()) {
11240            // parent is not deleted
11241            return false;
11242        }
11243
11244        for (int j = 0; j < deletedFolders.size(); j++) {
11245            if ((deletedFolders.get(j)).getStructureId().equals(parent.getStructureId())) {
11246                // parent is deleted, and it will get published
11247                return true;
11248            }
11249        }
11250
11251        // parent is new, but it will not get published
11252        return false;
11253    }
11254
11255    /**
11256     * Checks that no one of the resources to be published has a 'new' parent (that has not been published yet).<p>
11257     *
11258     * @param dbc the db context
11259     * @param publishList the publish list to check
11260     *
11261     * @throws CmsVfsException if there is a resource to be published with a 'new' parent
11262     */
11263    private void checkParentFolders(CmsDbContext dbc, CmsPublishList publishList) throws CmsVfsException {
11264
11265        boolean directPublish = publishList.isDirectPublish();
11266        // if we direct publish a file, check if all parent folders are already published
11267        if (directPublish) {
11268            // first get the names of all parent folders
11269            Iterator<CmsResource> it = publishList.getDirectPublishResources().iterator();
11270            List<String> parentFolderNames = new ArrayList<String>();
11271            while (it.hasNext()) {
11272                CmsResource res = it.next();
11273                String parentFolderName = CmsResource.getParentFolder(res.getRootPath());
11274                if (parentFolderName != null) {
11275                    parentFolderNames.add(parentFolderName);
11276                }
11277            }
11278            // remove duplicate parent folder names
11279            parentFolderNames = CmsFileUtil.removeRedundancies(parentFolderNames);
11280            String parentFolderName = null;
11281            try {
11282                I_CmsVfsDriver vfsDriver = getVfsDriver(dbc);
11283                // now check all folders if they exist in the online project
11284                Iterator<String> parentIt = parentFolderNames.iterator();
11285                while (parentIt.hasNext()) {
11286                    parentFolderName = parentIt.next();
11287                    vfsDriver.readFolder(dbc, CmsProject.ONLINE_PROJECT_ID, parentFolderName);
11288                }
11289            } catch (CmsException e) {
11290                throw new CmsVfsException(
11291                    Messages.get().container(Messages.RPT_PARENT_FOLDER_NOT_PUBLISHED_1, parentFolderName));
11292            }
11293        }
11294    }
11295
11296    /**
11297     * Checks the parent of a resource during publishing.<p>
11298     *
11299     * @param dbc the current database context
11300     * @param folderList a list of folders
11301     * @param res a resource to check the parent for
11302     *
11303     * @return true if the resource should be published
11304     */
11305    private boolean checkParentResource(CmsDbContext dbc, List<CmsResource> folderList, CmsResource res) {
11306
11307        String parentPath = CmsResource.getParentFolder(res.getRootPath());
11308
11309        if (parentPath == null) {
11310            // resource has no parent
11311            return true;
11312        }
11313
11314        CmsResource parent;
11315        try {
11316            parent = readResource(dbc, parentPath, CmsResourceFilter.ALL);
11317        } catch (Exception e) {
11318            // failure: if we cannot read the parent, we should not publish the resource
11319            return false;
11320        }
11321
11322        if (!parent.getState().isNew()) {
11323            // parent is already published
11324            return true;
11325        }
11326
11327        for (int j = 0; j < folderList.size(); j++) {
11328            if (folderList.get(j).getStructureId().equals(parent.getStructureId())) {
11329                // parent is new, but it will get published
11330                return true;
11331            }
11332        }
11333
11334        // parent is new, but it will not get published
11335        return false;
11336    }
11337
11338    /**
11339     * Copies all relations from the source resource to the target resource.<p>
11340     *
11341     * @param dbc the database context
11342     * @param source the source
11343     * @param target the target
11344     *
11345     * @throws CmsException if something goes wrong
11346     */
11347    private void copyRelations(CmsDbContext dbc, CmsResource source, CmsResource target) throws CmsException {
11348
11349        // copy relations all relations
11350        CmsObject cms = new CmsObject(getSecurityManager(), dbc.getRequestContext());
11351        Iterator<CmsRelation> itRelations = getRelationsForResource(
11352            dbc,
11353            source,
11354            CmsRelationFilter.TARGETS.filterNotDefinedInContent()).iterator();
11355        while (itRelations.hasNext()) {
11356            CmsRelation relation = itRelations.next();
11357            if (relation.getType().getCopyBehavior() == CopyBehavior.copy) {
11358                try {
11359                    CmsResource relTarget = relation.getTarget(cms, CmsResourceFilter.ALL);
11360                    addRelationToResource(dbc, target, relTarget, relation.getType(), true);
11361                } catch (CmsVfsResourceNotFoundException e) {
11362                    // ignore this broken relation
11363                    if (LOG.isWarnEnabled()) {
11364                        LOG.warn(e.getLocalizedMessage(), e);
11365                    }
11366                }
11367            }
11368        }
11369        // repair categories
11370        repairCategories(dbc, getProjectIdForContext(dbc), target);
11371    }
11372
11373    /**
11374     * Filters the given list of resources, removes all resources where the current user
11375     * does not have READ permissions, plus the filter is applied.<p>
11376     *
11377     * @param dbc the current database context
11378     * @param resourceList a list of CmsResources
11379     * @param filter the resource filter to use
11380     *
11381     * @return the filtered list of resources
11382     *
11383     * @throws CmsException in case errors testing the permissions
11384     */
11385    private List<CmsResource> filterPermissions(
11386        CmsDbContext dbc,
11387        List<CmsResource> resourceList,
11388        CmsResourceFilter filter)
11389    throws CmsException {
11390
11391        if (filter.requireTimerange()) {
11392            // never check time range here - this must be done later in #updateContextDates(...)
11393            filter = filter.addExcludeTimerange();
11394        }
11395        ResourceListWithCacheability result = new ResourceListWithCacheability();
11396        boolean nocacheWasSet = false;
11397        if (null == dbc.getAttribute(ATTR_PERMISSION_NOCACHE)) {
11398            // The attribute will be used by the permission handler to tell us that lists containing the resource
11399            // should not be cached.
11400            dbc.setAttribute(ATTR_PERMISSION_NOCACHE, new boolean[] {false});
11401            // insurance against potential indirect recursive calls introduced by future code changes:
11402            // make sure we only remove the attribute later if we were the one who set it
11403            nocacheWasSet = true;
11404        }
11405        try {
11406            for (int i = 0; i < resourceList.size(); i++) {
11407                // check the permission of all resources
11408                CmsResource currentResource = resourceList.get(i);
11409                if (m_securityManager.hasPermissions(
11410                    dbc,
11411                    currentResource,
11412                    CmsPermissionSet.ACCESS_READ,
11413                    LockCheck.yes,
11414                    filter).isAllowed()) {
11415                    // only return resources where permission was granted
11416                    result.add(currentResource);
11417                }
11418            }
11419        } finally {
11420            if (nocacheWasSet) {
11421                boolean[] nocache = (boolean[])dbc.getAttribute(ATTR_PERMISSION_NOCACHE);
11422                if (nocache != null) {
11423                    dbc.removeAttribute(ATTR_PERMISSION_NOCACHE);
11424                    if (nocache[0]) {
11425                        result.setCacheable(false);
11426                    }
11427                }
11428            }
11429        }
11430        // return the result
11431        return result;
11432    }
11433
11434    /**
11435     * Returns a filtered list of resources for publishing.<p>
11436     * Contains all resources, which are not locked
11437     * and which have a parent folder that is already published or will be published, too.<p>
11438     *
11439     * @param dbc the current database context
11440     * @param publishList the filling publish list
11441     * @param resourceList the list of resources to filter
11442     *
11443     * @return a filtered list of resources
11444     */
11445    private List<CmsResource> filterResources(
11446        CmsDbContext dbc,
11447        CmsPublishList publishList,
11448        List<CmsResource> resourceList) {
11449
11450        List<CmsResource> result = new ArrayList<CmsResource>();
11451
11452        // local folder list for adding new publishing subfolders
11453        // this solves the {@link org.opencms.file.TestPublishIssues#testPublishScenarioD} problem.
11454        List<CmsResource> newFolderList = new ArrayList<CmsResource>(
11455            publishList == null ? resourceList : publishList.getFolderList());
11456
11457        for (int i = 0; i < resourceList.size(); i++) {
11458            CmsResource res = resourceList.get(i);
11459            try {
11460                CmsLock lock = getLock(dbc, res);
11461                if (lock.isPublish()) {
11462                    // if already enqueued
11463                    continue;
11464                }
11465                if (!lock.isLockableBy(dbc.currentUser())) {
11466                    // checks if there is a shared lock and if the resource is deleted
11467                    // this solves the {@link org.opencms.file.TestPublishIssues#testPublishScenarioE} problem.
11468                    if (lock.isShared() && (publishList != null)) {
11469                        if (!res.getState().isDeleted()
11470                            || !checkDeletedParentFolder(dbc, publishList.getDeletedFolderList(), res)) {
11471                            continue;
11472                        }
11473                    } else {
11474                        // don't add locked resources
11475                        continue;
11476                    }
11477                }
11478                if (!"/".equals(res.getRootPath()) && !checkParentResource(dbc, newFolderList, res)) {
11479                    continue;
11480                }
11481                // check permissions
11482                try {
11483                    m_securityManager.checkPermissions(
11484                        dbc,
11485                        res,
11486                        CmsPermissionSet.ACCESS_DIRECT_PUBLISH,
11487                        false,
11488                        CmsResourceFilter.ALL);
11489                } catch (CmsException e) {
11490                    // skip if not enough permissions
11491                    continue;
11492                }
11493                if (res.isFolder()) {
11494                    newFolderList.add(res);
11495                }
11496                result.add(res);
11497            } catch (Exception e) {
11498                // should never happen
11499                LOG.error(e.getLocalizedMessage(), e);
11500            }
11501        }
11502        return result;
11503    }
11504
11505    /**
11506     * Returns a filtered list of sibling resources for publishing.<p>
11507     *
11508     * Contains all siblings of the given resources, which are not locked
11509     * and which have a parent folder that is already published or will be published, too.<p>
11510     *
11511     * @param dbc the current database context
11512     * @param publishList the unfinished publish list
11513     * @param resourceList the list of siblings to filter
11514     *
11515     * @return a filtered list of sibling resources for publishing
11516     */
11517    private List<CmsResource> filterSiblings(
11518        CmsDbContext dbc,
11519        CmsPublishList publishList,
11520        Collection<CmsResource> resourceList) {
11521
11522        List<CmsResource> result = new ArrayList<CmsResource>();
11523
11524        // removed internal extendible folder list, since iterated (sibling) resources are files in any case, never folders
11525
11526        for (CmsResource res : resourceList) {
11527            try {
11528                CmsLock lock = getLock(dbc, res);
11529                if (lock.isPublish()) {
11530                    // if already enqueued
11531                    continue;
11532                }
11533                if (!lock.isLockableBy(dbc.currentUser())) {
11534                    // checks if there is a shared lock and if the resource is deleted
11535                    // this solves the {@link org.opencms.file.TestPublishIssues#testPublishScenarioE} problem.
11536                    if (lock.isShared() && (publishList != null)) {
11537                        if (!res.getState().isDeleted()
11538                            || !checkDeletedParentFolder(dbc, publishList.getDeletedFolderList(), res)) {
11539                            continue;
11540                        }
11541                    } else {
11542                        // don't add locked resources
11543                        continue;
11544                    }
11545                }
11546                if (!"/".equals(res.getRootPath()) && !checkParentResource(dbc, publishList.getFolderList(), res)) {
11547                    // don't add resources that have no parent in the online project
11548                    continue;
11549                }
11550                // check permissions
11551                try {
11552                    m_securityManager.checkPermissions(
11553                        dbc,
11554                        res,
11555                        CmsPermissionSet.ACCESS_DIRECT_PUBLISH,
11556                        false,
11557                        CmsResourceFilter.ALL);
11558                } catch (CmsException e) {
11559                    // skip if not enough permissions
11560                    continue;
11561                }
11562                result.add(res);
11563            } catch (Exception e) {
11564                // should never happen
11565                LOG.error(e.getLocalizedMessage(), e);
11566            }
11567        }
11568        return result;
11569    }
11570
11571    /**
11572     * Returns the access control list of a given resource.<p>
11573     *
11574     * @param dbc the current database context
11575     * @param resource the resource
11576     * @param forFolder should be true if resource is a folder
11577     * @param depth the depth to include non-inherited access entries, also
11578     * @param inheritedOnly flag indicates to collect inherited permissions only
11579     *
11580     * @return the access control list of the resource
11581     *
11582     * @throws CmsException if something goes wrong
11583     */
11584    private CmsAccessControlList getAccessControlList(
11585        CmsDbContext dbc,
11586        CmsResource resource,
11587        boolean inheritedOnly,
11588        boolean forFolder,
11589        int depth)
11590    throws CmsException {
11591
11592        String cacheKey = getCacheKey(
11593            new String[] {
11594                inheritedOnly ? "+" : "-",
11595                forFolder ? "+" : "-",
11596                Integer.toString(depth),
11597                resource.getStructureId().toString()},
11598            dbc);
11599
11600        CmsAccessControlList acl = m_monitor.getCachedACL(cacheKey);
11601
11602        // return the cached acl if already available
11603        if ((acl != null) && dbc.getProjectId().isNullUUID()) {
11604            return acl;
11605        }
11606
11607        List<CmsAccessControlEntry> aces = getUserDriver(dbc).readAccessControlEntries(
11608            dbc,
11609            dbc.currentProject(),
11610            resource.getResourceId(),
11611            (depth > 1) || ((depth > 0) && forFolder));
11612
11613        // sort the list of aces
11614        boolean overwriteAll = sortAceList(aces);
11615
11616        // if no 'overwrite all' ace was found
11617        if (!overwriteAll) {
11618            // get the acl of the parent
11619            CmsResource parentResource = null;
11620            try {
11621                // try to recurse over the id
11622                parentResource = getVfsDriver(dbc).readParentFolder(
11623                    dbc,
11624                    dbc.currentProject().getUuid(),
11625                    resource.getStructureId());
11626            } catch (CmsVfsResourceNotFoundException e) {
11627                // should never happen, but try with the path
11628                String parentPath = CmsResource.getParentFolder(resource.getRootPath());
11629                if (parentPath != null) {
11630                    parentResource = getVfsDriver(dbc).readFolder(dbc, dbc.currentProject().getUuid(), parentPath);
11631                }
11632            }
11633            if (parentResource != null) {
11634                acl = (CmsAccessControlList)getAccessControlList(
11635                    dbc,
11636                    parentResource,
11637                    inheritedOnly,
11638                    forFolder,
11639                    depth + 1).clone();
11640            }
11641        }
11642        if (acl == null) {
11643            acl = new CmsAccessControlList();
11644        }
11645
11646        Set<CmsUUID> exclusiveAccessPrincipals = new HashSet<>();
11647        if (!((depth == 0) && inheritedOnly)) {
11648            Iterator<CmsAccessControlEntry> itAces = aces.iterator();
11649            while (itAces.hasNext()) {
11650                CmsAccessControlEntry acEntry = itAces.next();
11651                if (depth > 0) {
11652                    acEntry.setFlags(CmsAccessControlEntry.ACCESS_FLAGS_INHERITED);
11653                }
11654                if ((depth == 0)
11655                    && resource.isFile()
11656                    && (0 != (acEntry.getFlags() & CmsAccessControlEntry.ACCESS_FLAGS_RESPONSIBLE))) {
11657
11658                    // 'responsible' flag is only interpreted as exclusive access if it's not inherited and set directly on a file
11659                    exclusiveAccessPrincipals.add(acEntry.getPrincipal());
11660                }
11661
11662                acl.add(acEntry);
11663
11664                // if the overwrite flag is set, reset the allowed permissions to the permissions of this entry
11665                // denied permissions are kept or extended
11666                if ((acEntry.getFlags() & CmsAccessControlEntry.ACCESS_FLAGS_OVERWRITE) > 0) {
11667                    acl.setAllowedPermissions(acEntry);
11668                }
11669            }
11670        }
11671        if (exclusiveAccessPrincipals.size() > 0) {
11672            acl.setExclusiveAccessPrincipals(exclusiveAccessPrincipals);
11673        }
11674
11675        if (dbc.getProjectId().isNullUUID()) {
11676            m_monitor.cacheACL(cacheKey, acl);
11677        }
11678        return acl;
11679    }
11680
11681    /**
11682     * Return a cache key build from the provided information.<p>
11683     *
11684     * @param prefix a prefix for the key
11685     * @param flag a boolean flag for the key (only used if prefix is not null)
11686     * @param projectId the project for which to generate the key
11687     * @param resource the resource for which to generate the key
11688     *
11689     * @return String a cache key build from the provided information
11690     */
11691    private String getCacheKey(String prefix, boolean flag, CmsUUID projectId, String resource) {
11692
11693        StringBuffer b = new StringBuffer(64);
11694        if (prefix != null) {
11695            b.append(prefix);
11696            b.append(flag ? '+' : '-');
11697        }
11698        b.append(CmsProject.isOnlineProject(projectId) ? '+' : '-');
11699        return b.append(resource).toString();
11700    }
11701
11702    /**
11703     * Return a cache key build from the provided information.<p>
11704     *
11705     * @param keys an array of keys to generate the cache key from
11706     * @param dbc the database context for which to generate the key
11707     *
11708     * @return String a cache key build from the provided information
11709     */
11710    private String getCacheKey(String[] keys, CmsDbContext dbc) {
11711
11712        if (!dbc.getProjectId().isNullUUID()) {
11713            return "";
11714        }
11715        StringBuffer b = new StringBuffer(64);
11716        int len = keys.length;
11717        if (len > 0) {
11718            for (int i = 0; i < len; i++) {
11719                b.append(keys[i]);
11720                b.append('_');
11721            }
11722        }
11723        if (dbc.currentProject().isOnlineProject()) {
11724            b.append("+");
11725        } else {
11726            b.append("-");
11727        }
11728        return b.toString();
11729    }
11730
11731    /**
11732     * Gets the correct driver interface to use for proxying a specific driver instance.<p>
11733     *
11734     * @param obj the driver instance
11735     * @return the interface to use for proxying
11736     */
11737    private Class<?> getDriverInterfaceForProxy(Object obj) {
11738
11739        for (Class<?> interfaceClass : new Class[] {
11740            I_CmsUserDriver.class,
11741            I_CmsVfsDriver.class,
11742            I_CmsProjectDriver.class,
11743            I_CmsHistoryDriver.class,
11744            I_CmsSubscriptionDriver.class}) {
11745            if (interfaceClass.isAssignableFrom(obj.getClass())) {
11746                return interfaceClass;
11747            }
11748        }
11749        return null;
11750    }
11751
11752    /**
11753     * Returns the correct project id.<p>
11754     *
11755     * @param dbc the database context
11756     *
11757     * @return the correct project id
11758     */
11759    private CmsUUID getProjectIdForContext(CmsDbContext dbc) {
11760
11761        CmsUUID projectId = dbc.getProjectId();
11762        if (projectId.isNullUUID()) {
11763            projectId = dbc.currentProject().getUuid();
11764        }
11765        return projectId;
11766    }
11767
11768    /**
11769     * Returns if and what state needs to be updated.<p>
11770     *
11771     * @param dbc the db context
11772     * @param resource the resource
11773     * @param properties the properties to check
11774     *
11775     * @return 0: none, 1: structure, 2: resource
11776     *
11777     * @throws CmsDataAccessException if something goes wrong
11778     */
11779    private int getUpdateState(CmsDbContext dbc, CmsResource resource, List<CmsProperty> properties)
11780    throws CmsDataAccessException {
11781
11782        int updateState = 0;
11783        I_CmsVfsDriver vfsDriver = getVfsDriver(dbc);
11784        Iterator<CmsProperty> it = properties.iterator();
11785        while (it.hasNext() && (updateState < 2)) {
11786            CmsProperty property = it.next();
11787
11788            // read existing property
11789            CmsProperty existingProperty = vfsDriver.readPropertyObject(
11790                dbc,
11791                property.getName(),
11792                dbc.currentProject(),
11793                resource);
11794
11795            // check the shared property
11796            if (property.getResourceValue() != null) {
11797                if (property.isDeleteResourceValue()) {
11798                    if (existingProperty.getResourceValue() != null) {
11799                        updateState = 2; // deleted
11800                    }
11801                } else {
11802                    if (existingProperty.getResourceValue() == null) {
11803                        updateState = 2; // created
11804                    } else {
11805                        if (!property.getResourceValue().equals(existingProperty.getResourceValue())) {
11806                            updateState = 2; // updated
11807                        }
11808                    }
11809                }
11810            }
11811            if (updateState == 0) {
11812                // check the individual property only if needed
11813                if (property.getStructureValue() != null) {
11814                    if (property.isDeleteStructureValue()) {
11815                        if (existingProperty.getStructureValue() != null) {
11816                            updateState = 1; // deleted
11817                        }
11818                    } else {
11819                        if (existingProperty.getStructureValue() == null) {
11820                            updateState = 1; // created
11821                        } else {
11822                            if (!property.getStructureValue().equals(existingProperty.getStructureValue())) {
11823                                updateState = 1; // updated
11824                            }
11825                        }
11826                    }
11827                }
11828            }
11829        }
11830        return updateState;
11831    }
11832
11833    /**
11834     * Returns all groups that are virtualizing the given role in the given ou.<p>
11835     *
11836     * @param dbc the database context
11837     * @param role the role
11838     *
11839     * @return all groups that are virtualizing the given role (or a child of it)
11840     *
11841     * @throws CmsException if something goes wrong
11842     */
11843    private List<CmsGroup> getVirtualGroupsForRole(CmsDbContext dbc, CmsRole role) throws CmsException {
11844
11845        Set<Integer> roleFlags = new HashSet<Integer>();
11846        // add role flag
11847        Integer flags = Integer.valueOf(role.getVirtualGroupFlags());
11848        roleFlags.add(flags);
11849        // collect all child role flags
11850        Iterator<CmsRole> itChildRoles = role.getChildren(true).iterator();
11851        while (itChildRoles.hasNext()) {
11852            CmsRole child = itChildRoles.next();
11853            flags = Integer.valueOf(child.getVirtualGroupFlags());
11854            roleFlags.add(flags);
11855        }
11856        // iterate all groups matching the flags
11857        List<CmsGroup> groups = new ArrayList<CmsGroup>();
11858        Iterator<CmsGroup> it = getGroups(dbc, readOrganizationalUnit(dbc, role.getOuFqn()), false, false).iterator();
11859        while (it.hasNext()) {
11860            CmsGroup group = it.next();
11861            if (group.isVirtual()) {
11862                CmsRole r = CmsRole.valueOf(group);
11863                if (roleFlags.contains(Integer.valueOf(r.getVirtualGroupFlags()))) {
11864                    groups.add(group);
11865                }
11866            }
11867        }
11868        return groups;
11869    }
11870
11871    /**
11872     * Returns a list of users in a group.<p>
11873     *
11874     * @param dbc the current database context
11875     * @param ouFqn the organizational unit to get the users from
11876     * @param groupname the name of the group to list users from
11877     * @param includeOtherOuUsers include users of other organizational units
11878     * @param directUsersOnly if set only the direct assigned users will be returned,
11879     *                        if not also indirect users, ie. members of parent roles,
11880     *                        this parameter only works with roles
11881     * @param readRoles if to read roles or groups
11882     *
11883     * @return all <code>{@link CmsUser}</code> objects in the group
11884     *
11885     * @throws CmsException if operation was not successful
11886     */
11887    private List<CmsUser> internalUsersOfGroup(
11888        CmsDbContext dbc,
11889        String ouFqn,
11890        String groupname,
11891        boolean includeOtherOuUsers,
11892        boolean directUsersOnly,
11893        boolean readRoles)
11894    throws CmsException {
11895
11896        CmsGroup group = readGroup(dbc, groupname); // check that the group really exists
11897        if ((group == null) || (!((!readRoles && !group.isRole()) || (readRoles && group.isRole())))) {
11898            throw new CmsDbEntryNotFoundException(Messages.get().container(Messages.ERR_UNKNOWN_GROUP_1, groupname));
11899        }
11900
11901        String prefix = "_" + includeOtherOuUsers + "_" + directUsersOnly + "_" + ouFqn;
11902        String cacheKey = m_keyGenerator.getCacheKeyForGroupUsers(prefix, dbc, group);
11903        List<CmsUser> allUsers = m_monitor.getCachedUserList(cacheKey);
11904        if (allUsers == null) {
11905            Set<CmsUser> users = new HashSet<CmsUser>(
11906                getUserDriver(dbc).readUsersOfGroup(dbc, groupname, includeOtherOuUsers));
11907            if (readRoles && !directUsersOnly) {
11908                CmsRole role = CmsRole.valueOf(group);
11909                if (role.getParentRole() != null) {
11910                    try {
11911                        String parentGroup = role.getParentRole().getGroupName();
11912                        readGroup(dbc, parentGroup);
11913                        // iterate the parent roles
11914                        users.addAll(
11915                            internalUsersOfGroup(
11916                                dbc,
11917                                ouFqn,
11918                                parentGroup,
11919                                includeOtherOuUsers,
11920                                directUsersOnly,
11921                                readRoles));
11922                    } catch (CmsDbEntryNotFoundException e) {
11923                        // ignore, this may happen while deleting an orgunit
11924                        if (LOG.isDebugEnabled()) {
11925                            LOG.debug(e.getLocalizedMessage(), e);
11926                        }
11927                    }
11928                }
11929                String parentOu = CmsOrganizationalUnit.getParentFqn(group.getOuFqn());
11930                if (parentOu != null) {
11931                    // iterate the parent ou's
11932                    users.addAll(
11933                        internalUsersOfGroup(
11934                            dbc,
11935                            ouFqn,
11936                            parentOu + group.getSimpleName(),
11937                            includeOtherOuUsers,
11938                            directUsersOnly,
11939                            readRoles));
11940                }
11941            } else if (!readRoles && !directUsersOnly) {
11942                List<CmsGroup> groups = getChildren(dbc, group, false);
11943                for (CmsGroup parentGroup : groups) {
11944                    try {
11945                        // iterate the parent groups
11946                        users.addAll(
11947                            internalUsersOfGroup(
11948                                dbc,
11949                                ouFqn,
11950                                parentGroup.getName(),
11951                                includeOtherOuUsers,
11952                                directUsersOnly,
11953                                readRoles));
11954                    } catch (CmsDbEntryNotFoundException e) {
11955                        // ignore, this may happen while deleting an orgunit
11956                        if (LOG.isDebugEnabled()) {
11957                            LOG.debug(e.getLocalizedMessage(), e);
11958                        }
11959                    }
11960                }
11961            }
11962            // filter users from other ous
11963            if (!includeOtherOuUsers) {
11964                Iterator<CmsUser> itUsers = users.iterator();
11965                while (itUsers.hasNext()) {
11966                    CmsUser user = itUsers.next();
11967                    if (!user.getOuFqn().equals(ouFqn)) {
11968                        itUsers.remove();
11969                    }
11970                }
11971            }
11972
11973            // make user list unmodifiable for caching
11974            allUsers = Collections.unmodifiableList(new ArrayList<CmsUser>(users));
11975            if (dbc.getProjectId().isNullUUID()) {
11976                m_monitor.cacheUserList(cacheKey, allUsers);
11977            }
11978        }
11979        return allUsers;
11980    }
11981
11982    /**
11983     * Reads all resources that are inside and changed in a specified project.<p>
11984     *
11985     * @param dbc the current database context
11986     * @param projectId the ID of the project
11987     * @param mode one of the {@link CmsReadChangedProjectResourceMode} constants
11988     *
11989     * @return a List with all resources inside the specified project
11990     *
11991     * @throws CmsException if something goes wrong
11992     */
11993    private List<CmsResource> readChangedResourcesInsideProject(
11994        CmsDbContext dbc,
11995        CmsUUID projectId,
11996        CmsReadChangedProjectResourceMode mode)
11997    throws CmsException {
11998
11999        String cacheKey = projectId + "_" + mode.toString();
12000        List<CmsResource> result = m_monitor.getCachedProjectResources(cacheKey);
12001        if (result != null) {
12002            return result;
12003        }
12004        List<String> projectResources = readProjectResources(dbc, readProject(dbc, projectId));
12005        result = new ArrayList<CmsResource>();
12006        String currentProjectResource = null;
12007        List<CmsResource> resources = new ArrayList<CmsResource>();
12008        CmsResource currentResource = null;
12009        CmsLock currentLock = null;
12010
12011        for (int i = 0; i < projectResources.size(); i++) {
12012            // read all resources that are inside the project by visiting each project resource
12013            currentProjectResource = projectResources.get(i);
12014
12015            try {
12016                currentResource = readResource(dbc, currentProjectResource, CmsResourceFilter.ALL);
12017
12018                if (currentResource.isFolder()) {
12019                    resources.addAll(readResources(dbc, currentResource, CmsResourceFilter.ALL, true));
12020                } else {
12021                    resources.add(currentResource);
12022                }
12023            } catch (CmsException e) {
12024                // the project resource probably doesn't exist (anymore)...
12025                if (!(e instanceof CmsVfsResourceNotFoundException)) {
12026                    throw e;
12027                }
12028            }
12029        }
12030
12031        for (int j = 0; j < resources.size(); j++) {
12032            currentResource = resources.get(j);
12033            currentLock = getLock(dbc, currentResource).getEditionLock();
12034
12035            if (!currentResource.getState().isUnchanged()) {
12036                if ((currentLock.isNullLock() && (currentResource.getProjectLastModified().equals(projectId)))
12037                    || (currentLock.isOwnedBy(dbc.currentUser()) && (currentLock.getProjectId().equals(projectId)))) {
12038                    // add only resources that are
12039                    // - inside the project,
12040                    // - changed in the project,
12041                    // - either unlocked, or locked for the current user in the project
12042                    if ((mode == RCPRM_FILES_AND_FOLDERS_MODE)
12043                        || (currentResource.isFolder() && (mode == RCPRM_FOLDERS_ONLY_MODE))
12044                        || (currentResource.isFile() && (mode == RCPRM_FILES_ONLY_MODE))) {
12045                        result.add(currentResource);
12046                    }
12047                }
12048            }
12049        }
12050
12051        resources.clear();
12052        resources = null;
12053
12054        m_monitor.cacheProjectResources(cacheKey, result);
12055        return result;
12056    }
12057
12058    /**
12059     * Sorts the given list of {@link CmsAccessControlEntry} objects.<p>
12060     *
12061     * The the 'all others' ace in first place, the 'overwrite all' ace in second.<p>
12062     *
12063     * @param aces the list of ACEs to sort
12064     *
12065     * @return <code>true</code> if the list contains the 'overwrite all' ace
12066     */
12067    private boolean sortAceList(List<CmsAccessControlEntry> aces) {
12068
12069        // sort the list of entries
12070        Collections.sort(aces, CmsAccessControlEntry.COMPARATOR_ACE);
12071        // after sorting just the first 2 positions come in question
12072        for (int i = 0; i < Math.min(aces.size(), 2); i++) {
12073            CmsAccessControlEntry acEntry = aces.get(i);
12074            if (acEntry.getPrincipal().equals(CmsAccessControlEntry.PRINCIPAL_OVERWRITE_ALL_ID)) {
12075                return true;
12076            }
12077        }
12078        return false;
12079    }
12080
12081    /**
12082     * All permissions and resources attributes of the principal
12083     * are transfered to a replacement principal.<p>
12084     *
12085     * @param dbc the current database context
12086     * @param project the current project
12087     * @param principalId the id of the principal to be replaced
12088     * @param replacementId the user to be transfered
12089     * @param withACEs flag to signal if the ACEs should also be transfered or just deleted
12090     *
12091     * @throws CmsException if operation was not successful
12092     */
12093    private void transferPrincipalResources(
12094        CmsDbContext dbc,
12095        CmsProject project,
12096        CmsUUID principalId,
12097        CmsUUID replacementId,
12098        boolean withACEs)
12099    throws CmsException {
12100
12101        // get all resources for the given user including resources associated by ACEs or attributes
12102        I_CmsUserDriver userDriver = getUserDriver(dbc);
12103        I_CmsVfsDriver vfsDriver = getVfsDriver(dbc);
12104        Set<CmsResource> resources = getResourcesForPrincipal(dbc, project, principalId, null, true);
12105        Iterator<CmsResource> it = resources.iterator();
12106        while (it.hasNext()) {
12107            CmsResource resource = it.next();
12108            // check resource attributes
12109            boolean attrModified = false;
12110            CmsUUID createdUser = null;
12111            if (resource.getUserCreated().equals(principalId)) {
12112                createdUser = replacementId;
12113                attrModified = true;
12114            }
12115            CmsUUID lastModUser = null;
12116            if (resource.getUserLastModified().equals(principalId)) {
12117                lastModUser = replacementId;
12118                attrModified = true;
12119            }
12120            if (attrModified) {
12121                vfsDriver.transferResource(dbc, project, resource, createdUser, lastModUser);
12122                // clear the cache
12123                m_monitor.clearResourceCache();
12124            }
12125            boolean aceModified = false;
12126            // check aces
12127            if (withACEs) {
12128                Iterator<CmsAccessControlEntry> itAces = userDriver.readAccessControlEntries(
12129                    dbc,
12130                    project,
12131                    resource.getResourceId(),
12132                    false).iterator();
12133                while (itAces.hasNext()) {
12134                    CmsAccessControlEntry ace = itAces.next();
12135                    if (ace.getPrincipal().equals(principalId)) {
12136                        CmsAccessControlEntry newAce = new CmsAccessControlEntry(
12137                            ace.getResource(),
12138                            replacementId,
12139                            ace.getAllowedPermissions(),
12140                            ace.getDeniedPermissions(),
12141                            ace.getFlags());
12142                        // write the new ace
12143                        userDriver.writeAccessControlEntry(dbc, project, newAce);
12144                        aceModified = true;
12145                    }
12146                }
12147                if (aceModified) {
12148                    // clear the cache
12149                    m_monitor.clearAccessControlListCache();
12150                }
12151            }
12152            if (attrModified || aceModified) {
12153                // fire the event
12154                Map<String, Object> data = new HashMap<String, Object>(2);
12155                data.put(I_CmsEventListener.KEY_RESOURCE, resource);
12156                data.put(
12157                    I_CmsEventListener.KEY_CHANGE,
12158                    Integer.valueOf(((attrModified) ? CHANGED_RESOURCE : 0) | ((aceModified) ? CHANGED_ACCESSCONTROL : 0)));
12159                OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_RESOURCE_MODIFIED, data));
12160            }
12161        }
12162    }
12163
12164    /**
12165     * Undoes all content changes of a resource.<p>
12166     *
12167     * @param dbc the database context
12168     * @param onlineProject the online project
12169     * @param offlineResource the offline resource, or <code>null</code> if deleted
12170     * @param onlineResource the online resource
12171     * @param newState the new resource state
12172     * @param moveUndone is a move operation on the same resource has been made
12173     *
12174     * @throws CmsException if something goes wrong
12175     */
12176    private void undoContentChanges(
12177        CmsDbContext dbc,
12178        CmsProject onlineProject,
12179        CmsResource offlineResource,
12180        CmsResource onlineResource,
12181        CmsResourceState newState,
12182        boolean moveUndone)
12183    throws CmsException {
12184
12185        String path = ((moveUndone || (offlineResource == null))
12186        ? onlineResource.getRootPath()
12187        : offlineResource.getRootPath());
12188
12189        // change folder or file?
12190        I_CmsUserDriver userDriver = getUserDriver(dbc);
12191        I_CmsVfsDriver vfsDriver = getVfsDriver(dbc);
12192        if (onlineResource.isFolder()) {
12193            CmsFolder restoredFolder = new CmsFolder(
12194                onlineResource.getStructureId(),
12195                onlineResource.getResourceId(),
12196                path,
12197                onlineResource.getTypeId(),
12198                onlineResource.getFlags(),
12199                dbc.currentProject().getUuid(),
12200                newState,
12201                onlineResource.getDateCreated(),
12202                onlineResource.getUserCreated(),
12203                onlineResource.getDateLastModified(),
12204                onlineResource.getUserLastModified(),
12205                onlineResource.getDateReleased(),
12206                onlineResource.getDateExpired(),
12207                onlineResource.getVersion()); // version number does not matter since it will be computed later
12208
12209            // write the folder in the offline project
12210            // this sets a flag so that the folder date is not set to the current time
12211            restoredFolder.setDateLastModified(onlineResource.getDateLastModified());
12212
12213            // write the folder
12214            vfsDriver.writeResource(dbc, dbc.currentProject().getUuid(), restoredFolder, NOTHING_CHANGED);
12215
12216            // restore the properties from the online project
12217            vfsDriver.deletePropertyObjects(
12218                dbc,
12219                dbc.currentProject().getUuid(),
12220                restoredFolder,
12221                CmsProperty.DELETE_OPTION_DELETE_STRUCTURE_AND_RESOURCE_VALUES);
12222
12223            List<CmsProperty> propertyInfos = vfsDriver.readPropertyObjects(dbc, onlineProject, onlineResource);
12224            vfsDriver.writePropertyObjects(dbc, dbc.currentProject(), restoredFolder, propertyInfos);
12225
12226            // restore the access control entries from the online project
12227            userDriver.removeAccessControlEntries(dbc, dbc.currentProject(), onlineResource.getResourceId());
12228            ListIterator<CmsAccessControlEntry> aceList = userDriver.readAccessControlEntries(
12229                dbc,
12230                onlineProject,
12231                onlineResource.getResourceId(),
12232                false).listIterator();
12233
12234            while (aceList.hasNext()) {
12235                CmsAccessControlEntry ace = aceList.next();
12236                userDriver.createAccessControlEntry(
12237                    dbc,
12238                    dbc.currentProject(),
12239                    onlineResource.getResourceId(),
12240                    ace.getPrincipal(),
12241                    ace.getPermissions().getAllowedPermissions(),
12242                    ace.getPermissions().getDeniedPermissions(),
12243                    ace.getFlags());
12244            }
12245        } else {
12246            byte[] onlineContent = vfsDriver.readContent(
12247                dbc,
12248                CmsProject.ONLINE_PROJECT_ID,
12249                onlineResource.getResourceId());
12250
12251            CmsFile restoredFile = new CmsFile(
12252                onlineResource.getStructureId(),
12253                onlineResource.getResourceId(),
12254                path,
12255                onlineResource.getTypeId(),
12256                onlineResource.getFlags(),
12257                dbc.currentProject().getUuid(),
12258                newState,
12259                onlineResource.getDateCreated(),
12260                onlineResource.getUserCreated(),
12261                onlineResource.getDateLastModified(),
12262                onlineResource.getUserLastModified(),
12263                onlineResource.getDateReleased(),
12264                onlineResource.getDateExpired(),
12265                0,
12266                onlineResource.getLength(),
12267                onlineResource.getDateContent(),
12268                onlineResource.getVersion(), // version number does not matter since it will be computed later
12269                onlineContent);
12270
12271            // write the file in the offline project
12272            // this sets a flag so that the file date is not set to the current time
12273            restoredFile.setDateLastModified(onlineResource.getDateLastModified());
12274
12275            // collect the old properties
12276            List<CmsProperty> properties = vfsDriver.readPropertyObjects(dbc, onlineProject, onlineResource);
12277
12278            if (offlineResource != null) {
12279                // bug fix 1020: delete all properties (inclum_rejectStructureIdded shared),
12280                // shared properties will be recreated by the next call of #createResource(...)
12281                vfsDriver.deletePropertyObjects(
12282                    dbc,
12283                    dbc.currentProject().getUuid(),
12284                    onlineResource,
12285                    CmsProperty.DELETE_OPTION_DELETE_STRUCTURE_AND_RESOURCE_VALUES);
12286
12287                // implementation notes:
12288                // undo changes can become complex e.g. if a resource was deleted, and then
12289                // another resource was copied over the deleted file as a sibling
12290                // therefore we must "clean" delete the offline resource, and then create
12291                // an new resource with the create method
12292                // note that this does NOT apply to folders, since a folder cannot be replaced
12293                // like a resource anyway
12294                deleteResource(dbc, offlineResource, CmsResource.DELETE_PRESERVE_SIBLINGS);
12295            }
12296            CmsResource res = createResource(
12297                dbc,
12298                restoredFile.getRootPath(),
12299                restoredFile,
12300                restoredFile.getContents(),
12301                properties,
12302                false);
12303
12304            // copy the access control entries from the online project
12305            if (offlineResource != null) {
12306                userDriver.removeAccessControlEntries(dbc, dbc.currentProject(), onlineResource.getResourceId());
12307            }
12308            ListIterator<CmsAccessControlEntry> aceList = userDriver.readAccessControlEntries(
12309                dbc,
12310                onlineProject,
12311                onlineResource.getResourceId(),
12312                false).listIterator();
12313
12314            while (aceList.hasNext()) {
12315                CmsAccessControlEntry ace = aceList.next();
12316                userDriver.createAccessControlEntry(
12317                    dbc,
12318                    dbc.currentProject(),
12319                    res.getResourceId(),
12320                    ace.getPrincipal(),
12321                    ace.getPermissions().getAllowedPermissions(),
12322                    ace.getPermissions().getDeniedPermissions(),
12323                    ace.getFlags());
12324            }
12325
12326            vfsDriver.deleteUrlNameMappingEntries(
12327                dbc,
12328                false,
12329                CmsUrlNameMappingFilter.ALL.filterStructureId(res.getStructureId()).filterStates(
12330                    CmsUrlNameMappingEntry.MAPPING_STATUS_NEW,
12331                    CmsUrlNameMappingEntry.MAPPING_STATUS_REPLACE_ON_PUBLISH));
12332            // restore the state to unchanged
12333            res.setState(newState);
12334            m_vfsDriver.writeResourceState(dbc, dbc.currentProject(), res, UPDATE_ALL, false);
12335        }
12336
12337        // delete all offline relations
12338        if (offlineResource != null) {
12339            vfsDriver.deleteRelations(dbc, dbc.currentProject().getUuid(), offlineResource, CmsRelationFilter.TARGETS);
12340        }
12341        // get online relations
12342        List<CmsRelation> relations = vfsDriver.readRelations(
12343            dbc,
12344            CmsProject.ONLINE_PROJECT_ID,
12345            onlineResource,
12346            CmsRelationFilter.TARGETS);
12347        // write offline relations
12348        Iterator<CmsRelation> itRelations = relations.iterator();
12349        while (itRelations.hasNext()) {
12350            CmsRelation relation = itRelations.next();
12351            vfsDriver.createRelation(dbc, dbc.currentProject().getUuid(), relation);
12352        }
12353
12354        // update the cache
12355        m_monitor.clearResourceCache();
12356        m_monitor.flushCache(CmsMemoryMonitor.CacheType.PROPERTY, CmsMemoryMonitor.CacheType.PROPERTY_LIST);
12357
12358        if ((offlineResource == null) || offlineResource.getRootPath().equals(onlineResource.getRootPath())) {
12359            log(
12360                dbc,
12361                new CmsLogEntry(
12362                    dbc,
12363                    onlineResource.getStructureId(),
12364                    CmsLogEntryType.RESOURCE_RESTORED,
12365                    new String[] {onlineResource.getRootPath()}),
12366                false);
12367        } else {
12368            log(
12369                dbc,
12370                new CmsLogEntry(
12371                    dbc,
12372                    offlineResource.getStructureId(),
12373                    CmsLogEntryType.RESOURCE_MOVE_RESTORED,
12374                    new String[] {offlineResource.getRootPath(), onlineResource.getRootPath()}),
12375                false);
12376        }
12377        if (offlineResource != null) {
12378            OpenCms.fireCmsEvent(
12379                new CmsEvent(
12380                    I_CmsEventListener.EVENT_RESOURCE_AND_PROPERTIES_MODIFIED,
12381                    Collections.<String, Object> singletonMap(I_CmsEventListener.KEY_RESOURCE, offlineResource)));
12382        } else {
12383            OpenCms.fireCmsEvent(
12384                new CmsEvent(
12385                    I_CmsEventListener.EVENT_RESOURCE_AND_PROPERTIES_MODIFIED,
12386                    Collections.<String, Object> singletonMap(I_CmsEventListener.KEY_RESOURCE, onlineResource)));
12387        }
12388    }
12389
12390    /**
12391     * Updates the current users context dates with the given resource.<p>
12392     *
12393     * This checks the date information of the resource based on
12394     * {@link CmsResource#getDateLastModified()} as well as
12395     * {@link CmsResource#getDateReleased()} and {@link CmsResource#getDateExpired()}.
12396     * The current users request context is updated with the the "latest" dates found.<p>
12397     *
12398     * This is required in order to ensure proper setting of <code>"last-modified"</code> http headers
12399     * and also for expiration of cached elements in the Flex cache.
12400     * Consider the following use case: Page A is generated from resources x, y and z.
12401     * If either x, y or z has an expiration / release date set, then page A must expire at a certain point
12402     * in time. This is ensured by the context date check here.<p>
12403     *
12404     * @param dbc the current database context
12405     * @param resource the resource to get the date information from
12406     */
12407    private void updateContextDates(CmsDbContext dbc, CmsResource resource) {
12408
12409        CmsFlexRequestContextInfo info = dbc.getFlexRequestContextInfo();
12410        if (info != null) {
12411            info.updateFromResource(resource);
12412        }
12413    }
12414
12415    /**
12416     * Updates the current users context dates with each {@link CmsResource} object in the given list.<p>
12417     *
12418     * The given input list is returned unmodified.<p>
12419     *
12420     * Please see {@link #updateContextDates(CmsDbContext, CmsResource)} for an explanation of what this method does.<p>
12421     *
12422     * @param dbc the current database context
12423     * @param resourceList a list of {@link CmsResource} objects
12424     *
12425     * @return the original list of CmsResources with the full resource name set
12426     */
12427    private List<CmsResource> updateContextDates(CmsDbContext dbc, List<CmsResource> resourceList) {
12428
12429        CmsFlexRequestContextInfo info = dbc.getFlexRequestContextInfo();
12430        if (info != null) {
12431            for (int i = 0; i < resourceList.size(); i++) {
12432                CmsResource resource = resourceList.get(i);
12433                info.updateFromResource(resource);
12434            }
12435        }
12436        return resourceList;
12437    }
12438
12439    /**
12440     * Returns a List of {@link CmsResource} objects generated when applying the given filter to the given list,
12441     * also updates the current users context dates with each {@link CmsResource} object in the given list,
12442     * also applies the selected resource filter to all resources in the list and returns the remaining resources.<p>
12443     *
12444     * Please see {@link #updateContextDates(CmsDbContext, CmsResource)} for an explanation of what this method does.<p>
12445     *
12446     * @param dbc the current database context
12447     * @param resourceList a list of {@link CmsResource} objects
12448     * @param filter the resource filter to use
12449     *
12450     * @return a List of {@link CmsResource} objects generated when applying the given filter to the given list
12451     */
12452    private List<CmsResource> updateContextDates(
12453        CmsDbContext dbc,
12454        List<CmsResource> resourceList,
12455        CmsResourceFilter filter) {
12456
12457        if (CmsResourceFilter.ALL == filter) {
12458            // if there is no filter required, then use the simpler method that does not apply the filter
12459            return new ArrayList<CmsResource>(updateContextDates(dbc, resourceList));
12460        }
12461
12462        CmsFlexRequestContextInfo info = dbc.getFlexRequestContextInfo();
12463        List<CmsResource> result = new ArrayList<CmsResource>(resourceList.size());
12464        for (int i = 0; i < resourceList.size(); i++) {
12465            CmsResource resource = resourceList.get(i);
12466            if (filter.isValid(dbc.getRequestContext(), resource)) {
12467                result.add(resource);
12468            }
12469            // must also include "invalid" resources for the update of context dates
12470            // since a resource may be invalid because of release / expiration date
12471            if (info != null) {
12472                info.updateFromResource(resource);
12473            }
12474        }
12475        return result;
12476    }
12477
12478    /**
12479     * Updates the state of a resource, depending on the <code>resourceState</code> parameter.<p>
12480     *
12481     * @param dbc the db context
12482     * @param resource the resource
12483     * @param resourceState if <code>true</code> the resource state will be updated, if not just the structure state.
12484     *
12485     * @throws CmsDataAccessException if something goes wrong
12486     */
12487    private void updateState(CmsDbContext dbc, CmsResource resource, boolean resourceState)
12488    throws CmsDataAccessException {
12489
12490        CmsUUID projectId = ((dbc.getProjectId() == null) || dbc.getProjectId().isNullUUID())
12491        ? dbc.currentProject().getUuid()
12492        : dbc.getProjectId();
12493        resource.setUserLastModified(dbc.currentUser().getId());
12494        if (resourceState) {
12495            // update the whole resource state
12496            getVfsDriver(dbc).writeResource(dbc, projectId, resource, UPDATE_RESOURCE_STATE);
12497        } else {
12498            // update the structure state
12499            getVfsDriver(dbc).writeResource(dbc, projectId, resource, UPDATE_STRUCTURE_STATE);
12500        }
12501    }
12502
12503    /**
12504     * Wraps a driver object with a dynamic proxy that counts method calls and their durations.<p>
12505     *
12506     * @param newDriverInstance the driver instance to wrap
12507     * @return the proxy
12508     */
12509    private Object wrapDriverInProfilingProxy(Object newDriverInstance) {
12510
12511        Class<?> cls = getDriverInterfaceForProxy(newDriverInstance);
12512        if (cls == null) {
12513            return newDriverInstance;
12514        }
12515        return Proxy.newProxyInstance(
12516            Thread.currentThread().getContextClassLoader(),
12517            new Class[] {cls},
12518            new CmsProfilingInvocationHandler(newDriverInstance, CmsDefaultProfilingHandler.INSTANCE));
12519    }
12520
12521}