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 if (moduleResources.size() > 0) { 716 report.println( 717 Messages.get().container(Messages.RPT_PUBLISH_PROJECT_BEGIN_0), 718 I_CmsReport.FORMAT_HEADLINE); 719 // now unlock and publish the project 720 cms.unlockProject(deleteProject.getUuid()); 721 OpenCms.getPublishManager().publishProject(cms, report); 722 OpenCms.getPublishManager().waitWhileRunning(); 723 report.println( 724 Messages.get().container(Messages.RPT_PUBLISH_PROJECT_END_0), 725 I_CmsReport.FORMAT_HEADLINE); 726 report.println(Messages.get().container(Messages.RPT_DELETE_MODULE_END_0), I_CmsReport.FORMAT_HEADLINE); 727 } 728 729 } catch (CmsException e) { 730 throw new CmsConfigurationException(e.getMessageContainer(), e); 731 } finally { 732 cms.getRequestContext().setCurrentProject(previousProject); 733 } 734 735 // initialize the export points (removes export points from deleted module) 736 initModuleExportPoints(); 737 738 // update the configuration 739 updateModuleConfiguration(); 740 741 // reinit the manager is necessary 742 if (removeResourceTypes) { 743 OpenCms.getResourceManager().initialize(cms); 744 } 745 746 // reinit the workplace CSS URIs 747 if (!module.getParameters().isEmpty()) { 748 OpenCms.getWorkplaceAppManager().initWorkplaceCssUris(this); 749 } 750 } 751 752 /** 753 * Deletes a module from the configuration.<p> 754 * 755 * @param cms must be initialized with "Admin" permissions 756 * @param moduleName the name of the module to delete 757 * @param replace indicates if the module is replaced (true) or finally deleted (false) 758 * @param report the report to print progress messages to 759 * 760 * @throws CmsRoleViolationException if the required module manager role permissions are not available 761 * @throws CmsConfigurationException if a module with this name is not available for deleting 762 * @throws CmsLockException if the module resources can not be locked 763 */ 764 public synchronized void deleteModule(CmsObject cms, String moduleName, boolean replace, I_CmsReport report) 765 throws CmsRoleViolationException, CmsConfigurationException, CmsLockException { 766 767 deleteModule(cms, moduleName, replace, false, report); 768 } 769 770 /** 771 * Returns a list of installed modules.<p> 772 * 773 * @return a list of <code>{@link CmsModule}</code> objects 774 */ 775 public List<CmsModule> getAllInstalledModules() { 776 777 return new ArrayList<CmsModule>(m_modules.values()); 778 } 779 780 /** 781 * Returns the (immutable) list of configured module export points.<p> 782 * 783 * @return the (immutable) list of configured module export points 784 * @see CmsExportPoint 785 */ 786 public Set<CmsExportPoint> getExportPoints() { 787 788 return m_moduleExportPoints; 789 } 790 791 /** 792 * Returns the importExportRepository.<p> 793 * 794 * @return the importExportRepository 795 */ 796 public CmsModuleImportExportRepository getImportExportRepository() { 797 798 return m_importExportRepository; 799 } 800 801 /** 802 * Returns the module with the given module name, 803 * or <code>null</code> if no module with the given name is configured.<p> 804 * 805 * @param name the name of the module to return 806 * @return the module with the given module name 807 */ 808 public CmsModule getModule(String name) { 809 810 return m_modules.get(name); 811 } 812 813 /** 814 * Returns the set of names of all the installed modules.<p> 815 * 816 * @return the set of names of all the installed modules 817 */ 818 public Set<String> getModuleNames() { 819 820 synchronized (m_modules) { 821 return new HashSet<String>(m_modules.keySet()); 822 } 823 } 824 825 /** 826 * Checks if this module manager has a module with the given name installed.<p> 827 * 828 * @param name the name of the module to check 829 * @return true if this module manager has a module with the given name installed 830 */ 831 public boolean hasModule(String name) { 832 833 return m_modules.containsKey(name); 834 } 835 836 /** 837 * Initializes all module instance classes managed in this module manager.<p> 838 * 839 * @param cms an initialized CmsObject with "manage modules" role permissions 840 * @param configurationManager the initialized OpenCms configuration manager 841 * 842 * @throws CmsRoleViolationException if the provided OpenCms context does not have "manage modules" role permissions 843 */ 844 public synchronized void initialize(CmsObject cms, CmsConfigurationManager configurationManager) 845 throws CmsRoleViolationException { 846 847 if (OpenCms.getRunLevel() > OpenCms.RUNLEVEL_1_CORE_OBJECT) { 848 // certain test cases won't have an OpenCms context 849 OpenCms.getRoleManager().checkRole(cms, CmsRole.DATABASE_MANAGER); 850 } 851 852 Iterator<String> it; 853 int count = 0; 854 it = m_modules.keySet().iterator(); 855 while (it.hasNext()) { 856 // get the module description 857 CmsModule module = m_modules.get(it.next()); 858 859 if (module.getActionClass() != null) { 860 // create module instance class 861 I_CmsModuleAction moduleAction = module.getActionInstance(); 862 if (module.getActionClass() != null) { 863 try { 864 moduleAction = (I_CmsModuleAction)Class.forName(module.getActionClass()).newInstance(); 865 } catch (Exception e) { 866 CmsLog.INIT.info( 867 Messages.get().getBundle().key(Messages.INIT_CREATE_INSTANCE_FAILED_1, module.getName()), 868 e); 869 } 870 } 871 if (moduleAction != null) { 872 count++; 873 module.setActionInstance(moduleAction); 874 if (CmsLog.INIT.isInfoEnabled()) { 875 CmsLog.INIT.info( 876 Messages.get().getBundle().key( 877 Messages.INIT_INITIALIZE_MOD_CLASS_1, 878 moduleAction.getClass().getName())); 879 } 880 try { 881 // create a copy of the adminCms so that each module instance does have 882 // it's own context, a shared context might introduce side - effects 883 CmsObject adminCmsCopy = OpenCms.initCmsObject(cms); 884 // initialize the module 885 moduleAction.initialize(adminCmsCopy, configurationManager, module); 886 } catch (Throwable t) { 887 LOG.error( 888 Messages.get().getBundle().key( 889 Messages.LOG_INSTANCE_INIT_ERR_1, 890 moduleAction.getClass().getName()), 891 t); 892 } 893 } 894 } 895 } 896 897 // initialize the export points 898 initModuleExportPoints(); 899 m_importExportRepository.initialize(cms); 900 901 if (CmsLog.INIT.isInfoEnabled()) { 902 CmsLog.INIT.info( 903 Messages.get().getBundle().key(Messages.INIT_NUM_CLASSES_INITIALIZED_1, new Integer(count))); 904 } 905 } 906 907 /** 908 * Replaces an existing module with the one read from an import ZIP file.<p> 909 * 910 * If there is not already a module with the same name installed, then the module will just be imported normally. 911 * 912 * @param cms the CMS context 913 * @param importFile the import file 914 * @param report the report 915 * 916 * @return the module replacement status 917 * @throws CmsException if something goes wrong 918 */ 919 public CmsReplaceModuleInfo replaceModule(CmsObject cms, String importFile, I_CmsReport report) 920 throws CmsException { 921 922 CmsModule module = CmsModuleImportExportHandler.readModuleFromImport(importFile); 923 924 boolean hasModule = hasModule(module.getName()); 925 boolean usedNewUpdate = false; 926 if (hasModule) { 927 Optional<CmsModuleUpdater> optModuleUpdater; 928 if (m_moduleUpdateEnabled) { 929 optModuleUpdater = CmsModuleUpdater.create(cms, importFile, report); 930 } else { 931 optModuleUpdater = Optional.empty(); 932 } 933 if (optModuleUpdater.isPresent()) { 934 usedNewUpdate = true; 935 optModuleUpdater.get().run(); 936 } else { 937 deleteModule(cms, module.getName(), true, report); 938 CmsImportParameters params = new CmsImportParameters(importFile, "/", true); 939 OpenCms.getImportExportManager().importData(cms, report, params); 940 } 941 942 } else { 943 CmsImportParameters params = new CmsImportParameters(importFile, "/", true); 944 OpenCms.getImportExportManager().importData(cms, report, params); 945 } 946 return new CmsReplaceModuleInfo(module, usedNewUpdate); 947 } 948 949 /** 950 * Enables / disables incremental module updates, for testing purposes. 951 * 952 * @param enabled if incremental module updating should be enabled 953 */ 954 public void setModuleUpdateEnabled(boolean enabled) { 955 956 m_moduleUpdateEnabled = enabled; 957 } 958 959 /** 960 * Shuts down all module instance classes managed in this module manager.<p> 961 */ 962 public synchronized void shutDown() { 963 964 int count = 0; 965 Iterator<String> it = getModuleNames().iterator(); 966 while (it.hasNext()) { 967 String moduleName = it.next(); 968 // get the module 969 CmsModule module = m_modules.get(moduleName); 970 if (module == null) { 971 continue; 972 } 973 // get the module action instance 974 I_CmsModuleAction moduleAction = module.getActionInstance(); 975 if (moduleAction == null) { 976 continue; 977 } 978 979 count++; 980 if (CmsLog.INIT.isInfoEnabled()) { 981 CmsLog.INIT.info( 982 Messages.get().getBundle().key( 983 Messages.INIT_SHUTDOWN_MOD_CLASS_1, 984 moduleAction.getClass().getName())); 985 } 986 try { 987 // shut down the module 988 moduleAction.shutDown(module); 989 } catch (Throwable t) { 990 LOG.error( 991 Messages.get().getBundle().key( 992 Messages.LOG_INSTANCE_SHUTDOWN_ERR_1, 993 moduleAction.getClass().getName()), 994 t); 995 } 996 } 997 998 if (CmsLog.INIT.isInfoEnabled()) { 999 CmsLog.INIT.info( 1000 Messages.get().getBundle().key(Messages.INIT_SHUTDOWN_NUM_MOD_CLASSES_1, new Integer(count))); 1001 } 1002 1003 if (CmsLog.INIT.isInfoEnabled()) { 1004 CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_SHUTDOWN_1, this.getClass().getName())); 1005 } 1006 } 1007 1008 /** 1009 * Updates a already configured module with new values.<p> 1010 * 1011 * @param cms must be initialized with "Admin" permissions 1012 * @param module the module to update 1013 * 1014 * @throws CmsRoleViolationException if the required module manager role permissions are not available 1015 * @throws CmsConfigurationException if a module with this name is not available for updating 1016 */ 1017 public synchronized void updateModule(CmsObject cms, CmsModule module) 1018 throws CmsRoleViolationException, CmsConfigurationException { 1019 1020 // check for module manager role permissions 1021 OpenCms.getRoleManager().checkRole(cms, CmsRole.DATABASE_MANAGER); 1022 1023 CmsModule oldModule = m_modules.get(module.getName()); 1024 1025 if (oldModule == null) { 1026 // module is not currently configured, no update possible 1027 throw new CmsConfigurationException(Messages.get().container(Messages.ERR_OLD_MOD_ERR_1, module.getName())); 1028 } 1029 1030 if (LOG.isInfoEnabled()) { 1031 LOG.info(Messages.get().getBundle().key(Messages.LOG_MOD_UPDATE_1, module.getName())); 1032 } 1033 1034 // indicate that the version number was recently updated 1035 module.getVersion().setUpdated(true); 1036 1037 // initialize (freeze) the module 1038 module.initialize(cms); 1039 1040 // replace old version of module with new version 1041 m_modules.put(module.getName(), module); 1042 1043 try { 1044 I_CmsModuleAction moduleAction = oldModule.getActionInstance(); 1045 // handle module action instance if initialized 1046 if (moduleAction != null) { 1047 moduleAction.moduleUpdate(module); 1048 // set the old action instance 1049 // the new action instance will be used after a system restart 1050 module.setActionInstance(moduleAction); 1051 } 1052 } catch (Throwable t) { 1053 LOG.error(Messages.get().getBundle().key(Messages.LOG_INSTANCE_UPDATE_ERR_1, module.getName()), t); 1054 } 1055 1056 // initialize the export points 1057 initModuleExportPoints(); 1058 1059 // update the configuration 1060 updateModuleConfiguration(); 1061 1062 // reinit the workplace CSS URIs 1063 if (!module.getParameters().isEmpty()) { 1064 OpenCms.getWorkplaceAppManager().initWorkplaceCssUris(this); 1065 } 1066 } 1067 1068 /** 1069 * Updates the module configuration.<p> 1070 */ 1071 public void updateModuleConfiguration() { 1072 1073 OpenCms.writeConfiguration(CmsModuleConfiguration.class); 1074 } 1075 1076 /** 1077 * Initializes the list of export points from all configured modules.<p> 1078 */ 1079 private synchronized void initModuleExportPoints() { 1080 1081 Set<CmsExportPoint> exportPoints = new HashSet<CmsExportPoint>(); 1082 Iterator<CmsModule> i = m_modules.values().iterator(); 1083 while (i.hasNext()) { 1084 CmsModule module = i.next(); 1085 List<CmsExportPoint> moduleExportPoints = module.getExportPoints(); 1086 for (int j = 0; j < moduleExportPoints.size(); j++) { 1087 CmsExportPoint point = moduleExportPoints.get(j); 1088 if (exportPoints.contains(point)) { 1089 if (LOG.isWarnEnabled()) { 1090 LOG.warn( 1091 Messages.get().getBundle().key( 1092 Messages.LOG_DUPLICATE_EXPORT_POINT_2, 1093 point, 1094 module.getName())); 1095 } 1096 } else { 1097 exportPoints.add(point); 1098 if (LOG.isDebugEnabled()) { 1099 LOG.debug( 1100 Messages.get().getBundle().key(Messages.LOG_ADD_EXPORT_POINT_2, point, module.getName())); 1101 } 1102 } 1103 } 1104 } 1105 m_moduleExportPoints = Collections.unmodifiableSet(exportPoints); 1106 } 1107}