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, 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.workplace.tools.modules; 029 030import org.opencms.ade.configuration.CmsADEManager; 031import org.opencms.ade.configuration.formatters.CmsFormatterConfigurationCache; 032import org.opencms.configuration.CmsConfigurationCopyResource; 033import org.opencms.db.CmsExportPoint; 034import org.opencms.file.CmsFile; 035import org.opencms.file.CmsObject; 036import org.opencms.file.CmsProject; 037import org.opencms.file.CmsProperty; 038import org.opencms.file.CmsResource; 039import org.opencms.file.CmsResourceFilter; 040import org.opencms.file.CmsVfsResourceNotFoundException; 041import org.opencms.file.types.A_CmsResourceType; 042import org.opencms.file.types.CmsResourceTypeFolder; 043import org.opencms.file.types.CmsResourceTypeUnknown; 044import org.opencms.file.types.CmsResourceTypeXmlContainerPage; 045import org.opencms.file.types.CmsResourceTypeXmlContent; 046import org.opencms.file.types.I_CmsResourceType; 047import org.opencms.i18n.CmsLocaleManager; 048import org.opencms.i18n.CmsVfsBundleManager; 049import org.opencms.loader.CmsLoaderException; 050import org.opencms.lock.CmsLock; 051import org.opencms.main.CmsException; 052import org.opencms.main.CmsLog; 053import org.opencms.main.I_CmsEventListener; 054import org.opencms.main.OpenCms; 055import org.opencms.module.CmsModule; 056import org.opencms.module.Messages; 057import org.opencms.report.A_CmsReportThread; 058import org.opencms.report.I_CmsReport; 059import org.opencms.util.CmsStringUtil; 060import org.opencms.util.CmsUUID; 061import org.opencms.workplace.CmsWorkplace; 062import org.opencms.workplace.explorer.CmsExplorerTypeSettings; 063import org.opencms.xml.CmsXmlException; 064import org.opencms.xml.content.CmsXmlContent; 065import org.opencms.xml.content.CmsXmlContentFactory; 066 067import java.io.UnsupportedEncodingException; 068import java.util.ArrayList; 069import java.util.Collections; 070import java.util.HashMap; 071import java.util.List; 072import java.util.Map; 073import java.util.Random; 074import java.util.StringTokenizer; 075import java.util.regex.Matcher; 076import java.util.regex.Pattern; 077 078import org.apache.commons.logging.Log; 079 080import com.google.common.base.Function; 081import com.google.common.base.Functions; 082 083/** 084 * The report thread to clone a module.<p> 085 */ 086public class CmsCloneModuleThread extends A_CmsReportThread { 087 088 /** 089 * String replacement function.<p> 090 */ 091 class ReplaceAll implements Function<String, String> { 092 093 /** Regex to match. */ 094 private String m_from; 095 096 /** Replacement for the regex. */ 097 private String m_to; 098 099 /** 100 * Creates a new instance.<p> 101 * 102 * @param from the regex to match 103 * @param to the replacement 104 */ 105 public ReplaceAll(String from, String to) { 106 107 m_from = from; 108 m_to = to; 109 } 110 111 /** 112 * @see com.google.common.base.Function#apply(java.lang.Object) 113 */ 114 public String apply(String input) { 115 116 return input.replaceAll(m_from, m_to); 117 } 118 119 } 120 121 /** The icon path. */ 122 public static final String ICON_PATH = CmsWorkplace.VFS_PATH_RESOURCES + CmsWorkplace.RES_PATH_FILETYPES; 123 124 /** Classes folder within the module. */ 125 public static final String PATH_CLASSES = "classes/"; 126 127 /** The log object for this class. */ 128 private static final Log LOG = CmsLog.getLog(CmsCloneModuleThread.class); 129 130 /** The clone module information. */ 131 private CmsCloneModuleInfo m_cloneInfo; 132 133 /** 134 * Constructor.<p> 135 * 136 * @param cms the cms context 137 * @param cloneInfo the clone module information 138 */ 139 protected CmsCloneModuleThread(CmsObject cms, CmsCloneModuleInfo cloneInfo) { 140 141 super(cms, cloneInfo.getName()); 142 m_cloneInfo = cloneInfo; 143 initHtmlReport(cms.getRequestContext().getLocale()); 144 } 145 146 /** 147 * Returns a list of all module names.<p> 148 * 149 * @return a list of all module names 150 */ 151 public List<String> getAllModuleNames() { 152 153 List<String> sortedModuleNames = new ArrayList<String>(OpenCms.getModuleManager().getModuleNames()); 154 java.util.Collections.sort(sortedModuleNames); 155 return sortedModuleNames; 156 } 157 158 /** 159 * @see org.opencms.report.A_CmsReportThread#getReportUpdate() 160 */ 161 @Override 162 public String getReportUpdate() { 163 164 return getReport().getReportUpdate(); 165 } 166 167 /** 168 * @see java.lang.Thread#run() 169 */ 170 @Override 171 public void run() { 172 173 CmsModule sourceModule = OpenCms.getModuleManager().getModule(m_cloneInfo.getSourceModuleName()); 174 175 // clone the module object 176 CmsModule targetModule = sourceModule.clone(); 177 targetModule.setName(m_cloneInfo.getName()); 178 targetModule.setNiceName(m_cloneInfo.getNiceName()); 179 targetModule.setDescription(m_cloneInfo.getDescription()); 180 targetModule.setAuthorEmail(m_cloneInfo.getAuthorEmail()); 181 targetModule.setAuthorName(m_cloneInfo.getAuthorName()); 182 targetModule.setGroup(m_cloneInfo.getGroup()); 183 targetModule.setActionClass(m_cloneInfo.getActionClass()); 184 185 CmsObject cms = getCms(); 186 CmsProject currentProject = cms.getRequestContext().getCurrentProject(); 187 try { 188 CmsProject workProject = cms.createProject( 189 "Clone_module_work_project", 190 "Clone modulee work project", 191 OpenCms.getDefaultUsers().getGroupAdministrators(), 192 OpenCms.getDefaultUsers().getGroupAdministrators(), 193 CmsProject.PROJECT_TYPE_TEMPORARY); 194 cms.getRequestContext().setCurrentProject(workProject); 195 196 // store the module paths 197 String sourceModulePath = CmsWorkplace.VFS_PATH_MODULES + sourceModule.getName() + "/"; 198 String targetModulePath = CmsWorkplace.VFS_PATH_MODULES + targetModule.getName() + "/"; 199 200 // store the package name as path part 201 String sourcePathPart = sourceModule.getName().replaceAll("\\.", "/"); 202 String targetPathPart = targetModule.getName().replaceAll("\\.", "/"); 203 204 // store the classes folder paths 205 String sourceClassesPath = targetModulePath + PATH_CLASSES + sourcePathPart + "/"; 206 String targetClassesPath = targetModulePath + PATH_CLASSES + targetPathPart + "/"; 207 208 // copy the resources 209 cms.copyResource(sourceModulePath, targetModulePath); 210 211 // check if we have to create the classes folder 212 if (cms.existsResource(sourceClassesPath)) { 213 // in the source module a classes folder was defined, 214 // now create all sub-folders for the package structure in the new module folder 215 createTargetClassesFolder(targetModule, sourceClassesPath, targetModulePath + PATH_CLASSES); 216 // delete the origin classes folder 217 deleteSourceClassesFolder(targetModulePath, sourcePathPart, targetPathPart); 218 } 219 220 // TODO: clone module dependencies 221 222 // adjust the export points 223 cloneExportPoints(sourceModule, targetModule, sourcePathPart, targetPathPart); 224 225 // adjust the resource type names and IDs 226 Map<String, String> descKeys = new HashMap<String, String>(); 227 Map<I_CmsResourceType, I_CmsResourceType> resTypeMap = cloneResourceTypes( 228 sourceModule, 229 targetModule, 230 sourcePathPart, 231 targetPathPart, 232 descKeys); 233 234 // adjust the explorer type names and store referred icons and message keys 235 Map<String, String> iconPaths = new HashMap<String, String>(); 236 cloneExplorerTypes(targetModule, iconPaths, descKeys); 237 238 // rename the icon file names 239 cloneExplorerTypeIcons(iconPaths); 240 241 // adjust the module resources 242 adjustModuleResources(sourceModule, targetModule, sourcePathPart, targetPathPart, iconPaths); 243 244 // search and replace the localization keys 245 if (getCms().existsResource(targetClassesPath)) { 246 List<CmsResource> props = cms.readResources(targetClassesPath, CmsResourceFilter.DEFAULT_FILES); 247 replacesMessages(descKeys, props); 248 } 249 250 int type = OpenCms.getResourceManager().getResourceType(CmsVfsBundleManager.TYPE_XML_BUNDLE).getTypeId(); 251 CmsResourceFilter filter = CmsResourceFilter.requireType(type); 252 List<CmsResource> resources = cms.readResources(targetModulePath, filter); 253 replacesMessages(descKeys, resources); 254 renameXmlVfsBundles(resources, targetModule, sourceModule.getName()); 255 256 List<CmsResource> allModuleResources = cms.readResources(targetModulePath, CmsResourceFilter.ALL); 257 replacePath(sourceModulePath, targetModulePath, allModuleResources); 258 259 // search and replace paths 260 replaceModuleName(); 261 262 // replace formatter paths 263 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(m_cloneInfo.getFormatterTargetModule()) 264 && !targetModule.getResourceTypes().isEmpty()) { 265 replaceFormatterPaths(targetModule); 266 } 267 268 adjustConfigs(targetModule, resTypeMap); 269 270 // now unlock and publish the project 271 getReport().println( 272 Messages.get().container(Messages.RPT_PUBLISH_PROJECT_BEGIN_0), 273 I_CmsReport.FORMAT_HEADLINE); 274 cms.unlockProject(workProject.getUuid()); 275 OpenCms.getPublishManager().publishProject(cms, getReport()); 276 OpenCms.getPublishManager().waitWhileRunning(); 277 278 getReport().println( 279 Messages.get().container(Messages.RPT_PUBLISH_PROJECT_END_0), 280 I_CmsReport.FORMAT_HEADLINE); 281 282 // add the imported module to the module manager 283 OpenCms.getModuleManager().addModule(cms, targetModule); 284 285 // reinitialize the resource manager with additional module resource types if necessary 286 if (targetModule.getResourceTypes() != Collections.EMPTY_LIST) { 287 OpenCms.getResourceManager().initialize(cms); 288 } 289 // reinitialize the workplace manager with additional module explorer types if necessary 290 if (targetModule.getExplorerTypes() != Collections.EMPTY_LIST) { 291 OpenCms.getWorkplaceManager().addExplorerTypeSettings(targetModule); 292 } 293 294 // re-initialize the workplace 295 OpenCms.getWorkplaceManager().initialize(cms); 296 // fire "clear caches" event to reload all cached resource bundles 297 OpenCms.fireCmsEvent(I_CmsEventListener.EVENT_CLEAR_CACHES, new HashMap<String, Object>()); 298 299 // following changes will not be published right now, switch back to previous project 300 cms.getRequestContext().setCurrentProject(currentProject); 301 302 // change resource types and schema locations 303 if (isTrue(m_cloneInfo.getChangeResourceTypes())) { 304 changeResourceTypes(resTypeMap); 305 } 306 // adjust container pages 307 CmsObject cloneCms = OpenCms.initCmsObject(cms); 308 if (isTrue(m_cloneInfo.getApplyChangesEverywhere())) { 309 cloneCms.getRequestContext().setSiteRoot("/"); 310 } 311 312 if (m_cloneInfo.isRewriteContainerPages()) { 313 CmsResourceFilter f = CmsResourceFilter.requireType( 314 CmsResourceTypeXmlContainerPage.getContainerPageTypeId()); 315 List<CmsResource> allContainerPages = cloneCms.readResources("/", f); 316 replacePath(sourceModulePath, targetModulePath, allContainerPages); 317 } 318 } catch (Throwable e) { 319 LOG.error(e.getLocalizedMessage(), e); 320 getReport().addError(e); 321 } finally { 322 cms.getRequestContext().setCurrentProject(currentProject); 323 } 324 } 325 326 /** 327 * Returns <code>true</code> if the module has been created successful.<p> 328 * 329 * @return <code>true</code> if the module has been created successful 330 */ 331 public boolean success() { 332 333 return OpenCms.getModuleManager().getModule(m_cloneInfo.getName()) != null; 334 } 335 336 /** 337 * Adjusts the module configuration file and the formatter configurations.<p> 338 * 339 * @param targetModule the target module 340 * @param resTypeMap the resource type mapping 341 * 342 * @throws CmsException if something goes wrong 343 * @throws UnsupportedEncodingException if the file content could not be read with the determined encoding 344 */ 345 private void adjustConfigs(CmsModule targetModule, Map<I_CmsResourceType, I_CmsResourceType> resTypeMap) 346 throws CmsException, UnsupportedEncodingException { 347 348 String modPath = CmsWorkplace.VFS_PATH_MODULES + targetModule.getName() + "/"; 349 CmsObject cms = getCms(); 350 if (((m_cloneInfo.getSourceNamePrefix() != null) && (m_cloneInfo.getTargetNamePrefix() != null)) 351 || !m_cloneInfo.getSourceNamePrefix().equals(m_cloneInfo.getTargetNamePrefix())) { 352 // replace resource type names in formatter configurations 353 List<CmsResource> resources = cms.readResources( 354 modPath, 355 CmsResourceFilter.requireType( 356 OpenCms.getResourceManager().getResourceType( 357 CmsFormatterConfigurationCache.TYPE_FORMATTER_CONFIG))); 358 String source = "<Type><!\\[CDATA\\[" + m_cloneInfo.getSourceNamePrefix(); 359 String target = "<Type><!\\[CDATA\\[" + m_cloneInfo.getTargetNamePrefix(); 360 Function<String, String> replaceType = new ReplaceAll(source, target); 361 362 for (CmsResource resource : resources) { 363 transformResource(resource, replaceType); 364 } 365 resources.clear(); 366 } 367 368 // replace resource type names in module configuration 369 try { 370 CmsResource config = cms.readResource( 371 modPath + CmsADEManager.CONFIG_FILE_NAME, 372 CmsResourceFilter.requireType( 373 OpenCms.getResourceManager().getResourceType(CmsADEManager.MODULE_CONFIG_TYPE))); 374 Function<String, String> substitution = Functions.identity(); 375 // compose the substitution functions from simple substitution functions for each type 376 377 for (Map.Entry<I_CmsResourceType, I_CmsResourceType> mapping : resTypeMap.entrySet()) { 378 substitution = Functions.compose( 379 new ReplaceAll(mapping.getKey().getTypeName(), mapping.getValue().getTypeName()), 380 substitution); 381 } 382 383 // Either replace prefix in or prepend it to the folder name value 384 385 Function<String, String> replaceFolderName = new ReplaceAll( 386 "(<Folder>[ \n]*<Name><!\\[CDATA\\[)(" + m_cloneInfo.getSourceNamePrefix() + ")?", 387 "$1" + m_cloneInfo.getTargetNamePrefix()); 388 substitution = Functions.compose(replaceFolderName, substitution); 389 transformResource(config, substitution); 390 } catch (CmsVfsResourceNotFoundException e) { 391 LOG.info(e.getLocalizedMessage(), e); 392 } 393 } 394 395 /** 396 * Adjusts the paths of the module resources from the source path to the target path.<p> 397 * 398 * @param targetResources the paths to adjust 399 * @param sourceModuleName the source module name 400 * @param targetModuleName the target module name 401 * @param sourcePathPart the path part of the source module 402 * @param targetPathPart the path part of the target module 403 * @param iconPaths the path where resource type icons are located 404 * @return the adjusted paths 405 */ 406 private List<String> adjustModuleResourcePaths( 407 List<String> targetResources, 408 String sourceModuleName, 409 String targetModuleName, 410 String sourcePathPart, 411 String targetPathPart, 412 Map<String, String> iconPaths) { 413 414 List<String> newTargetResources = new ArrayList<String>(); 415 for (String modRes : targetResources) { 416 String nIcon = iconPaths.get(modRes.substring(modRes.lastIndexOf('/') + 1)); 417 if (nIcon != null) { 418 // the referenced resource is an resource type icon, add the new icon path 419 newTargetResources.add(ICON_PATH + nIcon); 420 } else if (modRes.contains(sourceModuleName)) { 421 // there is the name in it 422 newTargetResources.add(modRes.replaceAll(sourceModuleName, targetModuleName)); 423 } else if (modRes.contains(sourcePathPart)) { 424 // there is a path in it 425 newTargetResources.add(modRes.replaceAll(sourcePathPart, targetPathPart)); 426 } else { 427 // there is whether the path nor the name in it 428 newTargetResources.add(modRes); 429 } 430 } 431 return newTargetResources; 432 } 433 434 /** 435 * Adjusts the paths of the module resources from the source path to the target path.<p> 436 * 437 * @param sourceModule the source module 438 * @param targetModule the target module 439 * @param sourcePathPart the path part of the source module 440 * @param targetPathPart the path part of the target module 441 * @param iconPaths the path where resource type icons are located 442 */ 443 private void adjustModuleResources( 444 CmsModule sourceModule, 445 CmsModule targetModule, 446 String sourcePathPart, 447 String targetPathPart, 448 Map<String, String> iconPaths) { 449 450 List<String> newTargetResources = adjustModuleResourcePaths( 451 targetModule.getResources(), 452 sourceModule.getName(), 453 targetModule.getName(), 454 sourcePathPart, 455 targetPathPart, 456 iconPaths); 457 targetModule.setResources(newTargetResources); 458 459 List<String> newTargetExcludeResources = adjustModuleResourcePaths( 460 targetModule.getExcludeResources(), 461 sourceModule.getName(), 462 targetModule.getName(), 463 sourcePathPart, 464 targetPathPart, 465 iconPaths); 466 targetModule.setExcludeResources(newTargetExcludeResources); 467 } 468 469 /** 470 * Manipulates a string by cutting of a prefix, if present, and adding a new prefix. 471 * 472 * @param word the string to be manipulated 473 * @param oldPrefix the old prefix that should be replaced 474 * @param newPrefix the new prefix that is added 475 * @return the manipulated string 476 */ 477 private String alterPrefix(String word, String oldPrefix, String newPrefix) { 478 479 if (word.startsWith(oldPrefix)) { 480 return word.replaceFirst(oldPrefix, newPrefix); 481 } 482 return (newPrefix + word); 483 } 484 485 /** 486 * Changes the resource types and the schema locations of existing content.<p> 487 * 488 * @param resTypeMap a map containing the source types as keys and the target types as values 489 * 490 * @throws CmsException if something goes wrong 491 * @throws UnsupportedEncodingException if the file content could not be read with the determined encoding 492 */ 493 private void changeResourceTypes(Map<I_CmsResourceType, I_CmsResourceType> resTypeMap) 494 throws CmsException, UnsupportedEncodingException { 495 496 CmsObject cms = getCms(); 497 CmsObject cloneCms = OpenCms.initCmsObject(cms); 498 499 if (isTrue(m_cloneInfo.getApplyChangesEverywhere())) { 500 cloneCms.getRequestContext().setSiteRoot("/"); 501 } 502 503 for (Map.Entry<I_CmsResourceType, I_CmsResourceType> mapping : resTypeMap.entrySet()) { 504 CmsResourceFilter filter = CmsResourceFilter.requireType(mapping.getKey()); 505 List<CmsResource> resources = cloneCms.readResources("/", filter); 506 String sourceSchemaPath = mapping.getKey().getConfiguration().get("schema"); 507 String targetSchemaPath = mapping.getValue().getConfiguration().get("schema"); 508 for (CmsResource res : resources) { 509 if (lockResource(cms, res)) { 510 CmsFile file = cms.readFile(res); 511 if (CmsResourceTypeXmlContent.isXmlContent(file)) { 512 CmsXmlContent xmlContent = CmsXmlContentFactory.unmarshal(getCms(), file); 513 xmlContent.setAutoCorrectionEnabled(true); 514 file = xmlContent.correctXmlStructure(getCms()); 515 } 516 String encoding = CmsLocaleManager.getResourceEncoding(cms, file); 517 String content = new String(file.getContents(), encoding); 518 content = content.replaceAll(sourceSchemaPath, targetSchemaPath); 519 file.setContents(content.getBytes(encoding)); 520 try { 521 cms.writeFile(file); 522 } catch (CmsXmlException e) { 523 LOG.error(e.getMessage(), e); 524 } 525 res.setType(mapping.getValue().getTypeId()); 526 cms.writeResource(res); 527 } 528 } 529 } 530 } 531 532 /** 533 * Copies the explorer type icons.<p> 534 * 535 * @param iconPaths the path to the location where the icons are located 536 * 537 * @throws CmsException if something goes wrong 538 */ 539 private void cloneExplorerTypeIcons(Map<String, String> iconPaths) throws CmsException { 540 541 for (Map.Entry<String, String> entry : iconPaths.entrySet()) { 542 String source = ICON_PATH + entry.getKey(); 543 String target = ICON_PATH + entry.getValue(); 544 if (getCms().existsResource(source) && !getCms().existsResource(target)) { 545 getCms().copyResource(source, target); 546 } 547 } 548 } 549 550 /** 551 * Copies the explorer type definitions.<p> 552 * 553 * @param targetModule the target module 554 * @param iconPaths the path to the location where the icons are located 555 * @param descKeys a map that contains a mapping of the explorer type definitions messages 556 */ 557 private void cloneExplorerTypes( 558 CmsModule targetModule, 559 Map<String, String> iconPaths, 560 Map<String, String> descKeys) { 561 562 List<CmsExplorerTypeSettings> targetExplorerTypes = targetModule.getExplorerTypes(); 563 for (CmsExplorerTypeSettings expSetting : targetExplorerTypes) { 564 descKeys.put( 565 expSetting.getKey(), 566 alterPrefix(expSetting.getKey(), m_cloneInfo.getSourceNamePrefix(), m_cloneInfo.getTargetNamePrefix())); 567 String newIcon = alterPrefix( 568 expSetting.getIcon(), 569 m_cloneInfo.getSourceNamePrefix(), 570 m_cloneInfo.getTargetNamePrefix()); 571 String newBigIcon = alterPrefix( 572 expSetting.getBigIcon(), 573 m_cloneInfo.getSourceNamePrefix(), 574 m_cloneInfo.getTargetNamePrefix()); 575 iconPaths.put(expSetting.getIcon(), newIcon); 576 iconPaths.put(expSetting.getBigIcon(), newBigIcon); 577 String oldExpTypeName = expSetting.getName(); 578 String newExpTypeName = alterPrefix( 579 oldExpTypeName, 580 m_cloneInfo.getSourceNamePrefix(), 581 m_cloneInfo.getTargetNamePrefix()); 582 expSetting.setName(newExpTypeName); 583 584 expSetting.setKey(expSetting.getKey().replaceFirst(oldExpTypeName, newExpTypeName)); 585 expSetting.setIcon( 586 alterPrefix( 587 expSetting.getIcon(), 588 m_cloneInfo.getSourceNamePrefix(), 589 m_cloneInfo.getTargetNamePrefix())); 590 expSetting.setBigIcon( 591 alterPrefix( 592 expSetting.getBigIcon(), 593 m_cloneInfo.getSourceNamePrefix(), 594 m_cloneInfo.getTargetNamePrefix())); 595 expSetting.setInfo(expSetting.getInfo().replaceFirst(oldExpTypeName, newExpTypeName)); 596 } 597 } 598 599 /** 600 * Clones the export points of the module and adjusts its paths.<p> 601 * 602 * @param sourceModule the source module 603 * @param targetModule the target module 604 * @param sourcePathPart the source path part 605 * @param targetPathPart the target path part 606 */ 607 private void cloneExportPoints( 608 CmsModule sourceModule, 609 CmsModule targetModule, 610 String sourcePathPart, 611 String targetPathPart) { 612 613 for (CmsExportPoint exp : targetModule.getExportPoints()) { 614 if (exp.getUri().contains(sourceModule.getName())) { 615 exp.setUri(exp.getUri().replaceAll(sourceModule.getName(), targetModule.getName())); 616 } 617 if (exp.getUri().contains(sourcePathPart)) { 618 exp.setUri(exp.getUri().replaceAll(sourcePathPart, targetPathPart)); 619 } 620 } 621 } 622 623 /** 624 * Clones/copies the resource types.<p> 625 626 * @param sourceModule the source module 627 * @param targetModule the target module 628 * @param sourcePathPart the source path part 629 * @param targetPathPart the target path part 630 * @param keys the map where to put in the messages of the resource type 631 * 632 * @return a map with source resource types as key and the taregt resource types as value 633 */ 634 private Map<I_CmsResourceType, I_CmsResourceType> cloneResourceTypes( 635 CmsModule sourceModule, 636 CmsModule targetModule, 637 String sourcePathPart, 638 String targetPathPart, 639 Map<String, String> keys) { 640 641 Map<I_CmsResourceType, I_CmsResourceType> resourceTypeMapping = new HashMap<I_CmsResourceType, I_CmsResourceType>(); 642 643 List<I_CmsResourceType> targetResourceTypes = new ArrayList<I_CmsResourceType>(); 644 for (I_CmsResourceType sourceResType : targetModule.getResourceTypes()) { 645 646 // get the class name attribute 647 String className = sourceResType.getClassName(); 648 // create the class instance 649 I_CmsResourceType targetResType; 650 try { 651 if (className != null) { 652 className = className.trim(); 653 } 654 655 int newId = -1; 656 boolean exists = true; 657 do { 658 newId = new Random().nextInt((99999)) + 10000; 659 try { 660 OpenCms.getResourceManager().getResourceType(newId); 661 } catch (CmsLoaderException e) { 662 exists = false; 663 } 664 } while (exists); 665 666 targetResType = (I_CmsResourceType)Class.forName(className).newInstance(); 667 668 for (String mapping : sourceResType.getConfiguredMappings()) { 669 targetResType.addMappingType(mapping); 670 } 671 672 targetResType.setAdjustLinksFolder(sourceResType.getAdjustLinksFolder()); 673 674 if (targetResType instanceof A_CmsResourceType) { 675 A_CmsResourceType concreteTargetResType = (A_CmsResourceType)targetResType; 676 for (CmsProperty prop : sourceResType.getConfiguredDefaultProperties()) { 677 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(prop.getValue())) { 678 prop.setStructureValue( 679 prop.getStructureValue().replaceAll( 680 sourceModule.getName(), 681 targetModule.getName()).replaceAll(sourcePathPart, targetPathPart)); 682 prop.setResourceValue( 683 prop.getResourceValue().replaceAll( 684 sourceModule.getName(), 685 targetModule.getName()).replaceAll(sourcePathPart, targetPathPart)); 686 } 687 concreteTargetResType.addDefaultProperty(prop); 688 } 689 for (CmsConfigurationCopyResource conres : sourceResType.getConfiguredCopyResources()) { 690 concreteTargetResType.addCopyResource( 691 conres.getSource(), 692 conres.getTarget(), 693 conres.getTypeString()); 694 } 695 } 696 697 for (Map.Entry<String, String> entry : sourceResType.getConfiguration().entrySet()) { 698 targetResType.addConfigurationParameter( 699 entry.getKey(), 700 entry.getValue().replaceAll(sourceModule.getName(), targetModule.getName())); 701 } 702 703 targetResType.setAdditionalModuleResourceType(true); 704 targetResType.initConfiguration( 705 alterPrefix( 706 sourceResType.getTypeName(), 707 m_cloneInfo.getSourceNamePrefix(), 708 m_cloneInfo.getTargetNamePrefix()), 709 newId + "", 710 sourceResType.getClassName()); 711 712 keys.put(sourceResType.getTypeName(), targetResType.getTypeName()); 713 targetResourceTypes.add(targetResType); 714 715 resourceTypeMapping.put(sourceResType, targetResType); 716 717 } catch (Exception e) { 718 // resource type is unknown, use dummy class to import the module resources 719 targetResType = new CmsResourceTypeUnknown(); 720 // write an error to the log 721 LOG.error( 722 org.opencms.configuration.Messages.get().getBundle().key( 723 org.opencms.configuration.Messages.ERR_UNKNOWN_RESTYPE_CLASS_2, 724 className, 725 targetResType.getClass().getName()), 726 e); 727 } 728 } 729 targetModule.setResourceTypes(targetResourceTypes); 730 return resourceTypeMapping; 731 } 732 733 /** 734 * Creates the target folder for the module clone.<p> 735 * 736 * @param targetModule the target module 737 * @param sourceClassesPath the source module class path 738 * @param targetBaseClassesPath the 'classes' folder of the target module 739 * 740 * @throws CmsException if something goes wrong 741 */ 742 private void createTargetClassesFolder( 743 CmsModule targetModule, 744 String sourceClassesPath, 745 String targetBaseClassesPath) 746 throws CmsException { 747 748 StringTokenizer tok = new StringTokenizer(targetModule.getName(), "."); 749 int folderId = CmsResourceTypeFolder.getStaticTypeId(); 750 String targetClassesPath = targetBaseClassesPath; 751 752 while (tok.hasMoreTokens()) { 753 String folder = tok.nextToken(); 754 targetClassesPath += folder + "/"; 755 if (!getCms().existsResource(targetClassesPath)) { 756 getCms().createResource(targetClassesPath, folderId); 757 } 758 } 759 // move exiting content into new classes sub-folder 760 List<CmsResource> propertyFiles = getCms().readResources(sourceClassesPath, CmsResourceFilter.ALL); 761 for (CmsResource res : propertyFiles) { 762 if (!getCms().existsResource(targetClassesPath + res.getName())) { 763 getCms().copyResource(res.getRootPath(), targetClassesPath + res.getName()); 764 } 765 } 766 } 767 768 /** 769 * Deletes the temporarily copied classes files.<p> 770 * 771 * @param targetModulePath the target module path 772 * @param sourcePathPart the path part of the source module 773 * @param targetPathPart the target path part 774 * 775 * @throws CmsException if something goes wrong 776 */ 777 private void deleteSourceClassesFolder(String targetModulePath, String sourcePathPart, String targetPathPart) 778 throws CmsException { 779 780 String sourceFirstFolder = sourcePathPart.substring(0, sourcePathPart.indexOf('/')); 781 String targetFirstFolder = sourcePathPart.substring(0, sourcePathPart.indexOf('/')); 782 if (!sourceFirstFolder.equals(targetFirstFolder)) { 783 getCms().deleteResource( 784 targetModulePath + PATH_CLASSES + sourceFirstFolder, 785 CmsResource.DELETE_PRESERVE_SIBLINGS); 786 return; 787 } 788 String[] targetPathParts = CmsStringUtil.splitAsArray(targetPathPart, '/'); 789 String[] sourcePathParts = CmsStringUtil.splitAsArray(sourcePathPart, '/'); 790 int sourceLength = sourcePathParts.length; 791 int diff = 0; 792 for (int i = 0; i < targetPathParts.length; i++) { 793 if (sourceLength >= i) { 794 if (!targetPathParts[i].equals(sourcePathParts[i])) { 795 diff = i + 1; 796 } 797 } 798 } 799 String topSourceClassesPath = targetModulePath 800 + PATH_CLASSES 801 + sourcePathPart.substring(0, sourcePathPart.indexOf('/')) 802 + "/"; 803 804 if (diff != 0) { 805 topSourceClassesPath = targetModulePath + PATH_CLASSES; 806 for (int i = 0; i < diff; i++) { 807 topSourceClassesPath += sourcePathParts[i] + "/"; 808 } 809 } 810 getCms().deleteResource(topSourceClassesPath, CmsResource.DELETE_PRESERVE_SIBLINGS); 811 } 812 813 /** 814 * Returns <code>true</code> if form input is selected, checked, on or yes.<p> 815 * 816 * @param value the value to check 817 * 818 * @return <code>true</code> if form input is selected, checked, on or yes 819 */ 820 private boolean isTrue(String value) { 821 822 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(value)) { 823 if (Boolean.valueOf(value.toLowerCase()).booleanValue() 824 || value.toLowerCase().equals("on") 825 || value.toLowerCase().equals("yes") 826 || value.toLowerCase().equals("checked") 827 || value.toLowerCase().equals("selected")) { 828 return true; 829 } 830 } 831 return false; 832 } 833 834 /** 835 * Locks the current resource.<p> 836 * 837 * @param cms the current CmsObject 838 * @param cmsResource the resource to lock 839 * 840 * @return <code>true</code> if the given resource was locked was successfully 841 * 842 * @throws CmsException if some goes wrong 843 */ 844 private boolean lockResource(CmsObject cms, CmsResource cmsResource) throws CmsException { 845 846 CmsLock lock = cms.getLock(cms.getSitePath(cmsResource)); 847 // check the lock 848 if ((lock != null) 849 && lock.isOwnedBy(cms.getRequestContext().getCurrentUser()) 850 && lock.isOwnedInProjectBy( 851 cms.getRequestContext().getCurrentUser(), 852 cms.getRequestContext().getCurrentProject())) { 853 // prove is current lock from current user in current project 854 return true; 855 } else if ((lock != null) && !lock.isUnlocked() && !lock.isOwnedBy(cms.getRequestContext().getCurrentUser())) { 856 // the resource is not locked by the current user, so can not lock it 857 return false; 858 } else if ((lock != null) 859 && !lock.isUnlocked() 860 && lock.isOwnedBy(cms.getRequestContext().getCurrentUser()) 861 && !lock.isOwnedInProjectBy( 862 cms.getRequestContext().getCurrentUser(), 863 cms.getRequestContext().getCurrentProject())) { 864 // prove is current lock from current user but not in current project 865 // file is locked by current user but not in current project 866 867 cms.changeLock(cms.getSitePath(cmsResource)); 868 } else if ((lock != null) && lock.isUnlocked()) { 869 // lock resource from current user in current project 870 cms.lockResource(cms.getSitePath(cmsResource)); 871 } 872 lock = cms.getLock(cms.getSitePath(cmsResource)); 873 if ((lock != null) 874 && lock.isOwnedBy(cms.getRequestContext().getCurrentUser()) 875 && !lock.isOwnedInProjectBy( 876 cms.getRequestContext().getCurrentUser(), 877 cms.getRequestContext().getCurrentProject())) { 878 // resource could not be locked 879 return false; 880 } 881 // resource is locked successfully 882 return true; 883 } 884 885 /** 886 * Renames the vfs resource bundle files within the target module according to the new module's name.<p> 887 * 888 * @param resources the vfs resource bundle files 889 * @param targetModule the target module 890 * @param name the package name of the source module 891 * 892 * @return a list of all xml vfs bundles within the given module 893 * @throws CmsException if something gows wrong 894 */ 895 private List<CmsResource> renameXmlVfsBundles(List<CmsResource> resources, CmsModule targetModule, String name) 896 throws CmsException { 897 898 for (CmsResource res : resources) { 899 String newName = res.getName().replaceAll(name, targetModule.getName()); 900 String targetRootPath = CmsResource.getFolderPath(res.getRootPath()) + newName; 901 if (!getCms().existsResource(targetRootPath)) { 902 getCms().moveResource(res.getRootPath(), targetRootPath); 903 } 904 } 905 return resources; 906 907 } 908 909 /** 910 * Replaces the referenced formatters within the new XSD files with the new formatter paths.<p> 911 * 912 * @param targetModule the target module 913 * 914 * @throws CmsException if something goes wrong 915 * @throws UnsupportedEncodingException if the file content could not be read with the determined encoding 916 */ 917 private void replaceFormatterPaths(CmsModule targetModule) throws CmsException, UnsupportedEncodingException { 918 919 CmsResource formatterSourceFolder = getCms().readResource( 920 "/system/modules/" + m_cloneInfo.getFormatterSourceModule() + "/"); 921 CmsResource formatterTargetFolder = getCms().readResource( 922 "/system/modules/" + m_cloneInfo.getFormatterTargetModule() + "/"); 923 for (I_CmsResourceType type : targetModule.getResourceTypes()) { 924 String schemaPath = type.getConfiguration().get("schema"); 925 CmsResource res = getCms().readResource(schemaPath); 926 CmsFile file = getCms().readFile(res); 927 if (CmsResourceTypeXmlContent.isXmlContent(file)) { 928 CmsXmlContent xmlContent = CmsXmlContentFactory.unmarshal(getCms(), file); 929 xmlContent.setAutoCorrectionEnabled(true); 930 file = xmlContent.correctXmlStructure(getCms()); 931 } 932 String encoding = CmsLocaleManager.getResourceEncoding(getCms(), file); 933 String content = new String(file.getContents(), encoding); 934 content = content.replaceAll(formatterSourceFolder.getRootPath(), formatterTargetFolder.getRootPath()); 935 file.setContents(content.getBytes(encoding)); 936 getCms().writeFile(file); 937 } 938 } 939 940 /** 941 * Initializes a thread to find and replace all occurrence of the module's path.<p> 942 * 943 * @throws CmsException in case writing the file fails 944 * @throws UnsupportedEncodingException in case of the wrong encoding 945 946 */ 947 private void replaceModuleName() throws CmsException, UnsupportedEncodingException { 948 949 CmsResourceFilter filter = CmsResourceFilter.ALL.addRequireFile().addExcludeState( 950 CmsResource.STATE_DELETED).addRequireTimerange().addRequireVisible(); 951 List<CmsResource> resources = getCms().readResources( 952 CmsWorkplace.VFS_PATH_MODULES + m_cloneInfo.getName() + "/", 953 filter); 954 for (CmsResource resource : resources) { 955 CmsFile file = getCms().readFile(resource); 956 if (CmsResourceTypeXmlContent.isXmlContent(file)) { 957 CmsXmlContent xmlContent = CmsXmlContentFactory.unmarshal(getCms(), file); 958 xmlContent.setAutoCorrectionEnabled(true); 959 file = xmlContent.correctXmlStructure(getCms()); 960 } 961 byte[] contents = file.getContents(); 962 String encoding = CmsLocaleManager.getResourceEncoding(getCms(), file); 963 String content = new String(contents, encoding); 964 Matcher matcher = Pattern.compile(m_cloneInfo.getSourceModuleName()).matcher(content); 965 if (matcher.find()) { 966 contents = matcher.replaceAll(m_cloneInfo.getName()).getBytes(encoding); 967 if (lockResource(getCms(), file)) { 968 file.setContents(contents); 969 getCms().writeFile(file); 970 } 971 } 972 } 973 } 974 975 /** 976 * Replaces the paths within all the given resources and removes all UUIDs by an regex.<p> 977 * 978 * @param sourceModulePath the search path 979 * @param targetModulePath the replace path 980 * @param resources the resources 981 * 982 * @throws CmsException if something goes wrong 983 * @throws UnsupportedEncodingException if the file content could not be read with the determined encoding 984 */ 985 private void replacePath(String sourceModulePath, String targetModulePath, List<CmsResource> resources) 986 throws CmsException, UnsupportedEncodingException { 987 988 for (CmsResource resource : resources) { 989 if (resource.isFile()) { 990 CmsFile file = getCms().readFile(resource); 991 if (CmsResourceTypeXmlContent.isXmlContent(file)) { 992 CmsXmlContent xmlContent = CmsXmlContentFactory.unmarshal(getCms(), file); 993 xmlContent.setAutoCorrectionEnabled(true); 994 file = xmlContent.correctXmlStructure(getCms()); 995 } 996 String encoding = CmsLocaleManager.getResourceEncoding(getCms(), file); 997 String oldContent = new String(file.getContents(), encoding); 998 String newContent = oldContent.replaceAll(sourceModulePath, targetModulePath); 999 Matcher matcher = Pattern.compile(CmsUUID.UUID_REGEX).matcher(newContent); 1000 newContent = matcher.replaceAll(""); 1001 newContent = newContent.replaceAll("<uuid></uuid>", ""); 1002 if (!oldContent.equals(newContent)) { 1003 file.setContents(newContent.getBytes(encoding)); 1004 if (!resource.getRootPath().startsWith(CmsWorkplace.VFS_PATH_SYSTEM)) { 1005 if (lockResource(getCms(), resource)) { 1006 getCms().writeFile(file); 1007 } 1008 } else { 1009 getCms().writeFile(file); 1010 } 1011 } 1012 } 1013 } 1014 } 1015 1016 /** 1017 * Replaces the messages for the given resources.<p> 1018 * 1019 * @param descKeys the replacement mapping 1020 * @param resources the resources to consult 1021 * 1022 * @throws CmsException if something goes wrong 1023 * @throws UnsupportedEncodingException if the file content could not be read with the determined encoding 1024 1025 */ 1026 private void replacesMessages(Map<String, String> descKeys, List<CmsResource> resources) 1027 throws CmsException, UnsupportedEncodingException { 1028 1029 for (CmsResource resource : resources) { 1030 CmsFile file = getCms().readFile(resource); 1031 String encoding = CmsLocaleManager.getResourceEncoding(getCms(), file); 1032 String content = new String(file.getContents(), encoding); 1033 for (Map.Entry<String, String> entry : descKeys.entrySet()) { 1034 content = content.replaceAll(entry.getKey(), entry.getValue()); 1035 } 1036 file.setContents(content.getBytes(encoding)); 1037 getCms().writeFile(file); 1038 } 1039 } 1040 1041 /** 1042 * Reads a file into a string, applies a transformation to the string, and writes the string back to the file.<p> 1043 * 1044 * @param resource the resource to transform 1045 * @param transformation the transformation to apply 1046 * @throws CmsException if something goes wrong 1047 * @throws UnsupportedEncodingException in case the encoding is not supported 1048 */ 1049 private void transformResource(CmsResource resource, Function<String, String> transformation) 1050 throws CmsException, UnsupportedEncodingException { 1051 1052 CmsFile file = getCms().readFile(resource); 1053 if (CmsResourceTypeXmlContent.isXmlContent(file)) { 1054 CmsXmlContent xmlContent = CmsXmlContentFactory.unmarshal(getCms(), file); 1055 xmlContent.setAutoCorrectionEnabled(true); 1056 file = xmlContent.correctXmlStructure(getCms()); 1057 } 1058 String encoding = CmsLocaleManager.getResourceEncoding(getCms(), file); 1059 String content = new String(file.getContents(), encoding); 1060 content = transformation.apply(content); 1061 file.setContents(content.getBytes(encoding)); 1062 lockResource(getCms(), file); 1063 getCms().writeFile(file); 1064 1065 } 1066}