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