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}