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