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.lock;
029
030import org.opencms.db.CmsDbContext;
031import org.opencms.db.CmsDriverManager;
032import org.opencms.file.CmsProject;
033import org.opencms.file.CmsResource;
034import org.opencms.file.CmsResourceFilter;
035import org.opencms.file.CmsUser;
036import org.opencms.file.CmsVfsResourceNotFoundException;
037import org.opencms.file.I_CmsResource;
038import org.opencms.i18n.CmsMessageContainer;
039import org.opencms.main.CmsException;
040import org.opencms.main.OpenCms;
041import org.opencms.util.CmsUUID;
042
043import java.util.ArrayList;
044import java.util.Collections;
045import java.util.HashMap;
046import java.util.Iterator;
047import java.util.List;
048import java.util.Map;
049
050/**
051 * The CmsLockManager is used by the Cms application to detect
052 * the lock state of a resource.<p>
053 *
054 * The lock state depends on the path of the resource, and probably
055 * locked parent folders. The result of a query to the lock manager
056 * are instances of CmsLock objects.<p>
057 *
058 * @since 6.0.0
059 *
060 * @see org.opencms.file.CmsObject#getLock(CmsResource)
061 * @see org.opencms.lock.CmsLock
062 */
063public final class CmsLockManager {
064
065    /** The driver manager instance. */
066    private CmsDriverManager m_driverManager;
067
068    /** The flag to indicate if the locks should be written to the db. */
069    private boolean m_isDirty;
070
071    /** The flag to indicate if the lock manager has been started in run level 4. */
072    private boolean m_runningInServlet;
073
074    /**
075     * Default constructor, creates a new lock manager.<p>
076     *
077     * @param driverManager the driver manager instance
078     */
079    public CmsLockManager(CmsDriverManager driverManager) {
080
081        m_driverManager = driverManager;
082    }
083
084    /**
085     * Adds a resource to the lock manager.<p>
086     *
087     * @param dbc the current database context
088     * @param resource the resource
089     * @param user the user who locked the resource
090     * @param project the project where the resource is locked
091     * @param type the lock type
092     *
093     * @throws CmsLockException if the resource is locked
094     * @throws CmsException if something goes wrong
095     */
096    public void addResource(CmsDbContext dbc, CmsResource resource, CmsUser user, CmsProject project, CmsLockType type)
097    throws CmsLockException, CmsException {
098
099        // check the type
100        if (!type.isSystem() && !type.isExclusive()) {
101            // invalid type
102            throw new CmsLockException(Messages.get().container(Messages.ERR_INVALID_LOCK_TYPE_1, type.toString()));
103        }
104
105        // get the current lock
106        CmsLock currentLock = getLock(dbc, resource);
107
108        if (currentLock.getType().isShallow() && !type.isShallow() && type.isExclusive()) {
109            throw new CmsLockException(
110                Messages.get().container(Messages.ERR_LOCK_CANT_UPGRADE_SHALLOW_LOCK_1, currentLock.toString()));
111        }
112        // check lockability
113        checkLockable(dbc, resource, user, project, type, currentLock);
114
115        boolean needNewLock = true;
116        // prevent shared locks get compromised
117        if ((type.isExclusive()) && !(type.isTemporary() && currentLock.isInherited())) {
118            if (!currentLock.getEditionLock().isUnlocked()) {
119                needNewLock = false;
120            }
121        }
122
123        CmsLock newLock = CmsLock.getNullLock();
124        if (needNewLock) {
125            // lock the resource
126            newLock = new CmsLock(resource.getRootPath(), user.getId(), project, type);
127            lockResource(newLock);
128        }
129
130        // handle collisions with exclusive locked sub-resources in case of a folder
131        if (resource.isFolder() && newLock.getSystemLock().isUnlocked() && !type.isShallow()) {
132            String resourceName = resource.getRootPath();
133            Iterator<CmsLock> itLocks = OpenCms.getMemoryMonitor().getAllCachedLocks().iterator();
134            while (itLocks.hasNext()) {
135                CmsLock lock = itLocks.next();
136                String lockedPath = lock.getResourceName();
137                if (lockedPath.startsWith(resourceName) && !lockedPath.equals(resourceName)) {
138                    unlockResource(lockedPath, false);
139                }
140            }
141        }
142    }
143
144    /**
145     * Counts the exclusive locked resources in a project.<p>
146     *
147     * @param project the project
148     *
149     * @return the number of exclusive locked resources in the specified project
150     */
151    public int countExclusiveLocksInProject(CmsProject project) {
152
153        int count = 0;
154        Iterator<CmsLock> itLocks = OpenCms.getMemoryMonitor().getAllCachedLocks().iterator();
155        while (itLocks.hasNext()) {
156            CmsLock lock = itLocks.next();
157            if (lock.getEditionLock().isInProject(project)) {
158                count++;
159            }
160        }
161        return count;
162    }
163
164    /**
165     * Returns the lock state of the given resource.<p>
166     *
167     * In case no lock is set, the <code>null lock</code> which can be obtained
168     * by {@link CmsLock#getNullLock()} is returned.<p>
169     *
170     * @param dbc the current database context
171     * @param resource the resource
172     *
173     * @return the lock state of the given resource
174    
175     * @throws CmsException if something goes wrong
176     */
177    public CmsLock getLock(CmsDbContext dbc, CmsResource resource) throws CmsException {
178
179        return getLock(dbc, resource, true);
180    }
181
182    /**
183     * Returns the lock state of the given resource.<p>
184     *
185     * In case no lock is set, the <code>null lock</code> which can be obtained
186     * by {@link CmsLock#getNullLock()} is returned.<p>
187     *
188     * @param dbc the current database context
189     * @param resource the resource
190     * @param includeSiblings if siblings (shared locks) should be included in the search
191     *
192     * @return the lock state of the given resource
193    
194     * @throws CmsException if something goes wrong
195     */
196    public CmsLock getLock(CmsDbContext dbc, CmsResource resource, boolean includeSiblings) throws CmsException {
197
198        // resources are never locked in the online project
199        // and non-existent resources are never locked
200        if ((resource == null) || (dbc.currentProject().isOnlineProject())) {
201            return CmsLock.getNullLock();
202        }
203
204        // check exclusive direct locks first
205        CmsLock lock = getDirectLock(resource.getRootPath());
206        if ((lock == null) && includeSiblings && (resource.getSiblingCount() > 1)) {
207            // check if siblings are exclusively locked
208            List<CmsResource> siblings = internalReadSiblings(dbc, resource);
209            lock = getSiblingsLock(siblings, resource.getRootPath());
210        }
211        if (lock == null) {
212            // if there is no parent lock, this will be the null lock as well
213            lock = getParentLock(resource.getRootPath());
214        }
215        if (!lock.getSystemLock().isUnlocked()) {
216            lock = lock.getSystemLock();
217        } else {
218            lock = lock.getEditionLock();
219        }
220        return lock;
221    }
222
223    /**
224     * Returns all exclusive locked resources matching the given resource and filter.<p>
225     *
226     * @param dbc the database context
227     * @param resource the resource
228     * @param filter the lock filter
229     *
230     * @return a list of resources
231     *
232     * @throws CmsException if something goes wrong
233     */
234    public List<CmsResource> getLockedResources(CmsDbContext dbc, CmsResource resource, CmsLockFilter filter)
235    throws CmsException {
236
237        List<CmsResource> lockedResources = new ArrayList<CmsResource>();
238        Iterator<CmsLock> itLocks = OpenCms.getMemoryMonitor().getAllCachedLocks().iterator();
239        while (itLocks.hasNext()) {
240            CmsLock lock = itLocks.next();
241            CmsResource lockedResource;
242            boolean matchesFilter = filter.match(resource.getRootPath(), lock);
243            if (!matchesFilter && !filter.isSharedExclusive()) {
244                // we don't need to read the resource if the filter didn't match and we don't need to look at the siblings
245                continue;
246            }
247            try {
248                lockedResource = m_driverManager.readResource(dbc, lock.getResourceName(), CmsResourceFilter.ALL);
249            } catch (CmsVfsResourceNotFoundException e) {
250                OpenCms.getMemoryMonitor().uncacheLock(lock.getResourceName());
251                continue;
252            }
253            if (filter.isSharedExclusive() && (lockedResource.getSiblingCount() > 1)) {
254                Iterator<CmsResource> itSiblings = internalReadSiblings(dbc, lockedResource).iterator();
255                while (itSiblings.hasNext()) {
256                    CmsResource sibling = itSiblings.next();
257                    CmsLock siblingLock = internalSiblingLock(lock, sibling.getRootPath());
258                    if (filter.match(resource.getRootPath(), siblingLock)) {
259                        lockedResources.add(sibling);
260                    }
261                }
262            }
263            if (matchesFilter) {
264                lockedResources.add(lockedResource);
265            }
266        }
267        Collections.sort(lockedResources, I_CmsResource.COMPARE_ROOT_PATH);
268        return lockedResources;
269    }
270
271    /**
272     * Returns all exclusive locked resources matching the given resource and filter, but uses a cache for resource loookups.<p>
273     *
274     * @param dbc the database context
275     * @param resource the resource
276     * @param filter the lock filter
277     * @param cache a cache to use for resource lookups
278     *
279     * @return a list of resources
280     *
281     * @throws CmsException if something goes wrong
282     */
283    public List<CmsResource> getLockedResourcesWithCache(
284        CmsDbContext dbc,
285        CmsResource resource,
286        CmsLockFilter filter,
287        Map<String, CmsResource> cache)
288    throws CmsException {
289
290        List<CmsResource> lockedResources = new ArrayList<CmsResource>();
291        Iterator<CmsLock> itLocks = OpenCms.getMemoryMonitor().getAllCachedLocks().iterator();
292        while (itLocks.hasNext()) {
293            CmsLock lock = itLocks.next();
294            CmsResource lockedResource;
295            boolean matchesFilter = filter.match(resource.getRootPath(), lock);
296            if (!matchesFilter && !filter.isSharedExclusive()) {
297                // we don't need to read the resource if the filter didn't match and we don't need to look at the siblings
298                continue;
299            }
300            lockedResource = cache.get(lock.getResourceName());
301            if (lockedResource == null) {
302                try {
303                    lockedResource = m_driverManager.readResource(dbc, lock.getResourceName(), CmsResourceFilter.ALL);
304                    cache.put(lock.getResourceName(), lockedResource);
305                } catch (CmsVfsResourceNotFoundException e) {
306                    OpenCms.getMemoryMonitor().uncacheLock(lock.getResourceName());
307                    // we put a dummy resource object in the map so we won't need to read the nonexistent resource again
308                    CmsResource dummy = new CmsResource(
309                        null,
310                        null,
311                        "",
312                        0,
313                        false,
314                        0,
315                        null,
316                        null,
317                        0,
318                        null,
319                        0,
320                        null,
321                        0,
322                        0,
323                        0,
324                        0,
325                        0,
326                        0);
327                    cache.put(lock.getResourceName(), dummy);
328                    continue;
329                }
330            } else if (lockedResource.getStructureId() == null) {
331                // dummy resource, i.e. the resource was not found in a previous readResource call
332                continue;
333            }
334            if (filter.isSharedExclusive() && (lockedResource != null) && (lockedResource.getSiblingCount() > 1)) {
335                Iterator<CmsResource> itSiblings = internalReadSiblings(dbc, lockedResource).iterator();
336                while (itSiblings.hasNext()) {
337                    CmsResource sibling = itSiblings.next();
338                    CmsLock siblingLock = internalSiblingLock(lock, sibling.getRootPath());
339                    if (filter.match(resource.getRootPath(), siblingLock)) {
340                        lockedResources.add(sibling);
341                    }
342                }
343            }
344            if (matchesFilter) {
345                lockedResources.add(lockedResource);
346            }
347        }
348        Collections.sort(lockedResources, I_CmsResource.COMPARE_ROOT_PATH);
349        return lockedResources;
350    }
351
352    /**
353     * Returns all exclusive locked resources matching the given resource name and filter.<p>
354     *
355     * @param dbc the database context
356     * @param resourceName the resource name
357     * @param filter the lock filter
358     *
359     * @return a list of root paths
360     *
361     * @throws CmsException if something goes wrong
362     */
363    public List<CmsLock> getLocks(CmsDbContext dbc, String resourceName, CmsLockFilter filter) throws CmsException {
364
365        List<CmsLock> locks = new ArrayList<CmsLock>();
366        Iterator<CmsLock> itLocks = OpenCms.getMemoryMonitor().getAllCachedLocks().iterator();
367        while (itLocks.hasNext()) {
368            CmsLock lock = itLocks.next();
369            if (filter.isSharedExclusive()) {
370                CmsResource resource;
371                try {
372                    resource = m_driverManager.readResource(dbc, lock.getResourceName(), CmsResourceFilter.ALL);
373                } catch (CmsVfsResourceNotFoundException e) {
374                    OpenCms.getMemoryMonitor().uncacheLock(lock.getResourceName());
375                    continue;
376                }
377                if (resource.getSiblingCount() > 1) {
378                    Iterator<CmsResource> itSiblings = internalReadSiblings(dbc, resource).iterator();
379                    while (itSiblings.hasNext()) {
380                        CmsResource sibling = itSiblings.next();
381                        CmsLock siblingLock = internalSiblingLock(lock, sibling.getRootPath());
382                        if (filter.match(resourceName, siblingLock)) {
383                            locks.add(siblingLock);
384                        }
385                    }
386                }
387            }
388            if (filter.match(resourceName, lock)) {
389                locks.add(lock);
390            }
391        }
392        return locks;
393    }
394
395    /**
396     * Returns <code>true</code> if the given resource contains a resource that has a system lock.<p>
397     *
398     * This check is required for certain operations on folders.<p>
399     *
400     * @param dbc the database context
401     * @param resource the resource to check the system locks for
402     *
403     * @return <code>true</code> if the given resource contains a resource that has a system lock
404     *
405     * @throws CmsException if something goes wrong
406     */
407    public boolean hasSystemLocks(CmsDbContext dbc, CmsResource resource) throws CmsException {
408
409        if (resource == null) {
410            return false;
411        }
412        Iterator<CmsLock> itLocks = OpenCms.getMemoryMonitor().getAllCachedLocks().iterator();
413        while (itLocks.hasNext()) {
414            CmsLock lock = itLocks.next();
415            if (lock.getSystemLock().isUnlocked()) {
416                // only system locks matter here
417                continue;
418            }
419            if (lock.getResourceName().startsWith(resource.getRootPath())) {
420                if (lock.getResourceName().startsWith(resource.getRootPath())) {
421                    return true;
422                }
423                try {
424                    resource = m_driverManager.readResource(dbc, lock.getResourceName(), CmsResourceFilter.ALL);
425                } catch (CmsVfsResourceNotFoundException e) {
426                    OpenCms.getMemoryMonitor().uncacheLock(lock.getResourceName());
427                    continue;
428                }
429                CmsResource lockedResource;
430                try {
431                    lockedResource = m_driverManager.readResource(dbc, lock.getResourceName(), CmsResourceFilter.ALL);
432                } catch (CmsVfsResourceNotFoundException e) {
433                    OpenCms.getMemoryMonitor().uncacheLock(lock.getResourceName());
434                    continue;
435                }
436                if (lockedResource.getSiblingCount() > 1) {
437                    Iterator<CmsResource> itSiblings = internalReadSiblings(dbc, lockedResource).iterator();
438                    while (itSiblings.hasNext()) {
439                        CmsResource sibling = itSiblings.next();
440                        CmsLock siblingLock = internalSiblingLock(lock, sibling.getRootPath());
441                        if (siblingLock.getResourceName().startsWith(resource.getRootPath())) {
442                            return true;
443                        }
444                    }
445                }
446            }
447        }
448        return false;
449    }
450
451    /**
452     * Moves a lock during the move resource operation.<p>
453     *
454     * @param source the source root path
455     * @param destination the destination root path
456     */
457    public void moveResource(String source, String destination) {
458
459        CmsLock lock = OpenCms.getMemoryMonitor().getCachedLock(source);
460        if (lock != null) {
461            OpenCms.getMemoryMonitor().uncacheLock(lock.getResourceName());
462            CmsLock newLock = new CmsLock(destination, lock.getUserId(), lock.getProject(), lock.getType());
463            lock = lock.getRelatedLock();
464            if ((lock != null) && !lock.isNullLock()) {
465                CmsLock relatedLock = new CmsLock(destination, lock.getUserId(), lock.getProject(), lock.getType());
466                newLock.setRelatedLock(relatedLock);
467            }
468            OpenCms.getMemoryMonitor().cacheLock(newLock);
469        }
470    }
471
472    /**
473     * Reads the latest saved locks from the database and installs them to
474     * this lock manager.<p>
475     *
476     *  @param dbc the current database context
477     *
478     *  @throws CmsException if something goes wrong
479     */
480    public void readLocks(CmsDbContext dbc) throws CmsException {
481
482        if (OpenCms.getRunLevel() > OpenCms.RUNLEVEL_3_SHELL_ACCESS) {
483            // read the locks only if the wizard is not enabled
484            Map<String, CmsLock> lockCache = new HashMap<String, CmsLock>();
485            List<CmsLock> locks = m_driverManager.getProjectDriver(dbc).readLocks(dbc);
486            Iterator<CmsLock> itLocks = locks.iterator();
487            while (itLocks.hasNext()) {
488                CmsLock lock = itLocks.next();
489                internalLockResource(lock, lockCache);
490            }
491            OpenCms.getMemoryMonitor().flushLocks(lockCache);
492            m_runningInServlet = true;
493        }
494    }
495
496    /**
497     * Removes a resource after it has been deleted by the driver manager.<p>
498     *
499     * @param dbc the current database context
500     * @param resourceName the root path of the deleted resource
501     * @throws CmsException if something goes wrong
502     */
503    public void removeDeletedResource(CmsDbContext dbc, String resourceName) throws CmsException {
504
505        try {
506            m_driverManager.getVfsDriver(dbc).readResource(dbc, dbc.currentProject().getUuid(), resourceName, false);
507            throw new CmsLockException(
508                Messages.get().container(
509                    Messages.ERR_REMOVING_UNDELETED_RESOURCE_1,
510                    dbc.getRequestContext().removeSiteRoot(resourceName)));
511        } catch (CmsVfsResourceNotFoundException e) {
512            // ok, ignore
513        }
514        unlockResource(resourceName, true);
515        unlockResource(resourceName, false);
516    }
517
518    /**
519     * Removes all locks of a user.<p>
520     *
521     * Edition and system locks are removed.<p>
522     *
523     * @param userId the id of the user whose locks should be removed
524     */
525    public void removeLocks(CmsUUID userId) {
526
527        Iterator<CmsLock> itLocks = OpenCms.getMemoryMonitor().getAllCachedLocks().iterator();
528        while (itLocks.hasNext()) {
529            CmsLock currentLock = itLocks.next();
530            boolean editLock = currentLock.getEditionLock().getUserId().equals(userId);
531            boolean sysLock = currentLock.getSystemLock().getUserId().equals(userId);
532            if (editLock) {
533                unlockResource(currentLock.getResourceName(), false);
534            }
535            if (sysLock) {
536                unlockResource(currentLock.getResourceName(), true);
537            }
538        }
539    }
540
541    /**
542     * Removes a resource from the lock manager.<p>
543     *
544     * The forceUnlock option should be used with caution.<br>
545     * forceUnlock will remove the lock by ignoring any rules which may cause wrong lock states.<p>
546     *
547     * @param dbc the current database context
548     * @param resource the resource
549     * @param forceUnlock <code>true</code>, if a resource is forced to get unlocked (only edition locks),
550     *                    no matter by which user and in which project the resource is currently locked
551     * @param removeSystemLock <code>true</code>, if you also want to remove system locks
552     *
553     * @return the previous {@link CmsLock} object of the resource,
554     *          or <code>{@link CmsLock#getNullLock()}</code> if the resource was unlocked
555     *
556     * @throws CmsException if something goes wrong
557     */
558    public CmsLock removeResource(CmsDbContext dbc, CmsResource resource, boolean forceUnlock, boolean removeSystemLock)
559    throws CmsException {
560
561        String resourcename = resource.getRootPath();
562        CmsLock lock = getLock(dbc, resource).getEditionLock();
563
564        // check some abort conditions first
565        if (!lock.isNullLock()) {
566            // the resource is locked by another user or in other project
567            if (!forceUnlock && (!lock.isOwnedInProjectBy(dbc.currentUser(), dbc.currentProject()))) {
568                throw new CmsLockException(
569                    Messages.get().container(Messages.ERR_RESOURCE_UNLOCK_1, dbc.removeSiteRoot(resourcename)));
570            }
571
572            // sub-resources of a locked folder can't be unlocked
573            if (!forceUnlock && lock.isInherited()) {
574                throw new CmsLockException(
575                    Messages.get().container(Messages.ERR_UNLOCK_LOCK_INHERITED_1, dbc.removeSiteRoot(resourcename)));
576            }
577        }
578
579        // remove the lock and clean-up stuff
580        if (lock.isExclusive()) {
581            if (resource.isFolder() && !lock.getType().isShallow()) {
582                // in case of a folder, remove any exclusive locks on sub-resources that probably have
583                // been upgraded from an inherited lock when the user edited a resource
584                Iterator<CmsLock> itLocks = OpenCms.getMemoryMonitor().getAllCachedLocks().iterator();
585                while (itLocks.hasNext()) {
586                    String lockedPath = (itLocks.next()).getResourceName();
587                    if (lockedPath.startsWith(resourcename) && !lockedPath.equals(resourcename)) {
588                        // remove the exclusive locked sub-resource
589                        unlockResource(lockedPath, false);
590                    }
591                }
592            }
593            if (removeSystemLock) {
594                unlockResource(resourcename, true);
595            }
596            unlockResource(resourcename, false);
597            return lock;
598        }
599
600        if (lock.getType().isSharedExclusive()) {
601            List<String> locks = OpenCms.getMemoryMonitor().getAllCachedLockPaths();
602            // when a resource with a shared lock gets unlocked, fetch all siblings of the resource
603            // to the same content record to identify the exclusive locked sibling
604            List<CmsResource> siblings = internalReadSiblings(dbc, resource);
605            for (int i = 0; i < siblings.size(); i++) {
606                CmsResource sibling = siblings.get(i);
607                if (locks.contains(sibling.getRootPath())) {
608                    // remove the exclusive locked sibling
609                    if (removeSystemLock) {
610                        unlockResource(sibling.getRootPath(), true);
611                    }
612                    unlockResource(sibling.getRootPath(), false);
613                    break; // it can only be one!
614                }
615            }
616            return lock;
617        }
618
619        // remove system locks only if explicit required
620        if (removeSystemLock && !getLock(dbc, resource).getSystemLock().isUnlocked()) {
621            return unlockResource(resourcename, true);
622        }
623        return lock;
624    }
625
626    /**
627     * Removes all resources locked in a project.<p>
628     *
629     * @param projectId the ID of the project where the resources have been locked
630     * @param removeSystemLocks if <code>true</code>, also system locks are removed
631     */
632    public void removeResourcesInProject(CmsUUID projectId, boolean removeSystemLocks) {
633
634        Iterator<CmsLock> itLocks = OpenCms.getMemoryMonitor().getAllCachedLocks().iterator();
635        while (itLocks.hasNext()) {
636            CmsLock currentLock = itLocks.next();
637            if (removeSystemLocks && currentLock.getSystemLock().getProjectId().equals(projectId)) {
638                unlockResource(currentLock.getResourceName(), true);
639            }
640            if (currentLock.getEditionLock().getProjectId().equals(projectId)) {
641                unlockResource(currentLock.getResourceName(), false);
642            }
643        }
644    }
645
646    /**
647     * Removes all exclusive temporary locks of a user.<p>
648     *
649     * Only edition lock can be temporary, so no system locks are removed.<p>
650     *
651     * @param userId the id of the user whose locks has to be removed
652     */
653    public void removeTempLocks(CmsUUID userId) {
654
655        Iterator<CmsLock> itLocks = OpenCms.getMemoryMonitor().getAllCachedLocks().iterator();
656        while (itLocks.hasNext()) {
657            CmsLock currentLock = itLocks.next();
658            if (currentLock.isTemporary() && currentLock.getUserId().equals(userId)) {
659                unlockResource(currentLock.getResourceName(), false);
660            }
661        }
662    }
663
664    /**
665     * @see java.lang.Object#toString()
666     */
667    @Override
668    public String toString() {
669
670        StringBuffer buf = new StringBuffer();
671
672        // bring the list of locked resources into a human readable order first
673        List<CmsLock> lockedResources = OpenCms.getMemoryMonitor().getAllCachedLocks();
674        Collections.sort(lockedResources);
675
676        // iterate all locks
677        Iterator<CmsLock> itLocks = lockedResources.iterator();
678        while (itLocks.hasNext()) {
679            CmsLock lock = itLocks.next();
680            buf.append(lock).append("\n");
681        }
682        return buf.toString();
683    }
684
685    /**
686     * Writes the locks that are currently stored in-memory to the database to allow restoring them in
687     * later startups.<p>
688     *
689     * This overwrites the locks previously stored in the underlying database table.<p>
690     *
691     *  @param dbc the current database context
692     *
693     *  @throws CmsException if something goes wrong
694     */
695    public void writeLocks(CmsDbContext dbc) throws CmsException {
696
697        if (m_isDirty // only if something changed
698            && m_runningInServlet // only if started in run level 4
699            && OpenCms.getMemoryMonitor().requiresPersistency()) { // only if persistency is required
700
701            List<CmsLock> locks = OpenCms.getMemoryMonitor().getAllCachedLocks();
702            m_driverManager.getProjectDriver(dbc).writeLocks(dbc, locks);
703            m_isDirty = false;
704        }
705    }
706
707    /**
708     * Checks if the given resource is lockable by the given user/project/lock type.<p>
709     *
710     * @param dbc just to get the site path of the resource
711     * @param resource the resource to check lockability for
712     * @param user the user to check
713     * @param project the project to check
714     * @param type the lock type to check
715     * @param currentLock the resource current lock
716     *
717     * @throws CmsLockException if resource is not lockable
718     */
719    private void checkLockable(
720        CmsDbContext dbc,
721        CmsResource resource,
722        CmsUser user,
723        CmsProject project,
724        CmsLockType type,
725        CmsLock currentLock)
726    throws CmsLockException {
727
728        if (!currentLock.isLockableBy(user)) {
729            // check type, owner and project for system locks
730            // this is required if publishing several siblings
731            if (currentLock.getSystemLock().isUnlocked()
732                || (currentLock.getType() != type)
733                || !currentLock.isOwnedInProjectBy(user, project)) {
734                // display the right message
735                CmsMessageContainer message = null;
736                if (currentLock.getSystemLock().isPublish()) {
737                    message = Messages.get().container(
738                        Messages.ERR_RESOURCE_LOCKED_FORPUBLISH_1,
739                        dbc.getRequestContext().getSitePath(resource));
740                } else if (currentLock.getEditionLock().isInherited()) {
741                    message = Messages.get().container(
742                        Messages.ERR_RESOURCE_LOCKED_INHERITED_1,
743                        dbc.getRequestContext().getSitePath(resource));
744                } else {
745                    message = Messages.get().container(
746                        Messages.ERR_RESOURCE_LOCKED_BYOTHERUSER_1,
747                        dbc.getRequestContext().getSitePath(resource));
748                }
749                throw new CmsLockException(message);
750            }
751        }
752    }
753
754    /**
755     * Returns the direct lock of a resource.<p>
756     *
757     * @param resourcename the name of the resource
758     *
759     * @return the direct lock of the resource or <code>null</code>
760     */
761    private CmsLock getDirectLock(String resourcename) {
762
763        return OpenCms.getMemoryMonitor().getCachedLock(resourcename);
764    }
765
766    /**
767     * Returns the lock of a possible locked parent folder of a resource, system locks are ignored.<p>
768     *
769     * @param resourceName the name of the resource
770     *
771     * @return the lock of a parent folder, or {@link CmsLock#getNullLock()} if no parent folders are locked by a non system lock
772     */
773    private CmsLock getParentFolderLock(String resourceName) {
774
775        Iterator<CmsLock> itLocks = OpenCms.getMemoryMonitor().getAllCachedLocks().iterator();
776        while (itLocks.hasNext()) {
777            CmsLock lock = itLocks.next();
778            if (lock.getResourceName().endsWith("/")
779                && !lock.getType().isShallow()
780                && resourceName.startsWith(lock.getResourceName())
781                && !resourceName.equals(lock.getResourceName())) {
782                // system locks does not get inherited
783                lock = lock.getEditionLock();
784                // check the lock
785                if (!lock.isUnlocked()) {
786                    return lock;
787                }
788            }
789        }
790        return CmsLock.getNullLock();
791    }
792
793    /**
794     * Returns the inherited lock of a resource.<p>
795     *
796     * @param resourcename the name of the resource
797     * @return the inherited lock or the null lock
798     */
799    private CmsLock getParentLock(String resourcename) {
800
801        CmsLock parentFolderLock = getParentFolderLock(resourcename);
802        if (!parentFolderLock.isNullLock()) {
803            return new CmsLock(
804                resourcename,
805                parentFolderLock.getUserId(),
806                parentFolderLock.getProject(),
807                CmsLockType.INHERITED);
808        }
809        return CmsLock.getNullLock();
810    }
811
812    /**
813     * Returns the indirect lock of a resource depending on siblings lock state.<p>
814     *
815     * @param siblings the list of siblings
816     * @param resourcename the name of the resource
817     *
818     * @return the indirect lock of the resource or the null lock
819     */
820    private CmsLock getSiblingsLock(List<CmsResource> siblings, String resourcename) {
821
822        for (int i = 0; i < siblings.size(); i++) {
823            CmsResource sibling = siblings.get(i);
824            CmsLock exclusiveLock = getDirectLock(sibling.getRootPath());
825            if (exclusiveLock != null) {
826                // a sibling is already locked
827                return internalSiblingLock(exclusiveLock, resourcename);
828            }
829        }
830        // no locked siblings found
831        return null;
832
833    }
834
835    /**
836     * Finally set the given lock.<p>
837     *
838     * @param lock the lock to set
839     * @param locks during reading the locks from db we need to operate on an extra map
840     *
841     * @throws CmsLockException if the lock is not compatible with the current lock
842     */
843    private void internalLockResource(CmsLock lock, Map<String, CmsLock> locks) throws CmsLockException {
844
845        CmsLock currentLock = null;
846        if (locks == null) {
847            currentLock = OpenCms.getMemoryMonitor().getCachedLock(lock.getResourceName());
848        } else {
849            currentLock = locks.get(lock.getResourceName());
850        }
851        if (currentLock != null) {
852            if (currentLock.getSystemLock().equals(lock) || currentLock.getEditionLock().equals(lock)) {
853                return;
854            }
855            if (!currentLock.getSystemLock().isUnlocked() && lock.getSystemLock().isUnlocked()) {
856                lock.setRelatedLock(currentLock);
857                if (locks == null) {
858                    OpenCms.getMemoryMonitor().cacheLock(lock);
859                } else {
860                    locks.put(lock.getResourceName(), lock);
861                }
862            } else if (currentLock.getSystemLock().isUnlocked() && !lock.getSystemLock().isUnlocked()) {
863                currentLock.setRelatedLock(lock);
864            } else {
865                throw new CmsLockException(
866                    Messages.get().container(Messages.ERR_LOCK_ILLEGAL_STATE_2, currentLock, lock));
867            }
868        } else {
869            if (locks == null) {
870                OpenCms.getMemoryMonitor().cacheLock(lock);
871            } else {
872                locks.put(lock.getResourceName(), lock);
873            }
874        }
875    }
876
877    /**
878     * Reads all siblings from a given resource.<p>
879     *
880     * The result is a list of <code>{@link CmsResource}</code> objects.
881     * It does NOT contain the resource itself, only the siblings of the resource.<p>
882     *
883     * @param dbc the current database context
884     * @param resource the resource to find all siblings from
885     *
886     * @return a list of <code>{@link CmsResource}</code> Objects that
887     *          are siblings to the specified resource,
888     *          excluding the specified resource itself
889     *
890     * @throws CmsException if something goes wrong
891     */
892    private List<CmsResource> internalReadSiblings(CmsDbContext dbc, CmsResource resource) throws CmsException {
893
894        // reading siblings using the DriverManager methods while the lock state is checked would
895        // result in an infinite loop, therefore we must access the VFS driver directly
896        List<CmsResource> siblings = m_driverManager.getVfsDriver(
897            dbc).readSiblings(dbc, dbc.currentProject().getUuid(), resource, true);
898        siblings.remove(resource);
899        return siblings;
900    }
901
902    /**
903     * Returns a shared lock for the given excclusive lock and sibling.<p>
904     *
905     * @param exclusiveLock the exclusive lock to use (has to be set on a sibling of siblingName)
906     * @param siblingName the siblings name
907     *
908     * @return the shared lock
909     */
910    private CmsLock internalSiblingLock(CmsLock exclusiveLock, String siblingName) {
911
912        CmsLock lock = null;
913        if (!exclusiveLock.getSystemLock().isUnlocked()) {
914            lock = new CmsLock(
915                siblingName,
916                exclusiveLock.getUserId(),
917                exclusiveLock.getProject(),
918                exclusiveLock.getSystemLock().getType());
919        }
920        if ((lock == null) || !exclusiveLock.getEditionLock().isNullLock()) {
921            CmsLockType type = CmsLockType.SHARED_EXCLUSIVE;
922            if (!getParentLock(siblingName).isNullLock()) {
923                type = CmsLockType.SHARED_INHERITED;
924            }
925            if (lock == null) {
926                lock = new CmsLock(siblingName, exclusiveLock.getUserId(), exclusiveLock.getProject(), type);
927            } else {
928                CmsLock editionLock = new CmsLock(
929                    siblingName,
930                    exclusiveLock.getUserId(),
931                    exclusiveLock.getProject(),
932                    type);
933                lock.setRelatedLock(editionLock);
934            }
935        }
936        return lock;
937    }
938
939    /**
940     * Sets the given lock to the resource.<p>
941     *
942     * @param lock the lock to set
943     *
944     * @throws CmsLockException if the lock is not compatible with the current lock
945     */
946    private void lockResource(CmsLock lock) throws CmsLockException {
947
948        m_isDirty = true;
949        internalLockResource(lock, null);
950    }
951
952    /**
953     * Unlocks the the resource with the given name.<p>
954     *
955     * @param resourceName the name of the resource to unlock
956     * @param systemLocks <code>true</code> if only system locks should be removed,
957     *              and <code>false</code> if only exclusive locks should be removed
958     *
959     * @return the removed lock object
960     */
961    private CmsLock unlockResource(String resourceName, boolean systemLocks) {
962
963        m_isDirty = true;
964
965        // get the current lock
966        CmsLock lock = OpenCms.getMemoryMonitor().getCachedLock(resourceName);
967        if (lock == null) {
968            return CmsLock.getNullLock();
969        }
970
971        // check the lock type (system or user) to remove
972        if (systemLocks) {
973            if (!lock.getSystemLock().isUnlocked()) {
974                // if a system lock has to be removed
975                // user locks are removed too
976                OpenCms.getMemoryMonitor().uncacheLock(resourceName);
977                return lock;
978            } else {
979                // if it is a edition lock, do nothing
980                return CmsLock.getNullLock();
981            }
982        } else {
983            if (lock.getSystemLock().isUnlocked()) {
984                // if it is just an edition lock just remove it
985                OpenCms.getMemoryMonitor().uncacheLock(resourceName);
986                return lock;
987            } else {
988                // if it is a system lock check the edition lock
989                if (!lock.getEditionLock().isUnlocked()) {
990                    // remove the edition lock
991                    CmsLock tmp = lock.getEditionLock();
992                    CmsLock sysLock = lock.getSystemLock();
993                    sysLock.setRelatedLock(null);
994                    if (!sysLock.equals(lock)) {
995                        // replace the lock entry if needed
996                        OpenCms.getMemoryMonitor().cacheLock(sysLock);
997                    }
998                    return tmp;
999                } else {
1000                    // if there is no edition lock, only a system lock, do nothing
1001                    return CmsLock.getNullLock();
1002                }
1003            }
1004        }
1005    }
1006}