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}