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.i18n.tools; 029 030import org.opencms.ade.configuration.CmsADEConfigData; 031import org.opencms.ade.configuration.CmsResourceTypeConfig; 032import org.opencms.file.CmsFile; 033import org.opencms.file.CmsObject; 034import org.opencms.file.CmsProperty; 035import org.opencms.file.CmsPropertyDefinition; 036import org.opencms.file.CmsResource; 037import org.opencms.file.CmsResourceFilter; 038import org.opencms.file.types.CmsResourceTypeFolder; 039import org.opencms.file.types.CmsResourceTypeXmlContainerPage; 040import org.opencms.file.types.I_CmsResourceType; 041import org.opencms.i18n.CmsLocaleGroupService; 042import org.opencms.i18n.CmsLocaleGroupService.Status; 043import org.opencms.i18n.CmsLocaleManager; 044import org.opencms.i18n.CmsMessageContainer; 045import org.opencms.loader.I_CmsFileNameGenerator; 046import org.opencms.lock.CmsLockActionRecord; 047import org.opencms.lock.CmsLockActionRecord.LockChange; 048import org.opencms.lock.CmsLockUtil; 049import org.opencms.main.CmsException; 050import org.opencms.main.CmsLog; 051import org.opencms.main.OpenCms; 052import org.opencms.site.CmsSite; 053import org.opencms.ui.Messages; 054import org.opencms.util.CmsFileUtil; 055import org.opencms.util.CmsMacroResolver; 056import org.opencms.util.CmsStringUtil; 057import org.opencms.util.CmsUUID; 058import org.opencms.xml.CmsXmlException; 059import org.opencms.xml.containerpage.CmsContainerBean; 060import org.opencms.xml.containerpage.CmsContainerElementBean; 061import org.opencms.xml.containerpage.CmsContainerPageBean; 062import org.opencms.xml.containerpage.CmsXmlContainerPage; 063import org.opencms.xml.containerpage.CmsXmlContainerPageFactory; 064import org.opencms.xml.content.CmsXmlContent; 065import org.opencms.xml.content.CmsXmlContentFactory; 066 067import java.io.ByteArrayInputStream; 068import java.io.IOException; 069import java.io.InputStreamReader; 070import java.util.Iterator; 071import java.util.LinkedHashMap; 072import java.util.List; 073import java.util.Locale; 074import java.util.Map; 075import java.util.Properties; 076import java.util.Set; 077 078import org.apache.commons.logging.Log; 079 080import com.google.common.collect.Lists; 081import com.google.common.collect.Maps; 082import com.google.common.collect.Sets; 083 084/** 085 * Helper class for copying container pages including some of their elements.<p> 086 */ 087public class CmsContainerPageCopier { 088 089 /** 090 * Enum representing the element copy mode.<p> 091 */ 092 public enum CopyMode { 093 /** Choose between reuse / copy automatically depending on source / target locale and the configuration .*/ 094 automatic, 095 096 /** Do not copy elements. */ 097 reuse, 098 099 /** Automatically determine when to copy elements. */ 100 smartCopy, 101 102 /** Like smartCopy, but also converts locales of copied elements. */ 103 smartCopyAndChangeLocale; 104 105 } 106 107 /** 108 * Exception indicating that no custom replacement element was found 109 * for a type which requires replacement.<p> 110 */ 111 public static class NoCustomReplacementException extends Exception { 112 113 /** Serial version id. */ 114 private static final long serialVersionUID = 1L; 115 116 /** The resource for which no exception was found. */ 117 private CmsResource m_resource; 118 119 /** 120 * Creates a new instance.<p> 121 * 122 * @param resource the resource for which no replacement was found 123 */ 124 public NoCustomReplacementException(CmsResource resource) { 125 126 super(); 127 m_resource = resource; 128 } 129 130 /** 131 * Gets the resource for which no replacement was found.<p> 132 * 133 * @return the resource 134 */ 135 public CmsResource getResource() { 136 137 return m_resource; 138 } 139 } 140 141 /** The log instance used for this class. */ 142 private static final Log LOG = CmsLog.getLog(CmsContainerPageCopier.class); 143 144 /** The CMS context used by this object. */ 145 private CmsObject m_cms; 146 147 /** The CMS context used by this object, but with the site root set to "". */ 148 private CmsObject m_rootCms; 149 150 /** The copied resource. */ 151 private CmsResource m_copiedFolderOrPage; 152 153 /** The copy mode. */ 154 private CopyMode m_copyMode = CopyMode.smartCopyAndChangeLocale; 155 156 /** Map of custom replacements. */ 157 private Map<CmsUUID, CmsUUID> m_customReplacements; 158 159 /** Maps structure ids of original container elements to structure ids of their copies/replacements. */ 160 private Map<CmsUUID, CmsUUID> m_elementReplacements = Maps.newHashMap(); 161 162 /** The original page. */ 163 private CmsResource m_originalPage; 164 165 /** The target folder. */ 166 private CmsResource m_targetFolder; 167 168 /** Resource types which require custom replacements. */ 169 private Set<String> m_typesWithRequiredReplacements; 170 171 /** 172 * Creates a new instance.<p> 173 * 174 * @param cms the CMS context to use 175 */ 176 public CmsContainerPageCopier(CmsObject cms) { 177 178 m_cms = cms; 179 } 180 181 /** 182 * Converts locales for the copied container element.<p> 183 * 184 * @param elementResource the copied container element 185 * @throws CmsException if something goes wrong 186 */ 187 public void adjustLocalesForElement(CmsResource elementResource) throws CmsException { 188 189 if (m_copyMode != CopyMode.smartCopyAndChangeLocale) { 190 return; 191 } 192 193 CmsFile file = m_cms.readFile(elementResource); 194 Locale oldLocale = OpenCms.getLocaleManager().getDefaultLocale(m_cms, m_originalPage); 195 Locale newLocale = OpenCms.getLocaleManager().getDefaultLocale(m_cms, m_targetFolder); 196 CmsXmlContent content = CmsXmlContentFactory.unmarshal(m_cms, file); 197 try { 198 content.moveLocale(oldLocale, newLocale); 199 LOG.info("Replacing locale " + oldLocale + " -> " + newLocale + " for " + elementResource.getRootPath()); 200 file.setContents(content.marshal()); 201 m_cms.writeFile(file); 202 } catch (CmsXmlException e) { 203 LOG.info( 204 "NOT replacing locale for " 205 + elementResource.getRootPath() 206 + ": old=" 207 + oldLocale 208 + ", new=" 209 + newLocale 210 + ", contentLocales=" 211 + content.getLocales()); 212 } 213 214 } 215 216 /** 217 * Copies the given container page to the provided root path. 218 * @param originalPage the page to copy 219 * @param targetPageRootPath the root path of the copy target. 220 * @throws CmsException thrown if something goes wrong. 221 * @throws NoCustomReplacementException if a custom replacement is not found for a type which requires it. 222 */ 223 public void copyPageOnly(CmsResource originalPage, String targetPageRootPath) 224 throws CmsException, NoCustomReplacementException { 225 226 if ((null == originalPage) 227 || !OpenCms.getResourceManager().getResourceType(originalPage).getTypeName().equals( 228 CmsResourceTypeXmlContainerPage.getStaticTypeName())) { 229 throw new CmsException(new CmsMessageContainer(Messages.get(), Messages.ERR_PAGECOPY_INVALID_PAGE_0)); 230 } 231 m_originalPage = originalPage; 232 CmsObject rootCms = getRootCms(); 233 rootCms.copyResource(originalPage.getRootPath(), targetPageRootPath); 234 CmsResource copiedPage = rootCms.readResource(targetPageRootPath, CmsResourceFilter.IGNORE_EXPIRATION); 235 m_targetFolder = rootCms.readResource(CmsResource.getFolderPath(copiedPage.getRootPath())); 236 replaceElements(copiedPage); 237 attachLocaleGroups(copiedPage); 238 tryUnlock(copiedPage); 239 240 } 241 242 /** 243 * Gets the copied folder or page.<p> 244 * 245 * @return the copied folder or page 246 */ 247 public CmsResource getCopiedFolderOrPage() { 248 249 return m_copiedFolderOrPage; 250 } 251 252 /** 253 * Returns the target folder.<p> 254 * 255 * @return the target folder 256 */ 257 public CmsResource getTargetFolder() { 258 259 return m_targetFolder; 260 } 261 262 /** 263 * Produces the replacement for a container page element to use in a copy of an existing container page.<p> 264 * 265 * @param targetPage the target container page 266 * @param originalElement the original element 267 * @return the replacement element for the copied page 268 * 269 * @throws CmsException if something goes wrong 270 * @throws NoCustomReplacementException if a custom replacement is not found for a type which requires it 271 */ 272 public CmsContainerElementBean replaceContainerElement( 273 CmsResource targetPage, 274 CmsContainerElementBean originalElement) 275 throws CmsException, NoCustomReplacementException { 276 // if (m_elementReplacements.containsKey(originalElement.getId() 277 278 CmsObject targetCms = OpenCms.initCmsObject(m_cms); 279 280 CmsSite site = OpenCms.getSiteManager().getSiteForRootPath(m_targetFolder.getRootPath()); 281 if (site != null) { 282 targetCms.getRequestContext().setSiteRoot(site.getSiteRoot()); 283 } 284 285 if ((originalElement.getFormatterId() == null) || (originalElement.getId() == null)) { 286 String rootPath = m_originalPage != null ? m_originalPage.getRootPath() : "???"; 287 LOG.warn("Skipping container element because of missing id in page: " + rootPath); 288 return null; 289 } 290 291 if (m_elementReplacements.containsKey(originalElement.getId())) { 292 return new CmsContainerElementBean( 293 m_elementReplacements.get(originalElement.getId()), 294 maybeReplaceFormatter(originalElement.getFormatterId()), 295 maybeReplaceFormatterInSettings(originalElement.getIndividualSettings()), 296 originalElement.isCreateNew()); 297 } else { 298 CmsResource originalResource = m_cms.readResource( 299 originalElement.getId(), 300 CmsResourceFilter.IGNORE_EXPIRATION); 301 I_CmsResourceType type = OpenCms.getResourceManager().getResourceType(originalResource); 302 CmsADEConfigData config = OpenCms.getADEManager().lookupConfiguration(m_cms, targetPage.getRootPath()); 303 CmsResourceTypeConfig typeConfig = config.getResourceType(type.getTypeName()); 304 if ((m_copyMode != CopyMode.reuse) 305 && (typeConfig != null) 306 && (originalElement.isCreateNew() || typeConfig.isCopyInModels()) 307 && !type.getTypeName().equals(CmsResourceTypeXmlContainerPage.MODEL_GROUP_TYPE_NAME)) { 308 CmsResource resourceCopy = typeConfig.createNewElement( 309 targetCms, 310 originalResource, 311 targetPage.getRootPath()); 312 CmsContainerElementBean copy = new CmsContainerElementBean( 313 resourceCopy.getStructureId(), 314 maybeReplaceFormatter(originalElement.getFormatterId()), 315 maybeReplaceFormatterInSettings(originalElement.getIndividualSettings()), 316 originalElement.isCreateNew()); 317 m_elementReplacements.put(originalElement.getId(), resourceCopy.getStructureId()); 318 LOG.info( 319 "Copied container element " + originalResource.getRootPath() + " -> " + resourceCopy.getRootPath()); 320 CmsLockActionRecord record = null; 321 try { 322 record = CmsLockUtil.ensureLock(m_cms, resourceCopy); 323 adjustLocalesForElement(resourceCopy); 324 } finally { 325 if ((record != null) && (record.getChange() == LockChange.locked)) { 326 m_cms.unlockResource(resourceCopy); 327 } 328 } 329 return copy; 330 } else if (m_customReplacements != null) { 331 CmsUUID replacementId = m_customReplacements.get(originalElement.getId()); 332 if (replacementId != null) { 333 334 return new CmsContainerElementBean( 335 replacementId, 336 maybeReplaceFormatter(originalElement.getFormatterId()), 337 maybeReplaceFormatterInSettings(originalElement.getIndividualSettings()), 338 originalElement.isCreateNew()); 339 } else { 340 if ((m_typesWithRequiredReplacements != null) 341 && m_typesWithRequiredReplacements.contains(type.getTypeName())) { 342 throw new NoCustomReplacementException(originalResource); 343 } else { 344 return originalElement; 345 } 346 347 } 348 } else { 349 LOG.info("Reusing container element: " + originalResource.getRootPath()); 350 return originalElement; 351 } 352 } 353 } 354 355 /** 356 * Replaces the elements in the copied container page with copies, if appropriate based on the current copy mode.<p> 357 * 358 * @param containerPage the container page copy whose elements should be replaced with copies 359 * 360 * @throws CmsException if something goes wrong 361 * @throws NoCustomReplacementException if a custom replacement element was not found for a type which requires it 362 */ 363 public void replaceElements(CmsResource containerPage) throws CmsException, NoCustomReplacementException { 364 365 CmsObject rootCms = getRootCms(); 366 CmsObject targetCms = OpenCms.initCmsObject(m_cms); 367 targetCms.getRequestContext().setSiteRoot(""); 368 CmsSite site = OpenCms.getSiteManager().getSiteForRootPath(m_targetFolder.getRootPath()); 369 if (site != null) { 370 targetCms.getRequestContext().setSiteRoot(site.getSiteRoot()); 371 } else if (OpenCms.getSiteManager().startsWithShared(m_targetFolder.getRootPath())) { 372 targetCms.getRequestContext().setSiteRoot(OpenCms.getSiteManager().getSharedFolder()); 373 } 374 375 CmsProperty elementReplacementProp = rootCms.readPropertyObject( 376 m_targetFolder, 377 CmsPropertyDefinition.PROPERTY_ELEMENT_REPLACEMENTS, 378 true); 379 if ((elementReplacementProp != null) && (elementReplacementProp.getValue() != null)) { 380 try { 381 CmsResource elementReplacementMap = targetCms.readResource( 382 elementReplacementProp.getValue(), 383 CmsResourceFilter.IGNORE_EXPIRATION); 384 OpenCms.getLocaleManager(); 385 String encoding = CmsLocaleManager.getResourceEncoding(targetCms, elementReplacementMap); 386 CmsFile elementReplacementFile = targetCms.readFile(elementReplacementMap); 387 Properties props = new Properties(); 388 props.load( 389 new InputStreamReader(new ByteArrayInputStream(elementReplacementFile.getContents()), encoding)); 390 CmsMacroResolver resolver = new CmsMacroResolver(); 391 resolver.addMacro("sourcesite", m_cms.getRequestContext().getSiteRoot().replaceAll("/+$", "")); 392 resolver.addMacro("targetsite", targetCms.getRequestContext().getSiteRoot().replaceAll("/+$", "")); 393 Map<CmsUUID, CmsUUID> customReplacements = Maps.newHashMap(); 394 for (Map.Entry<Object, Object> entry : props.entrySet()) { 395 if ((entry.getKey() instanceof String) && (entry.getValue() instanceof String)) { 396 try { 397 String key = (String)entry.getKey(); 398 if ("required".equals(key)) { 399 m_typesWithRequiredReplacements = Sets.newHashSet( 400 ((String)entry.getValue()).split(" *, *")); 401 continue; 402 } 403 key = resolver.resolveMacros(key); 404 String value = (String)entry.getValue(); 405 value = resolver.resolveMacros(value); 406 CmsResource keyRes = rootCms.readResource(key, CmsResourceFilter.IGNORE_EXPIRATION); 407 CmsResource valRes = rootCms.readResource(value, CmsResourceFilter.IGNORE_EXPIRATION); 408 customReplacements.put(keyRes.getStructureId(), valRes.getStructureId()); 409 } catch (Exception e) { 410 LOG.error(e.getLocalizedMessage(), e); 411 } 412 m_customReplacements = customReplacements; 413 } 414 } 415 } catch (CmsException e) { 416 LOG.warn(e.getLocalizedMessage(), e); 417 } catch (IOException e) { 418 LOG.warn(e.getLocalizedMessage(), e); 419 } 420 } 421 422 CmsXmlContainerPage pageXml = CmsXmlContainerPageFactory.unmarshal(m_cms, containerPage); 423 CmsContainerPageBean page = pageXml.getContainerPage(m_cms); 424 List<CmsContainerBean> newContainers = Lists.newArrayList(); 425 for (CmsContainerBean container : page.getContainers().values()) { 426 List<CmsContainerElementBean> newElements = Lists.newArrayList(); 427 for (CmsContainerElementBean element : container.getElements()) { 428 CmsContainerElementBean newBean = replaceContainerElement(containerPage, element); 429 if (newBean != null) { 430 newElements.add(newBean); 431 } 432 } 433 CmsContainerBean newContainer = new CmsContainerBean( 434 container.getName(), 435 container.getType(), 436 container.getParentInstanceId(), 437 container.isRootContainer(), 438 newElements); 439 newContainers.add(newContainer); 440 } 441 CmsContainerPageBean newPageBean = new CmsContainerPageBean(newContainers); 442 pageXml.save(rootCms, newPageBean); 443 } 444 445 /** 446 * Starts the page copying process.<p> 447 * 448 * @param source the source (can be either a container page, or a folder whose default file is a container page) 449 * @param target the target folder 450 * 451 * @throws CmsException if soemthing goes wrong 452 * @throws NoCustomReplacementException if a custom replacement element was not found 453 */ 454 public void run(CmsResource source, CmsResource target) throws CmsException, NoCustomReplacementException { 455 456 run(source, target, null); 457 } 458 459 /** 460 * Starts the page copying process.<p> 461 * 462 * @param source the source (can be either a container page, or a folder whose default file is a container page) 463 * @param target the target folder 464 * @param targetName the name to give the new folder 465 * 466 * @throws CmsException if something goes wrong 467 * @throws NoCustomReplacementException if a custom replacement element was not found 468 */ 469 public void run(CmsResource source, CmsResource target, String targetName) 470 throws CmsException, NoCustomReplacementException { 471 472 LOG.info( 473 "Starting page copy process: page='" 474 + source.getRootPath() 475 + "', targetFolder='" 476 + target.getRootPath() 477 + "'"); 478 CmsObject rootCms = getRootCms(); 479 if (m_copyMode == CopyMode.automatic) { 480 Locale sourceLocale = OpenCms.getLocaleManager().getDefaultLocale(rootCms, source); 481 Locale targetLocale = OpenCms.getLocaleManager().getDefaultLocale(rootCms, target); 482 // if same locale, copy elements, otherwise use configured setting 483 LOG.debug( 484 "copy mode automatic: source=" 485 + sourceLocale 486 + " target=" 487 + targetLocale 488 + " reuseConfig=" 489 + OpenCms.getLocaleManager().shouldReuseElements() 490 + ""); 491 if (sourceLocale.equals(targetLocale)) { 492 m_copyMode = CopyMode.smartCopyAndChangeLocale; 493 } else { 494 if (OpenCms.getLocaleManager().shouldReuseElements()) { 495 m_copyMode = CopyMode.reuse; 496 } else { 497 m_copyMode = CopyMode.smartCopyAndChangeLocale; 498 } 499 } 500 } 501 502 if (source.isFolder()) { 503 if (source.equals(target)) { 504 throw new CmsException(Messages.get().container(Messages.ERR_PAGECOPY_SOURCE_IS_TARGET_0)); 505 } 506 CmsResource page = m_cms.readDefaultFile(source, CmsResourceFilter.IGNORE_EXPIRATION); 507 if ((page == null) || !CmsResourceTypeXmlContainerPage.isContainerPage(page)) { 508 throw new CmsException(Messages.get().container(Messages.ERR_PAGECOPY_INVALID_PAGE_0)); 509 } 510 List<CmsProperty> properties = Lists.newArrayList(m_cms.readPropertyObjects(source, false)); 511 Iterator<CmsProperty> iterator = properties.iterator(); 512 while (iterator.hasNext()) { 513 CmsProperty prop = iterator.next(); 514 // copied folder may be root of a locale subtree, but since we may want to copy to a different locale, 515 // we don't want the locale property in the copy 516 if (prop.getName().equals(CmsPropertyDefinition.PROPERTY_LOCALE) 517 || prop.getName().equals(CmsPropertyDefinition.PROPERTY_ELEMENT_REPLACEMENTS)) { 518 iterator.remove(); 519 } 520 } 521 522 I_CmsFileNameGenerator nameGen = OpenCms.getResourceManager().getNameGenerator(); 523 String copyPath; 524 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(targetName)) { 525 copyPath = CmsStringUtil.joinPaths(target.getRootPath(), targetName); 526 if (rootCms.existsResource(copyPath)) { 527 CmsResource existingResource = rootCms.readResource(copyPath); 528 // only overwrite the existing resource if it's a folder, otherwise find the next non-existing 'numbered' target path 529 if (!existingResource.isFolder()) { 530 copyPath = nameGen.getNewFileName(rootCms, copyPath + "%(number)", 4, true); 531 } 532 } 533 } else { 534 copyPath = CmsFileUtil.removeTrailingSeparator( 535 CmsStringUtil.joinPaths(target.getRootPath(), source.getName())); 536 copyPath = nameGen.getNewFileName(rootCms, copyPath + "%(number)", 4, true); 537 } 538 Double maxNavPosObj = readMaxNavPos(target); 539 double maxNavpos = maxNavPosObj == null ? 0 : maxNavPosObj.doubleValue(); 540 boolean hasNavpos = maxNavPosObj != null; 541 CmsResource copiedFolder = null; 542 CmsLockActionRecord lockRecord = null; 543 if (rootCms.existsResource(copyPath)) { 544 copiedFolder = rootCms.readResource(copyPath); 545 lockRecord = CmsLockUtil.ensureLock(rootCms, copiedFolder); 546 rootCms.writePropertyObjects(copyPath, properties); 547 } else { 548 copiedFolder = rootCms.createResource( 549 copyPath, 550 OpenCms.getResourceManager().getResourceType(CmsResourceTypeFolder.RESOURCE_TYPE_NAME), 551 null, 552 properties); 553 } 554 if (hasNavpos) { 555 String newNavPosStr = "" + (maxNavpos + 10); 556 rootCms.writePropertyObject( 557 copiedFolder.getRootPath(), 558 new CmsProperty(CmsPropertyDefinition.PROPERTY_NAVPOS, newNavPosStr, null)); 559 } 560 String pageCopyPath = CmsStringUtil.joinPaths(copiedFolder.getRootPath(), page.getName()); 561 m_originalPage = page; 562 m_targetFolder = target; 563 m_copiedFolderOrPage = copiedFolder; 564 if (rootCms.existsResource(pageCopyPath, CmsResourceFilter.IGNORE_EXPIRATION)) { 565 rootCms.deleteResource(pageCopyPath, CmsResource.DELETE_PRESERVE_SIBLINGS); 566 } 567 rootCms.copyResource(page.getRootPath(), pageCopyPath); 568 569 CmsResource copiedPage = rootCms.readResource(pageCopyPath, CmsResourceFilter.IGNORE_EXPIRATION); 570 571 replaceElements(copiedPage); 572 attachLocaleGroups(copiedPage); 573 if ((lockRecord == null) || (lockRecord.getChange() == LockChange.locked)) { 574 tryUnlock(copiedFolder); 575 } 576 } else { 577 CmsResource page = source; 578 if (!CmsResourceTypeXmlContainerPage.isContainerPage(page)) { 579 throw new CmsException(Messages.get().container(Messages.ERR_PAGECOPY_INVALID_PAGE_0)); 580 } 581 I_CmsFileNameGenerator nameGen = OpenCms.getResourceManager().getNameGenerator(); 582 String copyPath = CmsFileUtil.removeTrailingSeparator( 583 CmsStringUtil.joinPaths(target.getRootPath(), source.getName())); 584 int lastDot = copyPath.lastIndexOf("."); 585 int lastSlash = copyPath.lastIndexOf("/"); 586 if (lastDot > lastSlash) { // path has an extension 587 String macroPath = copyPath.substring(0, lastDot) + "%(number)" + copyPath.substring(lastDot); 588 copyPath = nameGen.getNewFileName(rootCms, macroPath, 4, true); 589 } else { 590 copyPath = nameGen.getNewFileName(rootCms, copyPath + "%(number)", 4, true); 591 } 592 Double maxNavPosObj = readMaxNavPos(target); 593 double maxNavpos = maxNavPosObj == null ? 0 : maxNavPosObj.doubleValue(); 594 boolean hasNavpos = maxNavPosObj != null; 595 rootCms.copyResource(page.getRootPath(), copyPath); 596 if (hasNavpos) { 597 String newNavPosStr = "" + (maxNavpos + 10); 598 rootCms.writePropertyObject( 599 copyPath, 600 new CmsProperty(CmsPropertyDefinition.PROPERTY_NAVPOS, newNavPosStr, null)); 601 } 602 CmsResource copiedPage = rootCms.readResource(copyPath); 603 m_originalPage = page; 604 m_targetFolder = target; 605 m_copiedFolderOrPage = copiedPage; 606 replaceElements(copiedPage); 607 attachLocaleGroups(copiedPage); 608 tryUnlock(copiedPage); 609 610 } 611 } 612 613 /** 614 * Sets the copy mode.<p> 615 * 616 * @param copyMode the copy mode 617 */ 618 public void setCopyMode(CopyMode copyMode) { 619 620 m_copyMode = copyMode; 621 } 622 623 /** 624 * Reads the max nav position from the contents of a folder.<p> 625 * 626 * @param target a folder 627 * @return the maximal NavPos from the contents of the folder, or null if no resources with a valid NavPos were found in the folder 628 * 629 * @throws CmsException if something goes wrong 630 */ 631 Double readMaxNavPos(CmsResource target) throws CmsException { 632 633 List<CmsResource> existingResourcesInFolder = m_cms.readResources( 634 target, 635 CmsResourceFilter.IGNORE_EXPIRATION, 636 false); 637 638 double maxNavpos = 0.0; 639 boolean hasNavpos = false; 640 for (CmsResource existingResource : existingResourcesInFolder) { 641 CmsProperty navpos = m_cms.readPropertyObject( 642 existingResource, 643 CmsPropertyDefinition.PROPERTY_NAVPOS, 644 false); 645 if (navpos.getValue() != null) { 646 try { 647 double navposNum = Double.parseDouble(navpos.getValue()); 648 hasNavpos = true; 649 maxNavpos = Math.max(navposNum, maxNavpos); 650 } catch (NumberFormatException e) { 651 // ignore 652 } 653 } 654 } 655 if (hasNavpos) { 656 return Double.valueOf(maxNavpos); 657 } else { 658 return null; 659 } 660 } 661 662 /** 663 * Attaches locale groups to the copied page. 664 * @param copiedPage the copied page. 665 * @throws CmsException thrown if the root cms cannot be retrieved. 666 */ 667 private void attachLocaleGroups(CmsResource copiedPage) throws CmsException { 668 669 CmsLocaleGroupService localeGroupService = getRootCms().getLocaleGroupService(); 670 if (Status.linkable == localeGroupService.checkLinkable(m_originalPage, copiedPage)) { 671 try { 672 localeGroupService.attachLocaleGroupIndirect(m_originalPage, copiedPage); 673 } catch (CmsException e) { 674 LOG.error(e.getLocalizedMessage(), e); 675 } 676 } 677 } 678 679 /** 680 * Return the cms object with the site root set to "/". 681 * @return the cms object with the site root set to "/". 682 * @throws CmsException thrown if initializing the root cms object fails. 683 */ 684 private CmsObject getRootCms() throws CmsException { 685 686 if (null == m_rootCms) { 687 m_rootCms = OpenCms.initCmsObject(m_cms); 688 m_rootCms.getRequestContext().setSiteRoot(""); 689 } 690 return m_rootCms; 691 } 692 693 /** 694 * Uses the custom translation table to translate formatter id.<p> 695 * 696 * @param formatterId the formatter id 697 * @return the formatter replacement 698 */ 699 private CmsUUID maybeReplaceFormatter(CmsUUID formatterId) { 700 701 if (m_customReplacements != null) { 702 CmsUUID replacement = m_customReplacements.get(formatterId); 703 if (replacement != null) { 704 return replacement; 705 } 706 } 707 return formatterId; 708 } 709 710 /** 711 * Replaces formatter id in element settings.<p> 712 * 713 * @param individualSettings the settings in which to replace the formatter id 714 * 715 * @return the map with the possible replaced ids 716 */ 717 private Map<String, String> maybeReplaceFormatterInSettings(Map<String, String> individualSettings) { 718 719 if (individualSettings == null) { 720 return null; 721 } else if (m_customReplacements == null) { 722 return individualSettings; 723 } else { 724 LinkedHashMap<String, String> result = new LinkedHashMap<String, String>(); 725 for (Map.Entry<String, String> entry : individualSettings.entrySet()) { 726 String value = entry.getValue(); 727 if (CmsUUID.isValidUUID(value)) { 728 CmsUUID valueId = new CmsUUID(value); 729 if (m_customReplacements.containsKey(valueId)) { 730 value = "" + m_customReplacements.get(valueId); 731 } 732 } 733 result.put(entry.getKey(), value); 734 } 735 return result; 736 } 737 } 738 739 /** 740 * Tries to unlock the given resource.<p> 741 * 742 * @param resource the resource to unlock 743 */ 744 private void tryUnlock(CmsResource resource) { 745 746 try { 747 m_cms.unlockResource(resource); 748 } catch (CmsException e) { 749 // usually not a problem 750 LOG.debug("failed to unlock " + resource.getRootPath(), e); 751 } 752 753 } 754 755}