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.module; 029 030import org.opencms.configuration.CmsConfigurationException; 031import org.opencms.configuration.CmsConfigurationManager; 032import org.opencms.configuration.CmsModuleConfiguration; 033import org.opencms.db.CmsExportPoint; 034import org.opencms.file.CmsObject; 035import org.opencms.file.CmsProject; 036import org.opencms.file.CmsResource; 037import org.opencms.i18n.CmsMessageContainer; 038import org.opencms.importexport.CmsImportExportManager; 039import org.opencms.importexport.CmsImportParameters; 040import org.opencms.lock.CmsLock; 041import org.opencms.lock.CmsLockException; 042import org.opencms.lock.CmsLockFilter; 043import org.opencms.main.CmsException; 044import org.opencms.main.CmsIllegalArgumentException; 045import org.opencms.main.CmsIllegalStateException; 046import org.opencms.main.CmsLog; 047import org.opencms.main.CmsRuntimeException; 048import org.opencms.main.OpenCms; 049import org.opencms.report.I_CmsReport; 050import org.opencms.security.CmsRole; 051import org.opencms.security.CmsRoleViolationException; 052import org.opencms.security.CmsSecurityException; 053import org.opencms.util.CmsStringUtil; 054 055import java.io.File; 056import java.util.ArrayList; 057import java.util.Collections; 058import java.util.HashMap; 059import java.util.HashSet; 060import java.util.Hashtable; 061import java.util.Iterator; 062import java.util.List; 063import java.util.Map; 064import java.util.Optional; 065import java.util.Set; 066 067import org.apache.commons.logging.Log; 068 069/** 070 * Manages the modules of an OpenCms installation.<p> 071 * 072 * @since 6.0.0 073 */ 074public class CmsModuleManager { 075 076 /** Indicates dependency check for module deletion. */ 077 public static final int DEPENDENCY_MODE_DELETE = 0; 078 079 /** Indicates dependency check for module import. */ 080 public static final int DEPENDENCY_MODE_IMPORT = 1; 081 082 /** The log object for this class. */ 083 private static final Log LOG = CmsLog.getLog(CmsModuleManager.class); 084 085 /** The import/export repository. */ 086 private CmsModuleImportExportRepository m_importExportRepository = new CmsModuleImportExportRepository(); 087 088 /** The list of module export points. */ 089 private Set<CmsExportPoint> m_moduleExportPoints; 090 091 /** The map of configured modules. */ 092 private Map<String, CmsModule> m_modules; 093 094 /** Whether incremental module updates are allowed (rather than deleting / reimporting the module). */ 095 private boolean m_moduleUpdateEnabled = true; 096 097 /** 098 * Basic constructor.<p> 099 * 100 * @param configuredModules the list of configured modules 101 */ 102 public CmsModuleManager(List<CmsModule> configuredModules) { 103 104 if (CmsLog.INIT.isInfoEnabled()) { 105 CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_MOD_MANAGER_CREATED_0)); 106 } 107 108 m_modules = new Hashtable<String, CmsModule>(); 109 for (int i = 0; i < configuredModules.size(); i++) { 110 CmsModule module = configuredModules.get(i); 111 m_modules.put(module.getName(), module); 112 if (CmsLog.INIT.isInfoEnabled()) { 113 CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_MOD_CONFIGURED_1, module.getName())); 114 } 115 } 116 117 if (CmsLog.INIT.isInfoEnabled()) { 118 CmsLog.INIT.info( 119 Messages.get().getBundle().key(Messages.INIT_NUM_MODS_CONFIGURED_1, new Integer(m_modules.size()))); 120 } 121 m_moduleExportPoints = Collections.emptySet(); 122 } 123 124 /** 125 * Returns a map of dependencies.<p> 126 * 127 * The module dependencies are get from the installed modules or 128 * from the module manifest.xml files found in the given FRS path.<p> 129 * 130 * Two types of dependency lists can be generated:<br> 131 * <ul> 132 * <li>Forward dependency lists: a list of modules that depends on a module</li> 133 * <li>Backward dependency lists: a list of modules that a module depends on</li> 134 * </ul> 135 * 136 * @param rfsAbsPath a RFS absolute path to search for modules, or <code>null</code> to use the installed modules 137 * @param mode if <code>true</code> a list of forward dependency is build, is not a list of backward dependency 138 * 139 * @return a Map of module names as keys and a list of dependency names as values 140 * 141 * @throws CmsConfigurationException if something goes wrong 142 */ 143 public static Map<String, List<String>> buildDepsForAllModules(String rfsAbsPath, boolean mode) 144 throws CmsConfigurationException { 145 146 Map<String, List<String>> ret = new HashMap<String, List<String>>(); 147 List<CmsModule> modules; 148 if (rfsAbsPath == null) { 149 modules = OpenCms.getModuleManager().getAllInstalledModules(); 150 } else { 151 modules = new ArrayList<CmsModule>(getAllModulesFromPath(rfsAbsPath).keySet()); 152 } 153 Iterator<CmsModule> itMods = modules.iterator(); 154 while (itMods.hasNext()) { 155 CmsModule module = itMods.next(); 156 157 // if module a depends on module b, and module c depends also on module b: 158 // build a map with a list containing "a" and "c" keyed by "b" to get a 159 // list of modules depending on module "b"... 160 Iterator<CmsModuleDependency> itDeps = module.getDependencies().iterator(); 161 while (itDeps.hasNext()) { 162 CmsModuleDependency dependency = itDeps.next(); 163 // module dependency package name 164 String moduleDependencyName = dependency.getName(); 165 166 if (mode) { 167 // get the list of dependent modules 168 List<String> moduleDependencies = ret.get(moduleDependencyName); 169 if (moduleDependencies == null) { 170 // build a new list if "b" has no dependent modules yet 171 moduleDependencies = new ArrayList<String>(); 172 ret.put(moduleDependencyName, moduleDependencies); 173 } 174 // add "a" as a module depending on "b" 175 moduleDependencies.add(module.getName()); 176 } else { 177 List<String> moduleDependencies = ret.get(module.getName()); 178 if (moduleDependencies == null) { 179 moduleDependencies = new ArrayList<String>(); 180 ret.put(module.getName(), moduleDependencies); 181 } 182 moduleDependencies.add(dependency.getName()); 183 } 184 } 185 } 186 itMods = modules.iterator(); 187 while (itMods.hasNext()) { 188 CmsModule module = itMods.next(); 189 if (ret.get(module.getName()) == null) { 190 ret.put(module.getName(), new ArrayList<String>()); 191 } 192 } 193 return ret; 194 } 195 196 /** 197 * Returns a map of dependencies between the given modules.<p> 198 * 199 * The module dependencies are get from the installed modules or 200 * from the module manifest.xml files found in the given FRS path.<p> 201 * 202 * Two types of dependency lists can be generated:<br> 203 * <ul> 204 * <li>Forward dependency lists: a list of modules that depends on a module</li> 205 * <li>Backward dependency lists: a list of modules that a module depends on</li> 206 * </ul> 207 * 208 * @param moduleNames a list of module names 209 * @param rfsAbsPath a RFS absolute path to search for modules, or <code>null</code> to use the installed modules 210 * @param mode if <code>true</code> a list of forward dependency is build, is not a list of backward dependency 211 * 212 * @return a Map of module names as keys and a list of dependency names as values 213 * 214 * @throws CmsConfigurationException if something goes wrong 215 */ 216 public static Map<String, List<String>> buildDepsForModulelist( 217 List<String> moduleNames, 218 String rfsAbsPath, 219 boolean mode) 220 throws CmsConfigurationException { 221 222 Map<String, List<String>> ret = buildDepsForAllModules(rfsAbsPath, mode); 223 Iterator<CmsModule> itMods; 224 if (rfsAbsPath == null) { 225 itMods = OpenCms.getModuleManager().getAllInstalledModules().iterator(); 226 } else { 227 itMods = getAllModulesFromPath(rfsAbsPath).keySet().iterator(); 228 } 229 while (itMods.hasNext()) { 230 CmsModule module = itMods.next(); 231 if (!moduleNames.contains(module.getName())) { 232 Iterator<List<String>> itDeps = ret.values().iterator(); 233 while (itDeps.hasNext()) { 234 List<String> dependencies = itDeps.next(); 235 dependencies.remove(module.getName()); 236 } 237 ret.remove(module.getName()); 238 } 239 } 240 return ret; 241 } 242 243 /** 244 * Returns a map of modules found in the given RFS absolute path.<p> 245 * 246 * @param rfsAbsPath the path to look for module distributions 247 * 248 * @return a map of <code>{@link CmsModule}</code> objects for keys and filename for values 249 * 250 * @throws CmsConfigurationException if something goes wrong 251 */ 252 public static Map<CmsModule, String> getAllModulesFromPath(String rfsAbsPath) throws CmsConfigurationException { 253 254 Map<CmsModule, String> modules = new HashMap<CmsModule, String>(); 255 if (rfsAbsPath == null) { 256 return modules; 257 } 258 File folder = new File(rfsAbsPath); 259 if (folder.exists()) { 260 // list all child resources in the given folder 261 File[] folderFiles = folder.listFiles(); 262 if (folderFiles != null) { 263 for (int i = 0; i < folderFiles.length; i++) { 264 File moduleFile = folderFiles[i]; 265 if (moduleFile.isFile() && !(moduleFile.getAbsolutePath().toLowerCase().endsWith(".zip"))) { 266 // skip non-ZIP files 267 continue; 268 } 269 if (moduleFile.isDirectory()) { 270 File manifest = new File(moduleFile, CmsImportExportManager.EXPORT_MANIFEST); 271 if (!manifest.exists() || !manifest.canRead()) { 272 // skip unused directories 273 continue; 274 } 275 } 276 modules.put( 277 CmsModuleImportExportHandler.readModuleFromImport(moduleFile.getAbsolutePath()), 278 moduleFile.getName()); 279 } 280 } 281 } 282 return modules; 283 } 284 285 /** 286 * Sorts a given list of module names by dependencies, 287 * so that the resulting list can be imported in that given order, 288 * that means modules without dependencies first.<p> 289 * 290 * The module dependencies are get from the installed modules or 291 * from the module manifest.xml files found in the given FRS path.<p> 292 * 293 * @param moduleNames a list of module names 294 * @param rfsAbsPath a RFS absolute path to search for modules, or <code>null</code> to use the installed modules 295 * 296 * @return a sorted list of module names 297 * 298 * @throws CmsConfigurationException if something goes wrong 299 */ 300 public static List<String> topologicalSort(List<String> moduleNames, String rfsAbsPath) 301 throws CmsConfigurationException { 302 303 List<String> modules = new ArrayList<String>(moduleNames); 304 List<String> retList = new ArrayList<String>(); 305 Map<String, List<String>> moduleDependencies = buildDepsForModulelist(moduleNames, rfsAbsPath, true); 306 boolean finished = false; 307 while (!finished) { 308 finished = true; 309 Iterator<String> itMods = modules.iterator(); 310 while (itMods.hasNext()) { 311 String moduleName = itMods.next(); 312 List<String> deps = moduleDependencies.get(moduleName); 313 if ((deps == null) || deps.isEmpty()) { 314 retList.add(moduleName); 315 Iterator<List<String>> itDeps = moduleDependencies.values().iterator(); 316 while (itDeps.hasNext()) { 317 List<String> dependencies = itDeps.next(); 318 dependencies.remove(moduleName); 319 } 320 finished = false; 321 itMods.remove(); 322 } 323 } 324 } 325 if (!modules.isEmpty()) { 326 throw new CmsIllegalStateException( 327 Messages.get().container(Messages.ERR_MODULE_DEPENDENCY_CYCLE_1, modules.toString())); 328 } 329 Collections.reverse(retList); 330 return retList; 331 } 332 333 /** 334 * Adds a new module to the module manager.<p> 335 * 336 * @param cms must be initialized with "Admin" permissions 337 * @param module the module to add 338 * 339 * @throws CmsSecurityException if the required permissions are not available (i.e. no "Admin" CmsObject has been provided) 340 * @throws CmsConfigurationException if a module with this name is already configured 341 */ 342 public synchronized void addModule(CmsObject cms, CmsModule module) 343 throws CmsSecurityException, CmsConfigurationException { 344 345 // check the role permissions 346 OpenCms.getRoleManager().checkRole(cms, CmsRole.DATABASE_MANAGER); 347 348 if (m_modules.containsKey(module.getName())) { 349 // module is currently configured, no create possible 350 throw new CmsConfigurationException( 351 Messages.get().container(Messages.ERR_MODULE_ALREADY_CONFIGURED_1, module.getName())); 352 353 } 354 355 if (LOG.isInfoEnabled()) { 356 LOG.info(Messages.get().getBundle().key(Messages.LOG_CREATE_NEW_MOD_1, module.getName())); 357 } 358 359 // initialize the module 360 module.initialize(cms); 361 362 m_modules.put(module.getName(), module); 363 364 try { 365 I_CmsModuleAction moduleAction = module.getActionInstance(); 366 String className = module.getActionClass(); 367 if ((moduleAction == null) && (className != null)) { 368 Class<?> actionClass = Class.forName(className, false, getClass().getClassLoader()); 369 if (I_CmsModuleAction.class.isAssignableFrom(actionClass)) { 370 moduleAction = ((Class<? extends I_CmsModuleAction>)actionClass).newInstance(); 371 module.setActionInstance(moduleAction); 372 } 373 } 374 // handle module action instance if initialized 375 if (moduleAction != null) { 376 377 moduleAction.moduleUpdate(module); 378 } 379 } catch (Throwable t) { 380 LOG.error(Messages.get().getBundle().key(Messages.LOG_MOD_UPDATE_ERR_1, module.getName()), t); 381 } 382 383 // initialize the export points 384 initModuleExportPoints(); 385 386 // update the configuration 387 updateModuleConfiguration(); 388 389 // reinit the workplace CSS URIs 390 if (!module.getParameters().isEmpty()) { 391 OpenCms.getWorkplaceAppManager().initWorkplaceCssUris(this); 392 } 393 } 394 395 /** 396 * Checks if a modules dependencies are fulfilled.<p> 397 * 398 * The possible values for the <code>mode</code> parameter are:<dl> 399 * <dt>{@link #DEPENDENCY_MODE_DELETE}</dt> 400 * <dd>Check for module deleting, i.e. are other modules dependent on the 401 * given module?</dd> 402 * <dt>{@link #DEPENDENCY_MODE_IMPORT}</dt> 403 * <dd>Check for module importing, i.e. are all dependencies required by the given 404 * module available?</dd></dl> 405 * 406 * @param module the module to check the dependencies for 407 * @param mode the dependency check mode 408 * @return a list of dependencies that are not fulfilled, if empty all dependencies are fulfilled 409 */ 410 public List<CmsModuleDependency> checkDependencies(CmsModule module, int mode) { 411 412 List<CmsModuleDependency> result = new ArrayList<CmsModuleDependency>(); 413 414 if (mode == DEPENDENCY_MODE_DELETE) { 415 // delete mode, check if other modules depend on this module 416 Iterator<CmsModule> i = m_modules.values().iterator(); 417 while (i.hasNext()) { 418 CmsModule otherModule = i.next(); 419 CmsModuleDependency dependency = otherModule.checkDependency(module); 420 if (dependency != null) { 421 // dependency found, add to list 422 result.add(new CmsModuleDependency(otherModule.getName(), otherModule.getVersion())); 423 } 424 } 425 426 } else if (mode == DEPENDENCY_MODE_IMPORT) { 427 // import mode, check if all module dependencies are fulfilled 428 Iterator<CmsModule> i = m_modules.values().iterator(); 429 // add all dependencies that must be found 430 result.addAll(module.getDependencies()); 431 while (i.hasNext() && (result.size() > 0)) { 432 CmsModule otherModule = i.next(); 433 CmsModuleDependency dependency = module.checkDependency(otherModule); 434 if (dependency != null) { 435 // dependency found, remove from list 436 result.remove(dependency); 437 } 438 } 439 } else { 440 // invalid mode selected 441 throw new CmsRuntimeException( 442 Messages.get().container(Messages.ERR_CHECK_DEPENDENCY_INVALID_MODE_1, new Integer(mode))); 443 } 444 445 return result; 446 } 447 448 /** 449 * Checks the module selection list for consistency, that means 450 * that if a module is selected, all its dependencies are also selected.<p> 451 * 452 * The module dependencies are get from the installed modules or 453 * from the module manifest.xml files found in the given FRS path.<p> 454 * 455 * @param moduleNames a list of module names 456 * @param rfsAbsPath a RFS absolute path to search for modules, or <code>null</code> to use the installed modules 457 * @param forDeletion there are two modes, one for installation of modules, and one for deletion. 458 * 459 * @throws CmsIllegalArgumentException if the module list is not consistent 460 * @throws CmsConfigurationException if something goes wrong 461 */ 462 public void checkModuleSelectionList(List<String> moduleNames, String rfsAbsPath, boolean forDeletion) 463 throws CmsIllegalArgumentException, CmsConfigurationException { 464 465 Map<String, List<String>> moduleDependencies = buildDepsForAllModules(rfsAbsPath, forDeletion); 466 Iterator<String> itMods = moduleNames.iterator(); 467 while (itMods.hasNext()) { 468 String moduleName = itMods.next(); 469 List<String> dependencies = moduleDependencies.get(moduleName); 470 if (dependencies != null) { 471 List<String> depModules = new ArrayList<String>(dependencies); 472 depModules.removeAll(moduleNames); 473 if (!depModules.isEmpty()) { 474 throw new CmsIllegalArgumentException( 475 Messages.get().container( 476 Messages.ERR_MODULE_SELECTION_INCONSISTENT_2, 477 moduleName, 478 depModules.toString())); 479 } 480 } 481 } 482 } 483 484 /** 485 * Deletes a module from the configuration.<p> 486 * 487 * @param cms must be initialized with "Admin" permissions 488 * @param moduleName the name of the module to delete 489 * @param replace indicates if the module is replaced (true) or finally deleted (false) 490 * @param preserveLibs <code>true</code> to keep any exported file exported into the WEB-INF lib folder 491 * @param report the report to print progress messages to 492 * 493 * @throws CmsRoleViolationException if the required module manager role permissions are not available 494 * @throws CmsConfigurationException if a module with this name is not available for deleting 495 * @throws CmsLockException if the module resources can not be locked 496 */ 497 public synchronized void deleteModule( 498 CmsObject cms, 499 String moduleName, 500 boolean replace, 501 boolean preserveLibs, 502 I_CmsReport report) 503 throws CmsRoleViolationException, CmsConfigurationException, CmsLockException { 504 505 // check for module manager role permissions 506 OpenCms.getRoleManager().checkRole(cms, CmsRole.DATABASE_MANAGER); 507 508 if (!m_modules.containsKey(moduleName)) { 509 // module is not currently configured, no update possible 510 throw new CmsConfigurationException( 511 Messages.get().container(Messages.ERR_MODULE_NOT_CONFIGURED_1, moduleName)); 512 } 513 514 if (LOG.isInfoEnabled()) { 515 LOG.info(Messages.get().getBundle().key(Messages.LOG_DEL_MOD_1, moduleName)); 516 } 517 518 CmsModule module = m_modules.get(moduleName); 519 String importSite = module.getSite(); 520 if (!CmsStringUtil.isEmptyOrWhitespaceOnly(importSite)) { 521 CmsObject newCms; 522 try { 523 newCms = OpenCms.initCmsObject(cms); 524 newCms.getRequestContext().setSiteRoot(importSite); 525 cms = newCms; 526 } catch (CmsException e) { 527 LOG.error(e.getLocalizedMessage(), e); 528 } 529 } 530 531 if (!replace) { 532 // module is deleted, not replaced 533 534 // perform dependency check 535 List<CmsModuleDependency> dependencies = checkDependencies(module, DEPENDENCY_MODE_DELETE); 536 if (!dependencies.isEmpty()) { 537 StringBuffer message = new StringBuffer(); 538 Iterator<CmsModuleDependency> it = dependencies.iterator(); 539 while (it.hasNext()) { 540 message.append(" ").append(it.next().getName()).append("\r\n"); 541 } 542 throw new CmsConfigurationException( 543 Messages.get().container(Messages.ERR_MOD_DEPENDENCIES_2, moduleName, message.toString())); 544 } 545 try { 546 I_CmsModuleAction moduleAction = module.getActionInstance(); 547 // handle module action instance if initialized 548 if (moduleAction != null) { 549 moduleAction.moduleUninstall(module); 550 } 551 } catch (Throwable t) { 552 LOG.error(Messages.get().getBundle().key(Messages.LOG_MOD_UNINSTALL_ERR_1, moduleName), t); 553 report.println( 554 Messages.get().container(Messages.LOG_MOD_UNINSTALL_ERR_1, moduleName), 555 I_CmsReport.FORMAT_WARNING); 556 } 557 } 558 559 boolean removeResourceTypes = !module.getResourceTypes().isEmpty(); 560 if (removeResourceTypes) { 561 // mark the resource manager to reinitialize if necessary 562 OpenCms.getWorkplaceManager().removeExplorerTypeSettings(module); 563 } 564 565 CmsProject previousProject = cms.getRequestContext().getCurrentProject(); 566 // try to create a new offline project for deletion 567 CmsProject deleteProject = null; 568 try { 569 // try to read a (leftover) module delete project 570 deleteProject = cms.readProject( 571 Messages.get().getBundle(cms.getRequestContext().getLocale()).key( 572 Messages.GUI_DELETE_MODULE_PROJECT_NAME_1, 573 new Object[] {moduleName})); 574 } catch (CmsException e) { 575 try { 576 // create a Project to delete the module 577 deleteProject = cms.createProject( 578 Messages.get().getBundle(cms.getRequestContext().getLocale()).key( 579 Messages.GUI_DELETE_MODULE_PROJECT_NAME_1, 580 new Object[] {moduleName}), 581 Messages.get().getBundle(cms.getRequestContext().getLocale()).key( 582 Messages.GUI_DELETE_MODULE_PROJECT_DESC_1, 583 new Object[] {moduleName}), 584 OpenCms.getDefaultUsers().getGroupAdministrators(), 585 OpenCms.getDefaultUsers().getGroupAdministrators(), 586 CmsProject.PROJECT_TYPE_TEMPORARY); 587 } catch (CmsException e1) { 588 throw new CmsConfigurationException(e1.getMessageContainer(), e1); 589 } 590 } 591 592 try { 593 cms.getRequestContext().setCurrentProject(deleteProject); 594 595 // check locks 596 List<String> lockedResources = new ArrayList<String>(); 597 CmsLockFilter filter1 = CmsLockFilter.FILTER_ALL.filterNotLockableByUser( 598 cms.getRequestContext().getCurrentUser()); 599 CmsLockFilter filter2 = CmsLockFilter.FILTER_INHERITED; 600 List<String> moduleResources = module.getResources(); 601 for (int iLock = 0; iLock < moduleResources.size(); iLock++) { 602 String resourceName = moduleResources.get(iLock); 603 try { 604 lockedResources.addAll(cms.getLockedResources(resourceName, filter1)); 605 lockedResources.addAll(cms.getLockedResources(resourceName, filter2)); 606 } catch (CmsException e) { 607 // may happen if the resource has already been deleted 608 if (LOG.isDebugEnabled()) { 609 LOG.debug(e.getMessageContainer(), e); 610 } 611 report.println(e.getMessageContainer(), I_CmsReport.FORMAT_WARNING); 612 } 613 } 614 if (!lockedResources.isEmpty()) { 615 CmsMessageContainer msg = Messages.get().container( 616 Messages.ERR_DELETE_MODULE_CHECK_LOCKS_2, 617 moduleName, 618 CmsStringUtil.collectionAsString(lockedResources, ",")); 619 report.addError(msg.key(cms.getRequestContext().getLocale())); 620 report.println(msg); 621 cms.getRequestContext().setCurrentProject(previousProject); 622 try { 623 cms.deleteProject(deleteProject.getUuid()); 624 } catch (CmsException e1) { 625 throw new CmsConfigurationException(e1.getMessageContainer(), e1); 626 } 627 throw new CmsLockException(msg); 628 } 629 } finally { 630 cms.getRequestContext().setCurrentProject(previousProject); 631 } 632 633 // now remove the module 634 module = m_modules.remove(moduleName); 635 636 if (preserveLibs) { 637 // to preserve the module libs, remove the responsible export points, before deleting module resources 638 Set<CmsExportPoint> exportPoints = new HashSet<CmsExportPoint>(m_moduleExportPoints); 639 Iterator<CmsExportPoint> it = exportPoints.iterator(); 640 while (it.hasNext()) { 641 CmsExportPoint point = it.next(); 642 if ((point.getUri().endsWith(module.getName() + "/lib/") 643 || point.getUri().endsWith(module.getName() + "/lib")) 644 && point.getConfiguredDestination().equals("WEB-INF/lib/")) { 645 it.remove(); 646 } 647 } 648 649 m_moduleExportPoints = Collections.unmodifiableSet(exportPoints); 650 } 651 652 try { 653 cms.getRequestContext().setCurrentProject(deleteProject); 654 655 // copy the module resources to the project 656 List<CmsResource> moduleResources = CmsModule.calculateModuleResources(cms, module); 657 for (CmsResource resource : moduleResources) { 658 try { 659 cms.copyResourceToProject(resource); 660 } catch (CmsException e) { 661 // may happen if the resource has already been deleted 662 if (LOG.isDebugEnabled()) { 663 LOG.debug( 664 Messages.get().getBundle().key( 665 Messages.LOG_MOVE_RESOURCE_FAILED_1, 666 cms.getSitePath(resource))); 667 } 668 report.println(e.getMessageContainer(), I_CmsReport.FORMAT_WARNING); 669 } 670 } 671 672 report.print(Messages.get().container(Messages.RPT_DELETE_MODULE_BEGIN_0), I_CmsReport.FORMAT_HEADLINE); 673 report.println( 674 org.opencms.report.Messages.get().container( 675 org.opencms.report.Messages.RPT_ARGUMENT_HTML_ITAG_1, 676 moduleName), 677 I_CmsReport.FORMAT_HEADLINE); 678 679 // move through all module resources and delete them 680 for (CmsResource resource : moduleResources) { 681 String sitePath = cms.getSitePath(resource); 682 try { 683 if (LOG.isDebugEnabled()) { 684 LOG.debug(Messages.get().getBundle().key(Messages.LOG_DEL_MOD_RESOURCE_1, sitePath)); 685 } 686 CmsLock lock = cms.getLock(resource); 687 if (lock.isUnlocked()) { 688 // lock the resource 689 cms.lockResource(resource); 690 } else if (lock.isLockableBy(cms.getRequestContext().getCurrentUser())) { 691 // steal the resource 692 cms.changeLock(resource); 693 } 694 if (!resource.getState().isDeleted()) { 695 // delete the resource 696 cms.deleteResource(sitePath, CmsResource.DELETE_PRESERVE_SIBLINGS); 697 } 698 // update the report 699 report.print(Messages.get().container(Messages.RPT_DELETE_0), I_CmsReport.FORMAT_NOTE); 700 report.println( 701 org.opencms.report.Messages.get().container( 702 org.opencms.report.Messages.RPT_ARGUMENT_1, 703 sitePath)); 704 if (!resource.getState().isNew()) { 705 // unlock the resource (so it gets deleted with next publish) 706 cms.unlockResource(resource); 707 } 708 } catch (CmsException e) { 709 // ignore the exception and delete the next resource 710 LOG.error(Messages.get().getBundle().key(Messages.LOG_DEL_MOD_EXC_1, sitePath), e); 711 report.println(e.getMessageContainer(), I_CmsReport.FORMAT_WARNING); 712 } 713 } 714 715 report.println(Messages.get().container(Messages.RPT_PUBLISH_PROJECT_BEGIN_0), I_CmsReport.FORMAT_HEADLINE); 716 717 // now unlock and publish the project 718 cms.unlockProject(deleteProject.getUuid()); 719 OpenCms.getPublishManager().publishProject(cms, report); 720 OpenCms.getPublishManager().waitWhileRunning(); 721 722 report.println(Messages.get().container(Messages.RPT_PUBLISH_PROJECT_END_0), I_CmsReport.FORMAT_HEADLINE); 723 report.println(Messages.get().container(Messages.RPT_DELETE_MODULE_END_0), I_CmsReport.FORMAT_HEADLINE); 724 } catch (CmsException e) { 725 throw new CmsConfigurationException(e.getMessageContainer(), e); 726 } finally { 727 cms.getRequestContext().setCurrentProject(previousProject); 728 } 729 730 // initialize the export points (removes export points from deleted module) 731 initModuleExportPoints(); 732 733 // update the configuration 734 updateModuleConfiguration(); 735 736 // reinit the manager is necessary 737 if (removeResourceTypes) { 738 OpenCms.getResourceManager().initialize(cms); 739 } 740 741 // reinit the workplace CSS URIs 742 if (!module.getParameters().isEmpty()) { 743 OpenCms.getWorkplaceAppManager().initWorkplaceCssUris(this); 744 } 745 } 746 747 /** 748 * Deletes a module from the configuration.<p> 749 * 750 * @param cms must be initialized with "Admin" permissions 751 * @param moduleName the name of the module to delete 752 * @param replace indicates if the module is replaced (true) or finally deleted (false) 753 * @param report the report to print progress messages to 754 * 755 * @throws CmsRoleViolationException if the required module manager role permissions are not available 756 * @throws CmsConfigurationException if a module with this name is not available for deleting 757 * @throws CmsLockException if the module resources can not be locked 758 */ 759 public synchronized void deleteModule(CmsObject cms, String moduleName, boolean replace, I_CmsReport report) 760 throws CmsRoleViolationException, CmsConfigurationException, CmsLockException { 761 762 deleteModule(cms, moduleName, replace, false, report); 763 } 764 765 /** 766 * Returns a list of installed modules.<p> 767 * 768 * @return a list of <code>{@link CmsModule}</code> objects 769 */ 770 public List<CmsModule> getAllInstalledModules() { 771 772 return new ArrayList<CmsModule>(m_modules.values()); 773 } 774 775 /** 776 * Returns the (immutable) list of configured module export points.<p> 777 * 778 * @return the (immutable) list of configured module export points 779 * @see CmsExportPoint 780 */ 781 public Set<CmsExportPoint> getExportPoints() { 782 783 return m_moduleExportPoints; 784 } 785 786 /** 787 * Returns the importExportRepository.<p> 788 * 789 * @return the importExportRepository 790 */ 791 public CmsModuleImportExportRepository getImportExportRepository() { 792 793 return m_importExportRepository; 794 } 795 796 /** 797 * Returns the module with the given module name, 798 * or <code>null</code> if no module with the given name is configured.<p> 799 * 800 * @param name the name of the module to return 801 * @return the module with the given module name 802 */ 803 public CmsModule getModule(String name) { 804 805 return m_modules.get(name); 806 } 807 808 /** 809 * Returns the set of names of all the installed modules.<p> 810 * 811 * @return the set of names of all the installed modules 812 */ 813 public Set<String> getModuleNames() { 814 815 synchronized (m_modules) { 816 return new HashSet<String>(m_modules.keySet()); 817 } 818 } 819 820 /** 821 * Checks if this module manager has a module with the given name installed.<p> 822 * 823 * @param name the name of the module to check 824 * @return true if this module manager has a module with the given name installed 825 */ 826 public boolean hasModule(String name) { 827 828 return m_modules.containsKey(name); 829 } 830 831 /** 832 * Initializes all module instance classes managed in this module manager.<p> 833 * 834 * @param cms an initialized CmsObject with "manage modules" role permissions 835 * @param configurationManager the initialized OpenCms configuration manager 836 * 837 * @throws CmsRoleViolationException if the provided OpenCms context does not have "manage modules" role permissions 838 */ 839 public synchronized void initialize(CmsObject cms, CmsConfigurationManager configurationManager) 840 throws CmsRoleViolationException { 841 842 if (OpenCms.getRunLevel() > OpenCms.RUNLEVEL_1_CORE_OBJECT) { 843 // certain test cases won't have an OpenCms context 844 OpenCms.getRoleManager().checkRole(cms, CmsRole.DATABASE_MANAGER); 845 } 846 847 Iterator<String> it; 848 int count = 0; 849 it = m_modules.keySet().iterator(); 850 while (it.hasNext()) { 851 // get the module description 852 CmsModule module = m_modules.get(it.next()); 853 854 if (module.getActionClass() != null) { 855 // create module instance class 856 I_CmsModuleAction moduleAction = module.getActionInstance(); 857 if (module.getActionClass() != null) { 858 try { 859 moduleAction = (I_CmsModuleAction)Class.forName(module.getActionClass()).newInstance(); 860 } catch (Exception e) { 861 CmsLog.INIT.info( 862 Messages.get().getBundle().key(Messages.INIT_CREATE_INSTANCE_FAILED_1, module.getName()), 863 e); 864 } 865 } 866 if (moduleAction != null) { 867 count++; 868 module.setActionInstance(moduleAction); 869 if (CmsLog.INIT.isInfoEnabled()) { 870 CmsLog.INIT.info( 871 Messages.get().getBundle().key( 872 Messages.INIT_INITIALIZE_MOD_CLASS_1, 873 moduleAction.getClass().getName())); 874 } 875 try { 876 // create a copy of the adminCms so that each module instance does have 877 // it's own context, a shared context might introduce side - effects 878 CmsObject adminCmsCopy = OpenCms.initCmsObject(cms); 879 // initialize the module 880 moduleAction.initialize(adminCmsCopy, configurationManager, module); 881 } catch (Throwable t) { 882 LOG.error( 883 Messages.get().getBundle().key( 884 Messages.LOG_INSTANCE_INIT_ERR_1, 885 moduleAction.getClass().getName()), 886 t); 887 } 888 } 889 } 890 } 891 892 // initialize the export points 893 initModuleExportPoints(); 894 m_importExportRepository.initialize(cms); 895 896 if (CmsLog.INIT.isInfoEnabled()) { 897 CmsLog.INIT.info( 898 Messages.get().getBundle().key(Messages.INIT_NUM_CLASSES_INITIALIZED_1, new Integer(count))); 899 } 900 } 901 902 /** 903 * Replaces an existing module with the one read from an import ZIP file.<p> 904 * 905 * If there is not already a module with the same name installed, then the module will just be imported normally. 906 * 907 * @param cms the CMS context 908 * @param importFile the import file 909 * @param report the report 910 * 911 * @return the module replacement status 912 * @throws CmsException if something goes wrong 913 */ 914 public CmsReplaceModuleInfo replaceModule(CmsObject cms, String importFile, I_CmsReport report) 915 throws CmsException { 916 917 CmsModule module = CmsModuleImportExportHandler.readModuleFromImport(importFile); 918 919 boolean hasModule = hasModule(module.getName()); 920 boolean usedNewUpdate = false; 921 if (hasModule) { 922 Optional<CmsModuleUpdater> optModuleUpdater; 923 if (m_moduleUpdateEnabled) { 924 optModuleUpdater = CmsModuleUpdater.create(cms, importFile, report); 925 } else { 926 optModuleUpdater = Optional.empty(); 927 } 928 if (optModuleUpdater.isPresent()) { 929 usedNewUpdate = true; 930 optModuleUpdater.get().run(); 931 } else { 932 deleteModule(cms, module.getName(), true, report); 933 CmsImportParameters params = new CmsImportParameters(importFile, "/", true); 934 OpenCms.getImportExportManager().importData(cms, report, params); 935 } 936 937 } else { 938 CmsImportParameters params = new CmsImportParameters(importFile, "/", true); 939 OpenCms.getImportExportManager().importData(cms, report, params); 940 } 941 return new CmsReplaceModuleInfo(module, usedNewUpdate); 942 } 943 944 /** 945 * Enables / disables incremental module updates, for testing purposes. 946 * 947 * @param enabled if incremental module updating should be enabled 948 */ 949 public void setModuleUpdateEnabled(boolean enabled) { 950 951 m_moduleUpdateEnabled = enabled; 952 } 953 954 /** 955 * Shuts down all module instance classes managed in this module manager.<p> 956 */ 957 public synchronized void shutDown() { 958 959 int count = 0; 960 Iterator<String> it = getModuleNames().iterator(); 961 while (it.hasNext()) { 962 String moduleName = it.next(); 963 // get the module 964 CmsModule module = m_modules.get(moduleName); 965 if (module == null) { 966 continue; 967 } 968 // get the module action instance 969 I_CmsModuleAction moduleAction = module.getActionInstance(); 970 if (moduleAction == null) { 971 continue; 972 } 973 974 count++; 975 if (CmsLog.INIT.isInfoEnabled()) { 976 CmsLog.INIT.info( 977 Messages.get().getBundle().key( 978 Messages.INIT_SHUTDOWN_MOD_CLASS_1, 979 moduleAction.getClass().getName())); 980 } 981 try { 982 // shut down the module 983 moduleAction.shutDown(module); 984 } catch (Throwable t) { 985 LOG.error( 986 Messages.get().getBundle().key( 987 Messages.LOG_INSTANCE_SHUTDOWN_ERR_1, 988 moduleAction.getClass().getName()), 989 t); 990 } 991 } 992 993 if (CmsLog.INIT.isInfoEnabled()) { 994 CmsLog.INIT.info( 995 Messages.get().getBundle().key(Messages.INIT_SHUTDOWN_NUM_MOD_CLASSES_1, new Integer(count))); 996 } 997 998 if (CmsLog.INIT.isInfoEnabled()) { 999 CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_SHUTDOWN_1, this.getClass().getName())); 1000 } 1001 } 1002 1003 /** 1004 * Updates a already configured module with new values.<p> 1005 * 1006 * @param cms must be initialized with "Admin" permissions 1007 * @param module the module to update 1008 * 1009 * @throws CmsRoleViolationException if the required module manager role permissions are not available 1010 * @throws CmsConfigurationException if a module with this name is not available for updating 1011 */ 1012 public synchronized void updateModule(CmsObject cms, CmsModule module) 1013 throws CmsRoleViolationException, CmsConfigurationException { 1014 1015 // check for module manager role permissions 1016 OpenCms.getRoleManager().checkRole(cms, CmsRole.DATABASE_MANAGER); 1017 1018 CmsModule oldModule = m_modules.get(module.getName()); 1019 1020 if (oldModule == null) { 1021 // module is not currently configured, no update possible 1022 throw new CmsConfigurationException(Messages.get().container(Messages.ERR_OLD_MOD_ERR_1, module.getName())); 1023 } 1024 1025 if (LOG.isInfoEnabled()) { 1026 LOG.info(Messages.get().getBundle().key(Messages.LOG_MOD_UPDATE_1, module.getName())); 1027 } 1028 1029 // indicate that the version number was recently updated 1030 module.getVersion().setUpdated(true); 1031 1032 // initialize (freeze) the module 1033 module.initialize(cms); 1034 1035 // replace old version of module with new version 1036 m_modules.put(module.getName(), module); 1037 1038 try { 1039 I_CmsModuleAction moduleAction = oldModule.getActionInstance(); 1040 // handle module action instance if initialized 1041 if (moduleAction != null) { 1042 moduleAction.moduleUpdate(module); 1043 // set the old action instance 1044 // the new action instance will be used after a system restart 1045 module.setActionInstance(moduleAction); 1046 } 1047 } catch (Throwable t) { 1048 LOG.error(Messages.get().getBundle().key(Messages.LOG_INSTANCE_UPDATE_ERR_1, module.getName()), t); 1049 } 1050 1051 // initialize the export points 1052 initModuleExportPoints(); 1053 1054 // update the configuration 1055 updateModuleConfiguration(); 1056 1057 // reinit the workplace CSS URIs 1058 if (!module.getParameters().isEmpty()) { 1059 OpenCms.getWorkplaceAppManager().initWorkplaceCssUris(this); 1060 } 1061 } 1062 1063 /** 1064 * Updates the module configuration.<p> 1065 */ 1066 public void updateModuleConfiguration() { 1067 1068 OpenCms.writeConfiguration(CmsModuleConfiguration.class); 1069 } 1070 1071 /** 1072 * Initializes the list of export points from all configured modules.<p> 1073 */ 1074 private synchronized void initModuleExportPoints() { 1075 1076 Set<CmsExportPoint> exportPoints = new HashSet<CmsExportPoint>(); 1077 Iterator<CmsModule> i = m_modules.values().iterator(); 1078 while (i.hasNext()) { 1079 CmsModule module = i.next(); 1080 List<CmsExportPoint> moduleExportPoints = module.getExportPoints(); 1081 for (int j = 0; j < moduleExportPoints.size(); j++) { 1082 CmsExportPoint point = moduleExportPoints.get(j); 1083 if (exportPoints.contains(point)) { 1084 if (LOG.isWarnEnabled()) { 1085 LOG.warn( 1086 Messages.get().getBundle().key( 1087 Messages.LOG_DUPLICATE_EXPORT_POINT_2, 1088 point, 1089 module.getName())); 1090 } 1091 } else { 1092 exportPoints.add(point); 1093 if (LOG.isDebugEnabled()) { 1094 LOG.debug( 1095 Messages.get().getBundle().key(Messages.LOG_ADD_EXPORT_POINT_2, point, module.getName())); 1096 } 1097 } 1098 } 1099 } 1100 m_moduleExportPoints = Collections.unmodifiableSet(exportPoints); 1101 } 1102}