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 resource 185 * @param originalResource the original container element resource 186 * 187 * @throws CmsException if something goes wrong 188 */ 189 public void adjustLocalesForElement(CmsResource elementResource, CmsResource originalResource) throws CmsException { 190 191 if (m_copyMode != CopyMode.smartCopyAndChangeLocale) { 192 return; 193 } 194 195 CmsFile file = m_cms.readFile(elementResource); 196 Locale oldLocale = OpenCms.getLocaleManager().getDefaultLocale(m_cms, m_originalPage); 197 Locale newLocale = OpenCms.getLocaleManager().getDefaultLocale(m_cms, m_targetFolder); 198 CmsXmlContent content = CmsXmlContentFactory.unmarshal(m_cms, file); 199 try { 200 if (content.hasLocale(newLocale)) { 201 // check if the new locale was already present in the original resource, it may have been created automatically 202 CmsFile origFile = m_cms.readFile(originalResource); 203 CmsXmlContent origContent = CmsXmlContentFactory.unmarshal(m_cms, origFile); 204 if (!origContent.hasLocale(newLocale)) { 205 // the target locale was not present in the original, remove it to ensure the source locale is moved 206 content.removeLocale(newLocale); 207 } 208 } 209 content.moveLocale(oldLocale, newLocale); 210 LOG.info("Replacing locale " + oldLocale + " -> " + newLocale + " for " + elementResource.getRootPath()); 211 file.setContents(content.marshal()); 212 m_cms.writeFile(file); 213 } catch (CmsXmlException e) { 214 LOG.info( 215 "NOT replacing locale for " 216 + elementResource.getRootPath() 217 + ": old=" 218 + oldLocale 219 + ", new=" 220 + newLocale 221 + ", contentLocales=" 222 + content.getLocales()); 223 } 224 225 } 226 227 /** 228 * Copies the given container page to the provided root path. 229 * @param originalPage the page to copy 230 * @param targetPageRootPath the root path of the copy target. 231 * @throws CmsException thrown if something goes wrong. 232 * @throws NoCustomReplacementException if a custom replacement is not found for a type which requires it. 233 */ 234 public void copyPageOnly(CmsResource originalPage, String targetPageRootPath) 235 throws CmsException, NoCustomReplacementException { 236 237 if ((null == originalPage) 238 || !OpenCms.getResourceManager().getResourceType(originalPage).getTypeName().equals( 239 CmsResourceTypeXmlContainerPage.getStaticTypeName())) { 240 throw new CmsException(new CmsMessageContainer(Messages.get(), Messages.ERR_PAGECOPY_INVALID_PAGE_0)); 241 } 242 m_originalPage = originalPage; 243 CmsObject rootCms = getRootCms(); 244 rootCms.copyResource(originalPage.getRootPath(), targetPageRootPath); 245 CmsResource copiedPage = rootCms.readResource(targetPageRootPath, CmsResourceFilter.IGNORE_EXPIRATION); 246 m_targetFolder = rootCms.readResource(CmsResource.getFolderPath(copiedPage.getRootPath())); 247 replaceElements(copiedPage); 248 attachLocaleGroups(copiedPage); 249 tryUnlock(copiedPage); 250 251 } 252 253 /** 254 * Gets the copied folder or page.<p> 255 * 256 * @return the copied folder or page 257 */ 258 public CmsResource getCopiedFolderOrPage() { 259 260 return m_copiedFolderOrPage; 261 } 262 263 /** 264 * Returns the target folder.<p> 265 * 266 * @return the target folder 267 */ 268 public CmsResource getTargetFolder() { 269 270 return m_targetFolder; 271 } 272 273 /** 274 * Produces the replacement for a container page element to use in a copy of an existing container page.<p> 275 * 276 * @param targetPage the target container page 277 * @param originalElement the original element 278 * @return the replacement element for the copied page 279 * 280 * @throws CmsException if something goes wrong 281 * @throws NoCustomReplacementException if a custom replacement is not found for a type which requires it 282 */ 283 public CmsContainerElementBean replaceContainerElement( 284 CmsResource targetPage, 285 CmsContainerElementBean originalElement) 286 throws CmsException, NoCustomReplacementException { 287 // if (m_elementReplacements.containsKey(originalElement.getId() 288 289 CmsObject targetCms = OpenCms.initCmsObject(m_cms); 290 291 CmsSite site = OpenCms.getSiteManager().getSiteForRootPath(m_targetFolder.getRootPath()); 292 if (site != null) { 293 targetCms.getRequestContext().setSiteRoot(site.getSiteRoot()); 294 } 295 296 if (originalElement.getId() == null) { 297 String rootPath = m_originalPage != null ? m_originalPage.getRootPath() : "???"; 298 LOG.warn("Skipping container element because of missing id in page: " + rootPath); 299 return null; 300 } 301 302 if (m_elementReplacements.containsKey(originalElement.getId())) { 303 return new CmsContainerElementBean( 304 m_elementReplacements.get(originalElement.getId()), 305 maybeReplaceFormatter(originalElement.getFormatterId()), 306 maybeReplaceFormatterInSettings(originalElement.getIndividualSettings()), 307 originalElement.isCreateNew()); 308 } else { 309 CmsResource originalResource = m_cms.readResource( 310 originalElement.getId(), 311 CmsResourceFilter.IGNORE_EXPIRATION); 312 I_CmsResourceType type = OpenCms.getResourceManager().getResourceType(originalResource); 313 CmsADEConfigData config = OpenCms.getADEManager().lookupConfiguration(m_cms, targetPage.getRootPath()); 314 CmsResourceTypeConfig typeConfig = config.getResourceType(type.getTypeName()); 315 if ((m_copyMode != CopyMode.reuse) 316 && (typeConfig != null) 317 && (originalElement.isCreateNew() || typeConfig.isCopyInModels()) 318 && !type.getTypeName().equals(CmsResourceTypeXmlContainerPage.MODEL_GROUP_TYPE_NAME)) { 319 // set the request context locale to the target content locale as this is used during content creation 320 Locale targetLocale = OpenCms.getLocaleManager().getDefaultLocale(m_cms, m_targetFolder); 321 targetCms.getRequestContext().setLocale(targetLocale); 322 CmsResource resourceCopy = typeConfig.createNewElement( 323 targetCms, 324 originalResource, 325 CmsResource.getParentFolder(targetPage.getRootPath())); 326 CmsContainerElementBean copy = new CmsContainerElementBean( 327 resourceCopy.getStructureId(), 328 maybeReplaceFormatter(originalElement.getFormatterId()), 329 maybeReplaceFormatterInSettings(originalElement.getIndividualSettings()), 330 originalElement.isCreateNew()); 331 m_elementReplacements.put(originalElement.getId(), resourceCopy.getStructureId()); 332 LOG.info( 333 "Copied container element " + originalResource.getRootPath() + " -> " + resourceCopy.getRootPath()); 334 CmsLockActionRecord record = null; 335 try { 336 record = CmsLockUtil.ensureLock(m_cms, resourceCopy); 337 adjustLocalesForElement(resourceCopy, originalResource); 338 } finally { 339 if ((record != null) && (record.getChange() == LockChange.locked)) { 340 m_cms.unlockResource(resourceCopy); 341 } 342 } 343 return copy; 344 } else if (m_customReplacements != null) { 345 CmsUUID replacementId = m_customReplacements.get(originalElement.getId()); 346 if (replacementId != null) { 347 348 return new CmsContainerElementBean( 349 replacementId, 350 maybeReplaceFormatter(originalElement.getFormatterId()), 351 maybeReplaceFormatterInSettings(originalElement.getIndividualSettings()), 352 originalElement.isCreateNew()); 353 } else { 354 if ((m_typesWithRequiredReplacements != null) 355 && m_typesWithRequiredReplacements.contains(type.getTypeName())) { 356 throw new NoCustomReplacementException(originalResource); 357 } else { 358 return originalElement; 359 } 360 361 } 362 } else { 363 LOG.info("Reusing container element: " + originalResource.getRootPath()); 364 return originalElement; 365 } 366 } 367 } 368 369 /** 370 * Replaces the elements in the copied container page with copies, if appropriate based on the current copy mode.<p> 371 * 372 * @param containerPage the container page copy whose elements should be replaced with copies 373 * 374 * @throws CmsException if something goes wrong 375 * @throws NoCustomReplacementException if a custom replacement element was not found for a type which requires it 376 */ 377 public void replaceElements(CmsResource containerPage) throws CmsException, NoCustomReplacementException { 378 379 CmsObject rootCms = getRootCms(); 380 CmsObject targetCms = OpenCms.initCmsObject(m_cms); 381 targetCms.getRequestContext().setSiteRoot(""); 382 CmsSite site = OpenCms.getSiteManager().getSiteForRootPath(m_targetFolder.getRootPath()); 383 if (site != null) { 384 targetCms.getRequestContext().setSiteRoot(site.getSiteRoot()); 385 } else if (OpenCms.getSiteManager().startsWithShared(m_targetFolder.getRootPath())) { 386 targetCms.getRequestContext().setSiteRoot(OpenCms.getSiteManager().getSharedFolder()); 387 } 388 389 CmsProperty elementReplacementProp = rootCms.readPropertyObject( 390 m_targetFolder, 391 CmsPropertyDefinition.PROPERTY_ELEMENT_REPLACEMENTS, 392 true); 393 if ((elementReplacementProp != null) && (elementReplacementProp.getValue() != null)) { 394 try { 395 CmsResource elementReplacementMap = targetCms.readResource( 396 elementReplacementProp.getValue(), 397 CmsResourceFilter.IGNORE_EXPIRATION); 398 OpenCms.getLocaleManager(); 399 String encoding = CmsLocaleManager.getResourceEncoding(targetCms, elementReplacementMap); 400 CmsFile elementReplacementFile = targetCms.readFile(elementReplacementMap); 401 Properties props = new Properties(); 402 props.load( 403 new InputStreamReader(new ByteArrayInputStream(elementReplacementFile.getContents()), encoding)); 404 CmsMacroResolver resolver = new CmsMacroResolver(); 405 resolver.addMacro("sourcesite", m_cms.getRequestContext().getSiteRoot().replaceAll("/+$", "")); 406 resolver.addMacro("targetsite", targetCms.getRequestContext().getSiteRoot().replaceAll("/+$", "")); 407 Map<CmsUUID, CmsUUID> customReplacements = Maps.newHashMap(); 408 for (Map.Entry<Object, Object> entry : props.entrySet()) { 409 if ((entry.getKey() instanceof String) && (entry.getValue() instanceof String)) { 410 try { 411 String key = (String)entry.getKey(); 412 if ("required".equals(key)) { 413 m_typesWithRequiredReplacements = Sets.newHashSet( 414 ((String)entry.getValue()).split(" *, *")); 415 continue; 416 } 417 key = resolver.resolveMacros(key); 418 String value = (String)entry.getValue(); 419 value = resolver.resolveMacros(value); 420 CmsResource keyRes = rootCms.readResource(key, CmsResourceFilter.IGNORE_EXPIRATION); 421 CmsResource valRes = rootCms.readResource(value, CmsResourceFilter.IGNORE_EXPIRATION); 422 customReplacements.put(keyRes.getStructureId(), valRes.getStructureId()); 423 } catch (Exception e) { 424 LOG.error(e.getLocalizedMessage(), e); 425 } 426 m_customReplacements = customReplacements; 427 } 428 } 429 } catch (CmsException e) { 430 LOG.warn(e.getLocalizedMessage(), e); 431 } catch (IOException e) { 432 LOG.warn(e.getLocalizedMessage(), e); 433 } 434 } 435 436 CmsXmlContainerPage pageXml = CmsXmlContainerPageFactory.unmarshal(m_cms, containerPage); 437 CmsContainerPageBean page = pageXml.getContainerPage(m_cms); 438 List<CmsContainerBean> newContainers = Lists.newArrayList(); 439 for (CmsContainerBean container : page.getContainers().values()) { 440 List<CmsContainerElementBean> newElements = Lists.newArrayList(); 441 for (CmsContainerElementBean element : container.getElements()) { 442 CmsContainerElementBean newBean = replaceContainerElement(containerPage, element); 443 if (newBean != null) { 444 newElements.add(newBean); 445 } 446 } 447 CmsContainerBean newContainer = container.copyWithNewElements(newElements); 448 newContainers.add(newContainer); 449 } 450 CmsContainerPageBean newPageBean = new CmsContainerPageBean(newContainers); 451 pageXml.save(rootCms, newPageBean); 452 } 453 454 /** 455 * Starts the page copying process.<p> 456 * 457 * @param source the source (can be either a container page, or a folder whose default file is a container page) 458 * @param target the target folder 459 * 460 * @throws CmsException if soemthing goes wrong 461 * @throws NoCustomReplacementException if a custom replacement element was not found 462 */ 463 public void run(CmsResource source, CmsResource target) throws CmsException, NoCustomReplacementException { 464 465 run(source, target, null); 466 } 467 468 /** 469 * Starts the page copying process.<p> 470 * 471 * @param source the source (can be either a container page, or a folder whose default file is a container page) 472 * @param target the target folder 473 * @param targetName the name to give the new folder 474 * 475 * @throws CmsException if something goes wrong 476 * @throws NoCustomReplacementException if a custom replacement element was not found 477 */ 478 public void run(CmsResource source, CmsResource target, String targetName) 479 throws CmsException, NoCustomReplacementException { 480 481 LOG.info( 482 "Starting page copy process: page='" 483 + source.getRootPath() 484 + "', targetFolder='" 485 + target.getRootPath() 486 + "'"); 487 CmsObject rootCms = getRootCms(); 488 if (m_copyMode == CopyMode.automatic) { 489 Locale sourceLocale = OpenCms.getLocaleManager().getDefaultLocale(rootCms, source); 490 Locale targetLocale = OpenCms.getLocaleManager().getDefaultLocale(rootCms, target); 491 // if same locale, copy elements, otherwise use configured setting 492 LOG.debug( 493 "copy mode automatic: source=" 494 + sourceLocale 495 + " target=" 496 + targetLocale 497 + " reuseConfig=" 498 + OpenCms.getLocaleManager().shouldReuseElements() 499 + ""); 500 if (sourceLocale.equals(targetLocale)) { 501 m_copyMode = CopyMode.smartCopyAndChangeLocale; 502 } else { 503 if (OpenCms.getLocaleManager().shouldReuseElements()) { 504 m_copyMode = CopyMode.reuse; 505 } else { 506 m_copyMode = CopyMode.smartCopyAndChangeLocale; 507 } 508 } 509 } 510 511 if (source.isFolder()) { 512 if (source.equals(target)) { 513 throw new CmsException(Messages.get().container(Messages.ERR_PAGECOPY_SOURCE_IS_TARGET_0)); 514 } 515 CmsResource page = m_cms.readDefaultFile(source, CmsResourceFilter.IGNORE_EXPIRATION); 516 if ((page == null) || !CmsResourceTypeXmlContainerPage.isContainerPage(page)) { 517 throw new CmsException(Messages.get().container(Messages.ERR_PAGECOPY_INVALID_PAGE_0)); 518 } 519 List<CmsProperty> properties = Lists.newArrayList(m_cms.readPropertyObjects(source, false)); 520 Iterator<CmsProperty> iterator = properties.iterator(); 521 while (iterator.hasNext()) { 522 CmsProperty prop = iterator.next(); 523 // copied folder may be root of a locale subtree, but since we may want to copy to a different locale, 524 // we don't want the locale property in the copy 525 if (prop.getName().equals(CmsPropertyDefinition.PROPERTY_LOCALE) 526 || prop.getName().equals(CmsPropertyDefinition.PROPERTY_ELEMENT_REPLACEMENTS)) { 527 iterator.remove(); 528 } 529 } 530 531 I_CmsFileNameGenerator nameGen = OpenCms.getResourceManager().getNameGenerator(); 532 String copyPath; 533 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(targetName)) { 534 copyPath = CmsStringUtil.joinPaths(target.getRootPath(), targetName); 535 if (rootCms.existsResource(copyPath)) { 536 CmsResource existingResource = rootCms.readResource(copyPath); 537 // only overwrite the existing resource if it's a folder, otherwise find the next non-existing 'numbered' target path 538 if (!existingResource.isFolder()) { 539 copyPath = nameGen.getNewFileName(rootCms, copyPath + "%(number)", 4, true); 540 } 541 } 542 } else { 543 copyPath = CmsFileUtil.removeTrailingSeparator( 544 CmsStringUtil.joinPaths(target.getRootPath(), source.getName())); 545 copyPath = nameGen.getNewFileName(rootCms, copyPath + "%(number)", 4, true); 546 } 547 Double maxNavPosObj = readMaxNavPos(target); 548 double maxNavpos = maxNavPosObj == null ? 0 : maxNavPosObj.doubleValue(); 549 boolean hasNavpos = maxNavPosObj != null; 550 CmsResource copiedFolder = null; 551 CmsLockActionRecord lockRecord = null; 552 if (rootCms.existsResource(copyPath)) { 553 copiedFolder = rootCms.readResource(copyPath); 554 lockRecord = CmsLockUtil.ensureLock(rootCms, copiedFolder); 555 rootCms.writePropertyObjects(copyPath, properties); 556 } else { 557 copiedFolder = rootCms.createResource( 558 copyPath, 559 OpenCms.getResourceManager().getResourceType(CmsResourceTypeFolder.RESOURCE_TYPE_NAME), 560 null, 561 properties); 562 } 563 if (hasNavpos) { 564 String newNavPosStr = "" + (maxNavpos + 10); 565 rootCms.writePropertyObject( 566 copiedFolder.getRootPath(), 567 new CmsProperty(CmsPropertyDefinition.PROPERTY_NAVPOS, newNavPosStr, null)); 568 } 569 String pageCopyPath = CmsStringUtil.joinPaths(copiedFolder.getRootPath(), page.getName()); 570 m_originalPage = page; 571 m_targetFolder = target; 572 m_copiedFolderOrPage = copiedFolder; 573 if (rootCms.existsResource(pageCopyPath, CmsResourceFilter.IGNORE_EXPIRATION)) { 574 rootCms.deleteResource(pageCopyPath, CmsResource.DELETE_PRESERVE_SIBLINGS); 575 } 576 rootCms.copyResource(page.getRootPath(), pageCopyPath); 577 578 CmsResource copiedPage = rootCms.readResource(pageCopyPath, CmsResourceFilter.IGNORE_EXPIRATION); 579 580 replaceElements(copiedPage); 581 attachLocaleGroups(copiedPage); 582 if ((lockRecord == null) || (lockRecord.getChange() == LockChange.locked)) { 583 tryUnlock(copiedFolder); 584 } 585 } else { 586 CmsResource page = source; 587 if (!CmsResourceTypeXmlContainerPage.isContainerPage(page)) { 588 throw new CmsException(Messages.get().container(Messages.ERR_PAGECOPY_INVALID_PAGE_0)); 589 } 590 I_CmsFileNameGenerator nameGen = OpenCms.getResourceManager().getNameGenerator(); 591 String copyPath = CmsFileUtil.removeTrailingSeparator( 592 CmsStringUtil.joinPaths(target.getRootPath(), source.getName())); 593 int lastDot = copyPath.lastIndexOf("."); 594 int lastSlash = copyPath.lastIndexOf("/"); 595 if (lastDot > lastSlash) { // path has an extension 596 String macroPath = copyPath.substring(0, lastDot) + "%(number)" + copyPath.substring(lastDot); 597 copyPath = nameGen.getNewFileName(rootCms, macroPath, 4, true); 598 } else { 599 copyPath = nameGen.getNewFileName(rootCms, copyPath + "%(number)", 4, true); 600 } 601 Double maxNavPosObj = readMaxNavPos(target); 602 double maxNavpos = maxNavPosObj == null ? 0 : maxNavPosObj.doubleValue(); 603 boolean hasNavpos = maxNavPosObj != null; 604 rootCms.copyResource(page.getRootPath(), copyPath); 605 if (hasNavpos) { 606 String newNavPosStr = "" + (maxNavpos + 10); 607 rootCms.writePropertyObject( 608 copyPath, 609 new CmsProperty(CmsPropertyDefinition.PROPERTY_NAVPOS, newNavPosStr, null)); 610 } 611 CmsResource copiedPage = rootCms.readResource(copyPath); 612 m_originalPage = page; 613 m_targetFolder = target; 614 m_copiedFolderOrPage = copiedPage; 615 replaceElements(copiedPage); 616 attachLocaleGroups(copiedPage); 617 tryUnlock(copiedPage); 618 619 } 620 } 621 622 /** 623 * Sets the copy mode.<p> 624 * 625 * @param copyMode the copy mode 626 */ 627 public void setCopyMode(CopyMode copyMode) { 628 629 m_copyMode = copyMode; 630 } 631 632 /** 633 * Reads the max nav position from the contents of a folder.<p> 634 * 635 * @param target a folder 636 * @return the maximal NavPos from the contents of the folder, or null if no resources with a valid NavPos were found in the folder 637 * 638 * @throws CmsException if something goes wrong 639 */ 640 Double readMaxNavPos(CmsResource target) throws CmsException { 641 642 List<CmsResource> existingResourcesInFolder = m_cms.readResources( 643 target, 644 CmsResourceFilter.IGNORE_EXPIRATION, 645 false); 646 647 double maxNavpos = 0.0; 648 boolean hasNavpos = false; 649 for (CmsResource existingResource : existingResourcesInFolder) { 650 CmsProperty navpos = m_cms.readPropertyObject( 651 existingResource, 652 CmsPropertyDefinition.PROPERTY_NAVPOS, 653 false); 654 if (navpos.getValue() != null) { 655 try { 656 double navposNum = Double.parseDouble(navpos.getValue()); 657 hasNavpos = true; 658 maxNavpos = Math.max(navposNum, maxNavpos); 659 } catch (NumberFormatException e) { 660 // ignore 661 } 662 } 663 } 664 if (hasNavpos) { 665 return Double.valueOf(maxNavpos); 666 } else { 667 return null; 668 } 669 } 670 671 /** 672 * Attaches locale groups to the copied page. 673 * @param copiedPage the copied page. 674 * @throws CmsException thrown if the root cms cannot be retrieved. 675 */ 676 private void attachLocaleGroups(CmsResource copiedPage) throws CmsException { 677 678 CmsLocaleGroupService localeGroupService = getRootCms().getLocaleGroupService(); 679 if (Status.linkable == localeGroupService.checkLinkable(m_originalPage, copiedPage)) { 680 try { 681 localeGroupService.attachLocaleGroupIndirect(m_originalPage, copiedPage); 682 } catch (CmsException e) { 683 LOG.error(e.getLocalizedMessage(), e); 684 } 685 } 686 } 687 688 /** 689 * Return the cms object with the site root set to "/". 690 * @return the cms object with the site root set to "/". 691 * @throws CmsException thrown if initializing the root cms object fails. 692 */ 693 private CmsObject getRootCms() throws CmsException { 694 695 if (null == m_rootCms) { 696 m_rootCms = OpenCms.initCmsObject(m_cms); 697 m_rootCms.getRequestContext().setSiteRoot(""); 698 } 699 return m_rootCms; 700 } 701 702 /** 703 * Uses the custom translation table to translate formatter id.<p> 704 * 705 * @param formatterId the formatter id 706 * @return the formatter replacement 707 */ 708 private CmsUUID maybeReplaceFormatter(CmsUUID formatterId) { 709 710 if (m_customReplacements != null) { 711 CmsUUID replacement = m_customReplacements.get(formatterId); 712 if (replacement != null) { 713 return replacement; 714 } 715 } 716 return formatterId; 717 } 718 719 /** 720 * Replaces formatter id in element settings.<p> 721 * 722 * @param individualSettings the settings in which to replace the formatter id 723 * 724 * @return the map with the possible replaced ids 725 */ 726 private Map<String, String> maybeReplaceFormatterInSettings(Map<String, String> individualSettings) { 727 728 if (individualSettings == null) { 729 return null; 730 } else if (m_customReplacements == null) { 731 return individualSettings; 732 } else { 733 LinkedHashMap<String, String> result = new LinkedHashMap<String, String>(); 734 for (Map.Entry<String, String> entry : individualSettings.entrySet()) { 735 String value = entry.getValue(); 736 if (CmsUUID.isValidUUID(value)) { 737 CmsUUID valueId = new CmsUUID(value); 738 if (m_customReplacements.containsKey(valueId)) { 739 value = "" + m_customReplacements.get(valueId); 740 } 741 } 742 result.put(entry.getKey(), value); 743 } 744 return result; 745 } 746 } 747 748 /** 749 * Tries to unlock the given resource.<p> 750 * 751 * @param resource the resource to unlock 752 */ 753 private void tryUnlock(CmsResource resource) { 754 755 try { 756 m_cms.unlockResource(resource); 757 } catch (CmsException e) { 758 // usually not a problem 759 LOG.debug("failed to unlock " + resource.getRootPath(), e); 760 } 761 762 } 763 764}