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