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