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        // 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(
503                Messages.get().container(
504                    Messages.ERR_REMOVING_UNDELETED_RESOURCE_1,
505                    dbc.getRequestContext().removeSiteRoot(resourceName)));
506        } catch (CmsVfsResourceNotFoundException e) {
507            // ok, ignore
508        }
509        unlockResource(resourceName, true);
510        unlockResource(resourceName, false);
511    }
512
513    /**
514     * Removes all locks of a user.<p>
515     *
516     * Edition and system locks are removed.<p>
517     *
518     * @param userId the id of the user whose locks should be removed
519     */
520    public void removeLocks(CmsUUID userId) {
521
522        Iterator<CmsLock> itLocks = OpenCms.getMemoryMonitor().getAllCachedLocks().iterator();
523        while (itLocks.hasNext()) {
524            CmsLock currentLock = itLocks.next();
525            boolean editLock = currentLock.getEditionLock().getUserId().equals(userId);
526            boolean sysLock = currentLock.getSystemLock().getUserId().equals(userId);
527            if (editLock) {
528                unlockResource(currentLock.getResourceName(), false);
529            }
530            if (sysLock) {
531                unlockResource(currentLock.getResourceName(), true);
532            }
533        }
534    }
535
536    /**
537     * Removes a resource from the lock manager.<p>
538     *
539     * The forceUnlock option should be used with caution.<br>
540     * forceUnlock will remove the lock by ignoring any rules which may cause wrong lock states.<p>
541     *
542     * @param dbc the current database context
543     * @param resource the resource
544     * @param forceUnlock <code>true</code>, if a resource is forced to get unlocked (only edition locks),
545     *                    no matter by which user and in which project the resource is currently locked
546     * @param removeSystemLock <code>true</code>, if you also want to remove system locks
547     *
548     * @return the previous {@link CmsLock} object of the resource,
549     *          or <code>{@link CmsLock#getNullLock()}</code> if the resource was unlocked
550     *
551     * @throws CmsException if something goes wrong
552     */
553    public CmsLock removeResource(CmsDbContext dbc, CmsResource resource, boolean forceUnlock, boolean removeSystemLock)
554    throws CmsException {
555
556        String resourcename = resource.getRootPath();
557        CmsLock lock = getLock(dbc, resource).getEditionLock();
558
559        // check some abort conditions first
560        if (!lock.isNullLock()) {
561            // the resource is locked by another user or in other project
562            if (!forceUnlock && (!lock.isOwnedInProjectBy(dbc.currentUser(), dbc.currentProject()))) {
563                throw new CmsLockException(
564                    Messages.get().container(Messages.ERR_RESOURCE_UNLOCK_1, 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(
570                    Messages.get().container(Messages.ERR_UNLOCK_LOCK_INHERITED_1, dbc.removeSiteRoot(resourcename)));
571            }
572        }
573
574        // remove the lock and clean-up stuff
575        if (lock.isExclusive()) {
576            if (resource.isFolder()) {
577                // in case of a folder, remove any exclusive locks on sub-resources that probably have
578                // been upgraded from an inherited lock when the user edited a resource
579                Iterator<CmsLock> itLocks = OpenCms.getMemoryMonitor().getAllCachedLocks().iterator();
580                while (itLocks.hasNext()) {
581                    String lockedPath = (itLocks.next()).getResourceName();
582                    if (lockedPath.startsWith(resourcename) && !lockedPath.equals(resourcename)) {
583                        // remove the exclusive locked sub-resource
584                        unlockResource(lockedPath, false);
585                    }
586                }
587            }
588            if (removeSystemLock) {
589                unlockResource(resourcename, true);
590            }
591            unlockResource(resourcename, false);
592            return lock;
593        }
594
595        if (lock.getType().isSharedExclusive()) {
596            List<String> locks = OpenCms.getMemoryMonitor().getAllCachedLockPaths();
597            // when a resource with a shared lock gets unlocked, fetch all siblings of the resource
598            // to the same content record to identify the exclusive locked sibling
599            List<CmsResource> siblings = internalReadSiblings(dbc, resource);
600            for (int i = 0; i < siblings.size(); i++) {
601                CmsResource sibling = siblings.get(i);
602                if (locks.contains(sibling.getRootPath())) {
603                    // remove the exclusive locked sibling
604                    if (removeSystemLock) {
605                        unlockResource(sibling.getRootPath(), true);
606                    }
607                    unlockResource(sibling.getRootPath(), false);
608                    break; // it can only be one!
609                }
610            }
611            return lock;
612        }
613
614        // remove system locks only if explicit required
615        if (removeSystemLock && !getLock(dbc, resource).getSystemLock().isUnlocked()) {
616            return unlockResource(resourcename, true);
617        }
618        return lock;
619    }
620
621    /**
622     * Removes all resources locked in a project.<p>
623     *
624     * @param projectId the ID of the project where the resources have been locked
625     * @param removeSystemLocks if <code>true</code>, also system locks are removed
626     */
627    public void removeResourcesInProject(CmsUUID projectId, boolean removeSystemLocks) {
628
629        Iterator<CmsLock> itLocks = OpenCms.getMemoryMonitor().getAllCachedLocks().iterator();
630        while (itLocks.hasNext()) {
631            CmsLock currentLock = itLocks.next();
632            if (removeSystemLocks && currentLock.getSystemLock().getProjectId().equals(projectId)) {
633                unlockResource(currentLock.getResourceName(), true);
634            }
635            if (currentLock.getEditionLock().getProjectId().equals(projectId)) {
636                unlockResource(currentLock.getResourceName(), false);
637            }
638        }
639    }
640
641    /**
642     * Removes all exclusive temporary locks of a user.<p>
643     *
644     * Only edition lock can be temporary, so no system locks are removed.<p>
645     *
646     * @param userId the id of the user whose locks has to be removed
647     */
648    public void removeTempLocks(CmsUUID userId) {
649
650        Iterator<CmsLock> itLocks = OpenCms.getMemoryMonitor().getAllCachedLocks().iterator();
651        while (itLocks.hasNext()) {
652            CmsLock currentLock = itLocks.next();
653            if (currentLock.isTemporary() && currentLock.getUserId().equals(userId)) {
654                unlockResource(currentLock.getResourceName(), false);
655            }
656        }
657    }
658
659    /**
660     * @see java.lang.Object#toString()
661     */
662    @Override
663    public String toString() {
664
665        StringBuffer buf = new StringBuffer();
666
667        // bring the list of locked resources into a human readable order first
668        List<CmsLock> lockedResources = OpenCms.getMemoryMonitor().getAllCachedLocks();
669        Collections.sort(lockedResources);
670
671        // iterate all locks
672        Iterator<CmsLock> itLocks = lockedResources.iterator();
673        while (itLocks.hasNext()) {
674            CmsLock lock = itLocks.next();
675            buf.append(lock).append("\n");
676        }
677        return buf.toString();
678    }
679
680    /**
681     * Writes the locks that are currently stored in-memory to the database to allow restoring them in
682     * later startups.<p>
683     *
684     * This overwrites the locks previously stored in the underlying database table.<p>
685     *
686     *  @param dbc the current database context
687     *
688     *  @throws CmsException if something goes wrong
689     */
690    public void writeLocks(CmsDbContext dbc) throws CmsException {
691
692        if (m_isDirty // only if something changed
693            && m_runningInServlet // only if started in run level 4
694            && OpenCms.getMemoryMonitor().requiresPersistency()) { // only if persistency is required
695
696            List<CmsLock> locks = OpenCms.getMemoryMonitor().getAllCachedLocks();
697            m_driverManager.getProjectDriver(dbc).writeLocks(dbc, locks);
698            m_isDirty = false;
699        }
700    }
701
702    /**
703     * Checks if the given resource is lockable by the given user/project/lock type.<p>
704     *
705     * @param dbc just to get the site path of the resource
706     * @param resource the resource to check lockability for
707     * @param user the user to check
708     * @param project the project to check
709     * @param type the lock type to check
710     * @param currentLock the resource current lock
711     *
712     * @throws CmsLockException if resource is not lockable
713     */
714    private void checkLockable(
715        CmsDbContext dbc,
716        CmsResource resource,
717        CmsUser user,
718        CmsProject project,
719        CmsLockType type,
720        CmsLock currentLock) throws CmsLockException {
721
722        if (!currentLock.isLockableBy(user)) {
723            // check type, owner and project for system locks
724            // this is required if publishing several siblings
725            if (currentLock.getSystemLock().isUnlocked()
726                || (currentLock.getType() != type)
727                || !currentLock.isOwnedInProjectBy(user, project)) {
728                // display the right message
729                CmsMessageContainer message = null;
730                if (currentLock.getSystemLock().isPublish()) {
731                    message = Messages.get().container(
732                        Messages.ERR_RESOURCE_LOCKED_FORPUBLISH_1,
733                        dbc.getRequestContext().getSitePath(resource));
734                } else if (currentLock.getEditionLock().isInherited()) {
735                    message = Messages.get().container(
736                        Messages.ERR_RESOURCE_LOCKED_INHERITED_1,
737                        dbc.getRequestContext().getSitePath(resource));
738                } else {
739                    message = Messages.get().container(
740                        Messages.ERR_RESOURCE_LOCKED_BYOTHERUSER_1,
741                        dbc.getRequestContext().getSitePath(resource));
742                }
743                throw new CmsLockException(message);
744            }
745        }
746    }
747
748    /**
749     * Returns the direct lock of a resource.<p>
750     *
751     * @param resourcename the name of the resource
752     *
753     * @return the direct lock of the resource or <code>null</code>
754     */
755    private CmsLock getDirectLock(String resourcename) {
756
757        return OpenCms.getMemoryMonitor().getCachedLock(resourcename);
758    }
759
760    /**
761     * Returns the lock of a possible locked parent folder of a resource, system locks are ignored.<p>
762     *
763     * @param resourceName the name of the resource
764     *
765     * @return the lock of a parent folder, or {@link CmsLock#getNullLock()} if no parent folders are locked by a non system lock
766     */
767    private CmsLock getParentFolderLock(String resourceName) {
768
769        Iterator<CmsLock> itLocks = OpenCms.getMemoryMonitor().getAllCachedLocks().iterator();
770        while (itLocks.hasNext()) {
771            CmsLock lock = itLocks.next();
772            if (lock.getResourceName().endsWith("/")
773                && resourceName.startsWith(lock.getResourceName())
774                && !resourceName.equals(lock.getResourceName())) {
775                // system locks does not get inherited
776                lock = lock.getEditionLock();
777                // check the lock
778                if (!lock.isUnlocked()) {
779                    return lock;
780                }
781            }
782        }
783        return CmsLock.getNullLock();
784    }
785
786    /**
787     * Returns the inherited lock of a resource.<p>
788     *
789     * @param resourcename the name of the resource
790     * @return the inherited lock or the null lock
791     */
792    private CmsLock getParentLock(String resourcename) {
793
794        CmsLock parentFolderLock = getParentFolderLock(resourcename);
795        if (!parentFolderLock.isNullLock()) {
796            return new CmsLock(
797                resourcename,
798                parentFolderLock.getUserId(),
799                parentFolderLock.getProject(),
800                CmsLockType.INHERITED);
801        }
802        return CmsLock.getNullLock();
803    }
804
805    /**
806     * Returns the indirect lock of a resource depending on siblings lock state.<p>
807     *
808     * @param siblings the list of siblings
809     * @param resourcename the name of the resource
810     *
811     * @return the indirect lock of the resource or the null lock
812     */
813    private CmsLock getSiblingsLock(List<CmsResource> siblings, String resourcename) {
814
815        for (int i = 0; i < siblings.size(); i++) {
816            CmsResource sibling = siblings.get(i);
817            CmsLock exclusiveLock = getDirectLock(sibling.getRootPath());
818            if (exclusiveLock != null) {
819                // a sibling is already locked
820                return internalSiblingLock(exclusiveLock, resourcename);
821            }
822        }
823        // no locked siblings found
824        return null;
825
826    }
827
828    /**
829     * Finally set the given lock.<p>
830     *
831     * @param lock the lock to set
832     * @param locks during reading the locks from db we need to operate on an extra map
833     *
834     * @throws CmsLockException if the lock is not compatible with the current lock
835     */
836    private void internalLockResource(CmsLock lock, Map<String, CmsLock> locks) throws CmsLockException {
837
838        CmsLock currentLock = null;
839        if (locks == null) {
840            currentLock = OpenCms.getMemoryMonitor().getCachedLock(lock.getResourceName());
841        } else {
842            currentLock = locks.get(lock.getResourceName());
843        }
844        if (currentLock != null) {
845            if (currentLock.getSystemLock().equals(lock) || currentLock.getEditionLock().equals(lock)) {
846                return;
847            }
848            if (!currentLock.getSystemLock().isUnlocked() && lock.getSystemLock().isUnlocked()) {
849                lock.setRelatedLock(currentLock);
850                if (locks == null) {
851                    OpenCms.getMemoryMonitor().cacheLock(lock);
852                } else {
853                    locks.put(lock.getResourceName(), lock);
854                }
855            } else if (currentLock.getSystemLock().isUnlocked() && !lock.getSystemLock().isUnlocked()) {
856                currentLock.setRelatedLock(lock);
857            } else {
858                throw new CmsLockException(
859                    Messages.get().container(Messages.ERR_LOCK_ILLEGAL_STATE_2, currentLock, lock));
860            }
861        } else {
862            if (locks == null) {
863                OpenCms.getMemoryMonitor().cacheLock(lock);
864            } else {
865                locks.put(lock.getResourceName(), lock);
866            }
867        }
868    }
869
870    /**
871     * Reads all siblings from a given resource.<p>
872     *
873     * The result is a list of <code>{@link CmsResource}</code> objects.
874     * It does NOT contain the resource itself, only the siblings of the resource.<p>
875     *
876     * @param dbc the current database context
877     * @param resource the resource to find all siblings from
878     *
879     * @return a list of <code>{@link CmsResource}</code> Objects that
880     *          are siblings to the specified resource,
881     *          excluding the specified resource itself
882     *
883     * @throws CmsException if something goes wrong
884     */
885    private List<CmsResource> internalReadSiblings(CmsDbContext dbc, CmsResource resource) throws CmsException {
886
887        // reading siblings using the DriverManager methods while the lock state is checked would
888        // result in an infinite loop, therefore we must access the VFS driver directly
889        List<CmsResource> siblings = m_driverManager.getVfsDriver(dbc).readSiblings(
890            dbc,
891            dbc.currentProject().getUuid(),
892            resource,
893            true);
894        siblings.remove(resource);
895        return siblings;
896    }
897
898    /**
899     * Returns a shared lock for the given excclusive lock and sibling.<p>
900     *
901     * @param exclusiveLock the exclusive lock to use (has to be set on a sibling of siblingName)
902     * @param siblingName the siblings name
903     *
904     * @return the shared lock
905     */
906    private CmsLock internalSiblingLock(CmsLock exclusiveLock, String siblingName) {
907
908        CmsLock lock = null;
909        if (!exclusiveLock.getSystemLock().isUnlocked()) {
910            lock = new CmsLock(
911                siblingName,
912                exclusiveLock.getUserId(),
913                exclusiveLock.getProject(),
914                exclusiveLock.getSystemLock().getType());
915        }
916        if ((lock == null) || !exclusiveLock.getEditionLock().isNullLock()) {
917            CmsLockType type = CmsLockType.SHARED_EXCLUSIVE;
918            if (!getParentLock(siblingName).isNullLock()) {
919                type = CmsLockType.SHARED_INHERITED;
920            }
921            if (lock == null) {
922                lock = new CmsLock(siblingName, exclusiveLock.getUserId(), exclusiveLock.getProject(), type);
923            } else {
924                CmsLock editionLock = new CmsLock(
925                    siblingName,
926                    exclusiveLock.getUserId(),
927                    exclusiveLock.getProject(),
928                    type);
929                lock.setRelatedLock(editionLock);
930            }
931        }
932        return lock;
933    }
934
935    /**
936     * Sets the given lock to the resource.<p>
937     *
938     * @param lock the lock to set
939     *
940     * @throws CmsLockException if the lock is not compatible with the current lock
941     */
942    private void lockResource(CmsLock lock) throws CmsLockException {
943
944        m_isDirty = true;
945        internalLockResource(lock, null);
946    }
947
948    /**
949     * Unlocks the the resource with the given name.<p>
950     *
951     * @param resourceName the name of the resource to unlock
952     * @param systemLocks <code>true</code> if only system locks should be removed,
953     *              and <code>false</code> if only exclusive locks should be removed
954     *
955     * @return the removed lock object
956     */
957    private CmsLock unlockResource(String resourceName, boolean systemLocks) {
958
959        m_isDirty = true;
960
961        // get the current lock
962        CmsLock lock = OpenCms.getMemoryMonitor().getCachedLock(resourceName);
963        if (lock == null) {
964            return CmsLock.getNullLock();
965        }
966
967        // check the lock type (system or user) to remove
968        if (systemLocks) {
969            if (!lock.getSystemLock().isUnlocked()) {
970                // if a system lock has to be removed
971                // user locks are removed too
972                OpenCms.getMemoryMonitor().uncacheLock(resourceName);
973                return lock;
974            } else {
975                // if it is a edition lock, do nothing
976                return CmsLock.getNullLock();
977            }
978        } else {
979            if (lock.getSystemLock().isUnlocked()) {
980                // if it is just an edition lock just remove it
981                OpenCms.getMemoryMonitor().uncacheLock(resourceName);
982                return lock;
983            } else {
984                // if it is a system lock check the edition lock
985                if (!lock.getEditionLock().isUnlocked()) {
986                    // remove the edition lock
987                    CmsLock tmp = lock.getEditionLock();
988                    CmsLock sysLock = lock.getSystemLock();
989                    sysLock.setRelatedLock(null);
990                    if (!sysLock.equals(lock)) {
991                        // replace the lock entry if needed
992                        OpenCms.getMemoryMonitor().cacheLock(sysLock);
993                    }
994                    return tmp;
995                } else {
996                    // if there is no edition lock, only a system lock, do nothing
997                    return CmsLock.getNullLock();
998                }
999            }
1000        }
1001    }
1002}