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