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 String className = module.getActionClass(); 364 if ((moduleAction == null) && (className != null)) { 365 Class<?> actionClass = Class.forName(className, false, getClass().getClassLoader()); 366 if (I_CmsModuleAction.class.isAssignableFrom(actionClass)) { 367 moduleAction = ((Class<? extends I_CmsModuleAction>)actionClass).newInstance(); 368 module.setActionInstance(moduleAction); 369 } 370 } 371 // handle module action instance if initialized 372 if (moduleAction != null) { 373 374 moduleAction.moduleUpdate(module); 375 } 376 } catch (Throwable t) { 377 LOG.error(Messages.get().getBundle().key(Messages.LOG_MOD_UPDATE_ERR_1, module.getName()), t); 378 } 379 380 // initialize the export points 381 initModuleExportPoints(); 382 383 // update the configuration 384 updateModuleConfiguration(); 385 386 // reinit the workplace CSS URIs 387 if (!module.getParameters().isEmpty()) { 388 OpenCms.getWorkplaceAppManager().initWorkplaceCssUris(this); 389 } 390 } 391 392 /** 393 * Checks if a modules dependencies are fulfilled.<p> 394 * 395 * The possible values for the <code>mode</code> parameter are:<dl> 396 * <dt>{@link #DEPENDENCY_MODE_DELETE}</dt> 397 * <dd>Check for module deleting, i.e. are other modules dependent on the 398 * given module?</dd> 399 * <dt>{@link #DEPENDENCY_MODE_IMPORT}</dt> 400 * <dd>Check for module importing, i.e. are all dependencies required by the given 401 * module available?</dd></dl> 402 * 403 * @param module the module to check the dependencies for 404 * @param mode the dependency check mode 405 * @return a list of dependencies that are not fulfilled, if empty all dependencies are fulfilled 406 */ 407 public List<CmsModuleDependency> checkDependencies(CmsModule module, int mode) { 408 409 List<CmsModuleDependency> result = new ArrayList<CmsModuleDependency>(); 410 411 if (mode == DEPENDENCY_MODE_DELETE) { 412 // delete mode, check if other modules depend on this module 413 Iterator<CmsModule> i = m_modules.values().iterator(); 414 while (i.hasNext()) { 415 CmsModule otherModule = i.next(); 416 CmsModuleDependency dependency = otherModule.checkDependency(module); 417 if (dependency != null) { 418 // dependency found, add to list 419 result.add(new CmsModuleDependency(otherModule.getName(), otherModule.getVersion())); 420 } 421 } 422 423 } else if (mode == DEPENDENCY_MODE_IMPORT) { 424 // import mode, check if all module dependencies are fulfilled 425 Iterator<CmsModule> i = m_modules.values().iterator(); 426 // add all dependencies that must be found 427 result.addAll(module.getDependencies()); 428 while (i.hasNext() && (result.size() > 0)) { 429 CmsModule otherModule = i.next(); 430 CmsModuleDependency dependency = module.checkDependency(otherModule); 431 if (dependency != null) { 432 // dependency found, remove from list 433 result.remove(dependency); 434 } 435 } 436 } else { 437 // invalid mode selected 438 throw new CmsRuntimeException( 439 Messages.get().container(Messages.ERR_CHECK_DEPENDENCY_INVALID_MODE_1, new Integer(mode))); 440 } 441 442 return result; 443 } 444 445 /** 446 * Checks the module selection list for consistency, that means 447 * that if a module is selected, all its dependencies are also selected.<p> 448 * 449 * The module dependencies are get from the installed modules or 450 * from the module manifest.xml files found in the given FRS path.<p> 451 * 452 * @param moduleNames a list of module names 453 * @param rfsAbsPath a RFS absolute path to search for modules, or <code>null</code> to use the installed modules 454 * @param forDeletion there are two modes, one for installation of modules, and one for deletion. 455 * 456 * @throws CmsIllegalArgumentException if the module list is not consistent 457 * @throws CmsConfigurationException if something goes wrong 458 */ 459 public void checkModuleSelectionList(List<String> moduleNames, String rfsAbsPath, boolean forDeletion) 460 throws CmsIllegalArgumentException, CmsConfigurationException { 461 462 Map<String, List<String>> moduleDependencies = buildDepsForAllModules(rfsAbsPath, forDeletion); 463 Iterator<String> itMods = moduleNames.iterator(); 464 while (itMods.hasNext()) { 465 String moduleName = itMods.next(); 466 List<String> dependencies = moduleDependencies.get(moduleName); 467 if (dependencies != null) { 468 List<String> depModules = new ArrayList<String>(dependencies); 469 depModules.removeAll(moduleNames); 470 if (!depModules.isEmpty()) { 471 throw new CmsIllegalArgumentException( 472 Messages.get().container( 473 Messages.ERR_MODULE_SELECTION_INCONSISTENT_2, 474 moduleName, 475 depModules.toString())); 476 } 477 } 478 } 479 } 480 481 /** 482 * Deletes a module from the configuration.<p> 483 * 484 * @param cms must be initialized with "Admin" permissions 485 * @param moduleName the name of the module to delete 486 * @param replace indicates if the module is replaced (true) or finally deleted (false) 487 * @param preserveLibs <code>true</code> to keep any exported file exported into the WEB-INF lib folder 488 * @param report the report to print progress messages to 489 * 490 * @throws CmsRoleViolationException if the required module manager role permissions are not available 491 * @throws CmsConfigurationException if a module with this name is not available for deleting 492 * @throws CmsLockException if the module resources can not be locked 493 */ 494 public synchronized void deleteModule( 495 CmsObject cms, 496 String moduleName, 497 boolean replace, 498 boolean preserveLibs, 499 I_CmsReport report) 500 throws CmsRoleViolationException, CmsConfigurationException, CmsLockException { 501 502 // check for module manager role permissions 503 OpenCms.getRoleManager().checkRole(cms, CmsRole.DATABASE_MANAGER); 504 505 if (!m_modules.containsKey(moduleName)) { 506 // module is not currently configured, no update possible 507 throw new CmsConfigurationException( 508 Messages.get().container(Messages.ERR_MODULE_NOT_CONFIGURED_1, moduleName)); 509 } 510 511 if (LOG.isInfoEnabled()) { 512 LOG.info(Messages.get().getBundle().key(Messages.LOG_DEL_MOD_1, moduleName)); 513 } 514 515 CmsModule module = m_modules.get(moduleName); 516 String importSite = module.getSite(); 517 if (!CmsStringUtil.isEmptyOrWhitespaceOnly(importSite)) { 518 CmsObject newCms; 519 try { 520 newCms = OpenCms.initCmsObject(cms); 521 newCms.getRequestContext().setSiteRoot(importSite); 522 cms = newCms; 523 } catch (CmsException e) { 524 LOG.error(e.getLocalizedMessage(), e); 525 } 526 } 527 528 if (!replace) { 529 // module is deleted, not replaced 530 531 // perform dependency check 532 List<CmsModuleDependency> dependencies = checkDependencies(module, DEPENDENCY_MODE_DELETE); 533 if (!dependencies.isEmpty()) { 534 StringBuffer message = new StringBuffer(); 535 Iterator<CmsModuleDependency> it = dependencies.iterator(); 536 while (it.hasNext()) { 537 message.append(" ").append(it.next().getName()).append("\r\n"); 538 } 539 throw new CmsConfigurationException( 540 Messages.get().container(Messages.ERR_MOD_DEPENDENCIES_2, moduleName, message.toString())); 541 } 542 try { 543 I_CmsModuleAction moduleAction = module.getActionInstance(); 544 // handle module action instance if initialized 545 if (moduleAction != null) { 546 moduleAction.moduleUninstall(module); 547 } 548 } catch (Throwable t) { 549 LOG.error(Messages.get().getBundle().key(Messages.LOG_MOD_UNINSTALL_ERR_1, moduleName), t); 550 report.println( 551 Messages.get().container(Messages.LOG_MOD_UNINSTALL_ERR_1, moduleName), 552 I_CmsReport.FORMAT_WARNING); 553 } 554 } 555 556 boolean removeResourceTypes = !module.getResourceTypes().isEmpty(); 557 if (removeResourceTypes) { 558 // mark the resource manager to reinitialize if necessary 559 OpenCms.getWorkplaceManager().removeExplorerTypeSettings(module); 560 } 561 562 CmsProject previousProject = cms.getRequestContext().getCurrentProject(); 563 // try to create a new offline project for deletion 564 CmsProject deleteProject = null; 565 try { 566 // try to read a (leftover) module delete project 567 deleteProject = cms.readProject( 568 Messages.get().getBundle(cms.getRequestContext().getLocale()).key( 569 Messages.GUI_DELETE_MODULE_PROJECT_NAME_1, 570 new Object[] {moduleName})); 571 } catch (CmsException e) { 572 try { 573 // create a Project to delete the module 574 deleteProject = cms.createProject( 575 Messages.get().getBundle(cms.getRequestContext().getLocale()).key( 576 Messages.GUI_DELETE_MODULE_PROJECT_NAME_1, 577 new Object[] {moduleName}), 578 Messages.get().getBundle(cms.getRequestContext().getLocale()).key( 579 Messages.GUI_DELETE_MODULE_PROJECT_DESC_1, 580 new Object[] {moduleName}), 581 OpenCms.getDefaultUsers().getGroupAdministrators(), 582 OpenCms.getDefaultUsers().getGroupAdministrators(), 583 CmsProject.PROJECT_TYPE_TEMPORARY); 584 } catch (CmsException e1) { 585 throw new CmsConfigurationException(e1.getMessageContainer(), e1); 586 } 587 } 588 589 try { 590 cms.getRequestContext().setCurrentProject(deleteProject); 591 592 // check locks 593 List<String> lockedResources = new ArrayList<String>(); 594 CmsLockFilter filter1 = CmsLockFilter.FILTER_ALL.filterNotLockableByUser( 595 cms.getRequestContext().getCurrentUser()); 596 CmsLockFilter filter2 = CmsLockFilter.FILTER_INHERITED; 597 List<String> moduleResources = module.getResources(); 598 for (int iLock = 0; iLock < moduleResources.size(); iLock++) { 599 String resourceName = moduleResources.get(iLock); 600 try { 601 lockedResources.addAll(cms.getLockedResources(resourceName, filter1)); 602 lockedResources.addAll(cms.getLockedResources(resourceName, filter2)); 603 } catch (CmsException e) { 604 // may happen if the resource has already been deleted 605 if (LOG.isDebugEnabled()) { 606 LOG.debug(e.getMessageContainer(), e); 607 } 608 report.println(e.getMessageContainer(), I_CmsReport.FORMAT_WARNING); 609 } 610 } 611 if (!lockedResources.isEmpty()) { 612 CmsMessageContainer msg = Messages.get().container( 613 Messages.ERR_DELETE_MODULE_CHECK_LOCKS_2, 614 moduleName, 615 CmsStringUtil.collectionAsString(lockedResources, ",")); 616 report.addError(msg.key(cms.getRequestContext().getLocale())); 617 report.println(msg); 618 cms.getRequestContext().setCurrentProject(previousProject); 619 try { 620 cms.deleteProject(deleteProject.getUuid()); 621 } catch (CmsException e1) { 622 throw new CmsConfigurationException(e1.getMessageContainer(), e1); 623 } 624 throw new CmsLockException(msg); 625 } 626 } finally { 627 cms.getRequestContext().setCurrentProject(previousProject); 628 } 629 630 // now remove the module 631 module = m_modules.remove(moduleName); 632 633 if (preserveLibs) { 634 // to preserve the module libs, remove the responsible export points, before deleting module resources 635 Set<CmsExportPoint> exportPoints = new HashSet<CmsExportPoint>(m_moduleExportPoints); 636 Iterator<CmsExportPoint> it = exportPoints.iterator(); 637 while (it.hasNext()) { 638 CmsExportPoint point = it.next(); 639 if ((point.getUri().endsWith(module.getName() + "/lib/") 640 || point.getUri().endsWith(module.getName() + "/lib")) 641 && point.getConfiguredDestination().equals("WEB-INF/lib/")) { 642 it.remove(); 643 } 644 } 645 646 m_moduleExportPoints = Collections.unmodifiableSet(exportPoints); 647 } 648 649 try { 650 cms.getRequestContext().setCurrentProject(deleteProject); 651 652 // copy the module resources to the project 653 List<CmsResource> moduleResources = CmsModule.calculateModuleResources(cms, module); 654 for (CmsResource resource : moduleResources) { 655 try { 656 cms.copyResourceToProject(resource); 657 } catch (CmsException e) { 658 // may happen if the resource has already been deleted 659 if (LOG.isDebugEnabled()) { 660 LOG.debug( 661 Messages.get().getBundle().key( 662 Messages.LOG_MOVE_RESOURCE_FAILED_1, 663 cms.getSitePath(resource))); 664 } 665 report.println(e.getMessageContainer(), I_CmsReport.FORMAT_WARNING); 666 } 667 } 668 669 report.print(Messages.get().container(Messages.RPT_DELETE_MODULE_BEGIN_0), I_CmsReport.FORMAT_HEADLINE); 670 report.println( 671 org.opencms.report.Messages.get().container( 672 org.opencms.report.Messages.RPT_ARGUMENT_HTML_ITAG_1, 673 moduleName), 674 I_CmsReport.FORMAT_HEADLINE); 675 676 // move through all module resources and delete them 677 for (CmsResource resource : moduleResources) { 678 String sitePath = cms.getSitePath(resource); 679 try { 680 if (LOG.isDebugEnabled()) { 681 LOG.debug(Messages.get().getBundle().key(Messages.LOG_DEL_MOD_RESOURCE_1, sitePath)); 682 } 683 CmsLock lock = cms.getLock(resource); 684 if (lock.isUnlocked()) { 685 // lock the resource 686 cms.lockResource(resource); 687 } else if (lock.isLockableBy(cms.getRequestContext().getCurrentUser())) { 688 // steal the resource 689 cms.changeLock(resource); 690 } 691 if (!resource.getState().isDeleted()) { 692 // delete the resource 693 cms.deleteResource(sitePath, CmsResource.DELETE_PRESERVE_SIBLINGS); 694 } 695 // update the report 696 report.print(Messages.get().container(Messages.RPT_DELETE_0), I_CmsReport.FORMAT_NOTE); 697 report.println( 698 org.opencms.report.Messages.get().container( 699 org.opencms.report.Messages.RPT_ARGUMENT_1, 700 sitePath)); 701 if (!resource.getState().isNew()) { 702 // unlock the resource (so it gets deleted with next publish) 703 cms.unlockResource(resource); 704 } 705 } catch (CmsException e) { 706 // ignore the exception and delete the next resource 707 LOG.error(Messages.get().getBundle().key(Messages.LOG_DEL_MOD_EXC_1, sitePath), e); 708 report.println(e.getMessageContainer(), I_CmsReport.FORMAT_WARNING); 709 } 710 } 711 712 report.println(Messages.get().container(Messages.RPT_PUBLISH_PROJECT_BEGIN_0), I_CmsReport.FORMAT_HEADLINE); 713 714 // now unlock and publish the project 715 cms.unlockProject(deleteProject.getUuid()); 716 OpenCms.getPublishManager().publishProject(cms, report); 717 OpenCms.getPublishManager().waitWhileRunning(); 718 719 report.println(Messages.get().container(Messages.RPT_PUBLISH_PROJECT_END_0), I_CmsReport.FORMAT_HEADLINE); 720 report.println(Messages.get().container(Messages.RPT_DELETE_MODULE_END_0), I_CmsReport.FORMAT_HEADLINE); 721 } catch (CmsException e) { 722 throw new CmsConfigurationException(e.getMessageContainer(), e); 723 } finally { 724 cms.getRequestContext().setCurrentProject(previousProject); 725 } 726 727 // initialize the export points (removes export points from deleted module) 728 initModuleExportPoints(); 729 730 // update the configuration 731 updateModuleConfiguration(); 732 733 // reinit the manager is necessary 734 if (removeResourceTypes) { 735 OpenCms.getResourceManager().initialize(cms); 736 } 737 738 // reinit the workplace CSS URIs 739 if (!module.getParameters().isEmpty()) { 740 OpenCms.getWorkplaceAppManager().initWorkplaceCssUris(this); 741 } 742 } 743 744 /** 745 * Deletes a module from the configuration.<p> 746 * 747 * @param cms must be initialized with "Admin" permissions 748 * @param moduleName the name of the module to delete 749 * @param replace indicates if the module is replaced (true) or finally deleted (false) 750 * @param report the report to print progress messages to 751 * 752 * @throws CmsRoleViolationException if the required module manager role permissions are not available 753 * @throws CmsConfigurationException if a module with this name is not available for deleting 754 * @throws CmsLockException if the module resources can not be locked 755 */ 756 public synchronized void deleteModule(CmsObject cms, String moduleName, boolean replace, I_CmsReport report) 757 throws CmsRoleViolationException, CmsConfigurationException, CmsLockException { 758 759 deleteModule(cms, moduleName, replace, false, report); 760 } 761 762 /** 763 * Returns a list of installed modules.<p> 764 * 765 * @return a list of <code>{@link CmsModule}</code> objects 766 */ 767 public List<CmsModule> getAllInstalledModules() { 768 769 return new ArrayList<CmsModule>(m_modules.values()); 770 } 771 772 /** 773 * Returns the (immutable) list of configured module export points.<p> 774 * 775 * @return the (immutable) list of configured module export points 776 * @see CmsExportPoint 777 */ 778 public Set<CmsExportPoint> getExportPoints() { 779 780 return m_moduleExportPoints; 781 } 782 783 /** 784 * Returns the importExportRepository.<p> 785 * 786 * @return the importExportRepository 787 */ 788 public CmsModuleImportExportRepository getImportExportRepository() { 789 790 return m_importExportRepository; 791 } 792 793 /** 794 * Returns the module with the given module name, 795 * or <code>null</code> if no module with the given name is configured.<p> 796 * 797 * @param name the name of the module to return 798 * @return the module with the given module name 799 */ 800 public CmsModule getModule(String name) { 801 802 return m_modules.get(name); 803 } 804 805 /** 806 * Returns the set of names of all the installed modules.<p> 807 * 808 * @return the set of names of all the installed modules 809 */ 810 public Set<String> getModuleNames() { 811 812 synchronized (m_modules) { 813 return new HashSet<String>(m_modules.keySet()); 814 } 815 } 816 817 /** 818 * Checks if this module manager has a module with the given name installed.<p> 819 * 820 * @param name the name of the module to check 821 * @return true if this module manager has a module with the given name installed 822 */ 823 public boolean hasModule(String name) { 824 825 return m_modules.containsKey(name); 826 } 827 828 /** 829 * Initializes all module instance classes managed in this module manager.<p> 830 * 831 * @param cms an initialized CmsObject with "manage modules" role permissions 832 * @param configurationManager the initialized OpenCms configuration manager 833 * 834 * @throws CmsRoleViolationException if the provided OpenCms context does not have "manage modules" role permissions 835 */ 836 public synchronized void initialize(CmsObject cms, CmsConfigurationManager configurationManager) 837 throws CmsRoleViolationException { 838 839 if (OpenCms.getRunLevel() > OpenCms.RUNLEVEL_1_CORE_OBJECT) { 840 // certain test cases won't have an OpenCms context 841 OpenCms.getRoleManager().checkRole(cms, CmsRole.DATABASE_MANAGER); 842 } 843 844 Iterator<String> it; 845 int count = 0; 846 it = m_modules.keySet().iterator(); 847 while (it.hasNext()) { 848 // get the module description 849 CmsModule module = m_modules.get(it.next()); 850 851 if (module.getActionClass() != null) { 852 // create module instance class 853 I_CmsModuleAction moduleAction = module.getActionInstance(); 854 if (module.getActionClass() != null) { 855 try { 856 moduleAction = (I_CmsModuleAction)Class.forName(module.getActionClass()).newInstance(); 857 } catch (Exception e) { 858 CmsLog.INIT.info( 859 Messages.get().getBundle().key(Messages.INIT_CREATE_INSTANCE_FAILED_1, module.getName()), 860 e); 861 } 862 } 863 if (moduleAction != null) { 864 count++; 865 module.setActionInstance(moduleAction); 866 if (CmsLog.INIT.isInfoEnabled()) { 867 CmsLog.INIT.info( 868 Messages.get().getBundle().key( 869 Messages.INIT_INITIALIZE_MOD_CLASS_1, 870 moduleAction.getClass().getName())); 871 } 872 try { 873 // create a copy of the adminCms so that each module instance does have 874 // it's own context, a shared context might introduce side - effects 875 CmsObject adminCmsCopy = OpenCms.initCmsObject(cms); 876 // initialize the module 877 moduleAction.initialize(adminCmsCopy, configurationManager, module); 878 } catch (Throwable t) { 879 LOG.error( 880 Messages.get().getBundle().key( 881 Messages.LOG_INSTANCE_INIT_ERR_1, 882 moduleAction.getClass().getName()), 883 t); 884 } 885 } 886 } 887 } 888 889 // initialize the export points 890 initModuleExportPoints(); 891 m_importExportRepository.initialize(cms); 892 893 if (CmsLog.INIT.isInfoEnabled()) { 894 CmsLog.INIT.info( 895 Messages.get().getBundle().key(Messages.INIT_NUM_CLASSES_INITIALIZED_1, new Integer(count))); 896 } 897 } 898 899 /** 900 * Replaces an existing module with the one read from an import ZIP file.<p> 901 * 902 * If there is not already a module with the same name installed, then the module will just be imported normally. 903 * 904 * @param cms the CMS context 905 * @param importFile the import file 906 * @param report the report 907 * 908 * @return the module replacement status 909 * @throws CmsException if something goes wrong 910 */ 911 public CmsReplaceModuleInfo replaceModule(CmsObject cms, String importFile, I_CmsReport report) 912 throws CmsException { 913 914 CmsModule module = CmsModuleImportExportHandler.readModuleFromImport(importFile); 915 916 boolean hasModule = hasModule(module.getName()); 917 boolean usedNewUpdate = false; 918 if (hasModule) { 919 Optional<CmsModuleUpdater> optModuleUpdater = CmsModuleUpdater.create(cms, importFile, report); 920 if (optModuleUpdater.isPresent()) { 921 usedNewUpdate = true; 922 optModuleUpdater.get().run(); 923 } else { 924 deleteModule(cms, module.getName(), true, report); 925 CmsImportParameters params = new CmsImportParameters(importFile, "/", true); 926 OpenCms.getImportExportManager().importData(cms, report, params); 927 } 928 929 } else { 930 CmsImportParameters params = new CmsImportParameters(importFile, "/", true); 931 OpenCms.getImportExportManager().importData(cms, report, params); 932 } 933 return new CmsReplaceModuleInfo(module, usedNewUpdate); 934 } 935 936 /** 937 * Shuts down all module instance classes managed in this module manager.<p> 938 */ 939 public synchronized void shutDown() { 940 941 int count = 0; 942 Iterator<String> it = getModuleNames().iterator(); 943 while (it.hasNext()) { 944 String moduleName = it.next(); 945 // get the module 946 CmsModule module = m_modules.get(moduleName); 947 if (module == null) { 948 continue; 949 } 950 // get the module action instance 951 I_CmsModuleAction moduleAction = module.getActionInstance(); 952 if (moduleAction == null) { 953 continue; 954 } 955 956 count++; 957 if (CmsLog.INIT.isInfoEnabled()) { 958 CmsLog.INIT.info( 959 Messages.get().getBundle().key( 960 Messages.INIT_SHUTDOWN_MOD_CLASS_1, 961 moduleAction.getClass().getName())); 962 } 963 try { 964 // shut down the module 965 moduleAction.shutDown(module); 966 } catch (Throwable t) { 967 LOG.error( 968 Messages.get().getBundle().key( 969 Messages.LOG_INSTANCE_SHUTDOWN_ERR_1, 970 moduleAction.getClass().getName()), 971 t); 972 } 973 } 974 975 if (CmsLog.INIT.isInfoEnabled()) { 976 CmsLog.INIT.info( 977 Messages.get().getBundle().key(Messages.INIT_SHUTDOWN_NUM_MOD_CLASSES_1, new Integer(count))); 978 } 979 980 if (CmsLog.INIT.isInfoEnabled()) { 981 CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_SHUTDOWN_1, this.getClass().getName())); 982 } 983 } 984 985 /** 986 * Updates a already configured module with new values.<p> 987 * 988 * @param cms must be initialized with "Admin" permissions 989 * @param module the module to update 990 * 991 * @throws CmsRoleViolationException if the required module manager role permissions are not available 992 * @throws CmsConfigurationException if a module with this name is not available for updating 993 */ 994 public synchronized void updateModule(CmsObject cms, CmsModule module) 995 throws CmsRoleViolationException, CmsConfigurationException { 996 997 // check for module manager role permissions 998 OpenCms.getRoleManager().checkRole(cms, CmsRole.DATABASE_MANAGER); 999 1000 CmsModule oldModule = m_modules.get(module.getName()); 1001 1002 if (oldModule == null) { 1003 // module is not currently configured, no update possible 1004 throw new CmsConfigurationException(Messages.get().container(Messages.ERR_OLD_MOD_ERR_1, module.getName())); 1005 } 1006 1007 if (LOG.isInfoEnabled()) { 1008 LOG.info(Messages.get().getBundle().key(Messages.LOG_MOD_UPDATE_1, module.getName())); 1009 } 1010 1011 // indicate that the version number was recently updated 1012 module.getVersion().setUpdated(true); 1013 1014 // initialize (freeze) the module 1015 module.initialize(cms); 1016 1017 // replace old version of module with new version 1018 m_modules.put(module.getName(), module); 1019 1020 try { 1021 I_CmsModuleAction moduleAction = oldModule.getActionInstance(); 1022 // handle module action instance if initialized 1023 if (moduleAction != null) { 1024 moduleAction.moduleUpdate(module); 1025 // set the old action instance 1026 // the new action instance will be used after a system restart 1027 module.setActionInstance(moduleAction); 1028 } 1029 } catch (Throwable t) { 1030 LOG.error(Messages.get().getBundle().key(Messages.LOG_INSTANCE_UPDATE_ERR_1, module.getName()), t); 1031 } 1032 1033 // initialize the export points 1034 initModuleExportPoints(); 1035 1036 // update the configuration 1037 updateModuleConfiguration(); 1038 1039 // reinit the workplace CSS URIs 1040 if (!module.getParameters().isEmpty()) { 1041 OpenCms.getWorkplaceAppManager().initWorkplaceCssUris(this); 1042 } 1043 } 1044 1045 /** 1046 * Updates the module configuration.<p> 1047 */ 1048 public void updateModuleConfiguration() { 1049 1050 OpenCms.writeConfiguration(CmsModuleConfiguration.class); 1051 } 1052 1053 /** 1054 * Initializes the list of export points from all configured modules.<p> 1055 */ 1056 private synchronized void initModuleExportPoints() { 1057 1058 Set<CmsExportPoint> exportPoints = new HashSet<CmsExportPoint>(); 1059 Iterator<CmsModule> i = m_modules.values().iterator(); 1060 while (i.hasNext()) { 1061 CmsModule module = i.next(); 1062 List<CmsExportPoint> moduleExportPoints = module.getExportPoints(); 1063 for (int j = 0; j < moduleExportPoints.size(); j++) { 1064 CmsExportPoint point = moduleExportPoints.get(j); 1065 if (exportPoints.contains(point)) { 1066 if (LOG.isWarnEnabled()) { 1067 LOG.warn( 1068 Messages.get().getBundle().key( 1069 Messages.LOG_DUPLICATE_EXPORT_POINT_2, 1070 point, 1071 module.getName())); 1072 } 1073 } else { 1074 exportPoints.add(point); 1075 if (LOG.isDebugEnabled()) { 1076 LOG.debug( 1077 Messages.get().getBundle().key(Messages.LOG_ADD_EXPORT_POINT_2, point, module.getName())); 1078 } 1079 } 1080 } 1081 } 1082 m_moduleExportPoints = Collections.unmodifiableSet(exportPoints); 1083 } 1084}