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