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