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