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.ade.containerpage; 029 030import org.opencms.ade.configuration.CmsADEConfigData; 031import org.opencms.ade.configuration.CmsFormatterUtils; 032import org.opencms.ade.configuration.CmsResourceTypeConfig; 033import org.opencms.ade.containerpage.shared.CmsContainerElement; 034import org.opencms.ade.containerpage.shared.CmsContainerElement.ModelGroupState; 035import org.opencms.ade.containerpage.shared.CmsFormatterConfig; 036import org.opencms.file.CmsObject; 037import org.opencms.file.CmsProperty; 038import org.opencms.file.CmsPropertyDefinition; 039import org.opencms.file.CmsResource; 040import org.opencms.file.CmsResourceFilter; 041import org.opencms.file.CmsUser; 042import org.opencms.file.types.CmsResourceTypeXmlContainerPage; 043import org.opencms.file.types.I_CmsResourceType; 044import org.opencms.flex.CmsFlexController; 045import org.opencms.i18n.CmsEncoder; 046import org.opencms.lock.CmsLock; 047import org.opencms.main.CmsException; 048import org.opencms.main.CmsLog; 049import org.opencms.main.OpenCms; 050import org.opencms.util.CmsStringUtil; 051import org.opencms.util.CmsUUID; 052import org.opencms.xml.containerpage.CmsADESessionCache; 053import org.opencms.xml.containerpage.CmsContainerBean; 054import org.opencms.xml.containerpage.CmsContainerElementBean; 055import org.opencms.xml.containerpage.CmsContainerPageBean; 056import org.opencms.xml.containerpage.CmsXmlContainerPage; 057import org.opencms.xml.containerpage.CmsXmlContainerPageFactory; 058import org.opencms.xml.containerpage.I_CmsFormatterBean; 059 060import java.io.IOException; 061import java.util.ArrayList; 062import java.util.Collections; 063import java.util.HashMap; 064import java.util.HashSet; 065import java.util.List; 066import java.util.Locale; 067import java.util.Map; 068import java.util.Map.Entry; 069import java.util.Set; 070 071import javax.servlet.http.HttpServletRequest; 072import javax.servlet.http.HttpServletResponse; 073 074import org.apache.commons.logging.Log; 075 076/** 077 * Handles all model group specific tasks.<p> 078 */ 079public class CmsModelGroupHelper { 080 081 /** The name of the container storing the groups base element. */ 082 public static final String MODEL_GROUP_BASE_CONTAINER = "base_container"; 083 084 /** Static reference to the log. */ 085 private static final Log LOG = CmsLog.getLog(CmsModelGroupHelper.class); 086 087 /** Settings to keep when resetting. */ 088 private static final String[] KEEP_SETTING_IDS = new String[] { 089 CmsContainerElement.MODEL_GROUP_STATE, 090 CmsContainerElement.ELEMENT_INSTANCE_ID, 091 CmsContainerElement.USE_AS_COPY_MODEL}; 092 093 /** The current cms context. */ 094 private CmsObject m_cms; 095 096 /** The session cache instance. */ 097 private CmsADESessionCache m_sessionCache; 098 099 /** The configuration data of the current container page location. */ 100 private CmsADEConfigData m_configData; 101 102 /** Indicating the edit model groups mode. */ 103 private boolean m_isEditingModelGroups; 104 105 /** 106 * Constructor.<p> 107 * 108 * @param cms the current cms context 109 * @param configData the configuration data 110 * @param sessionCache the session cache 111 * @param isEditingModelGroups the edit model groups flag 112 */ 113 public CmsModelGroupHelper( 114 CmsObject cms, 115 CmsADEConfigData configData, 116 CmsADESessionCache sessionCache, 117 boolean isEditingModelGroups) { 118 119 m_cms = cms; 120 m_sessionCache = sessionCache; 121 m_configData = configData; 122 m_isEditingModelGroups = isEditingModelGroups; 123 } 124 125 /** 126 * Creates a new model group resource.<p> 127 * 128 * @param cms the current cms context 129 * @param configData the configuration data 130 * 131 * @return the new resource 132 * 133 * @throws CmsException in case creating the resource fails 134 */ 135 public static CmsResource createModelGroup(CmsObject cms, CmsADEConfigData configData) throws CmsException { 136 137 CmsResourceTypeConfig typeConfig = configData.getResourceType( 138 CmsResourceTypeXmlContainerPage.MODEL_GROUP_TYPE_NAME); 139 return typeConfig.createNewElement(cms, configData.getBasePath()); 140 } 141 142 /** 143 * Returns if the given resource is a model group resource.<p> 144 * 145 * @param resource the resource 146 * 147 * @return <code>true</code> if the given resource is a model group resource 148 */ 149 public static boolean isModelGroupResource(CmsResource resource) { 150 151 return CmsResourceTypeXmlContainerPage.MODEL_GROUP_TYPE_NAME.equals( 152 OpenCms.getResourceManager().getResourceType(resource).getTypeName()); 153 } 154 155 /** 156 * Updates a model group resource to the changed data structure.<p> 157 * This step is necessary when updating from version 10.0.x to 10.5.x.<p> 158 * 159 * @param cms the cms context 160 * @param group the model group resource 161 * @param baseContainerName the new base container name 162 * 163 * @return <code>true</code> if the resource was updated 164 */ 165 public static boolean updateModelGroupResource(CmsObject cms, CmsResource group, String baseContainerName) { 166 167 if (!isModelGroupResource(group)) { 168 // skip resources that are no model group 169 return false; 170 } 171 try { 172 CmsXmlContainerPage xmlContainerPage = CmsXmlContainerPageFactory.unmarshal(cms, group); 173 CmsContainerPageBean pageBean = xmlContainerPage.getContainerPage(cms); 174 175 CmsContainerBean baseContainer = pageBean.getContainers().get(MODEL_GROUP_BASE_CONTAINER); 176 boolean changedContent = false; 177 if ((baseContainer != null) && CmsStringUtil.isNotEmptyOrWhitespaceOnly(baseContainerName)) { 178 179 List<CmsContainerBean> containers = new ArrayList<CmsContainerBean>(); 180 for (CmsContainerBean container : pageBean.getContainers().values()) { 181 if (container.getName().equals(MODEL_GROUP_BASE_CONTAINER)) { 182 CmsContainerBean replacer = new CmsContainerBean( 183 baseContainerName, 184 container.getType(), 185 container.getParentInstanceId(), 186 container.isRootContainer(), 187 container.getElements()); 188 containers.add(replacer); 189 changedContent = true; 190 } else { 191 containers.add(container); 192 } 193 } 194 if (changedContent) { 195 pageBean = new CmsContainerPageBean(containers); 196 } 197 } 198 if (changedContent) { 199 ensureLock(cms, group); 200 201 if (changedContent) { 202 xmlContainerPage.save(cms, pageBean); 203 } 204 if (group.getName().endsWith(".xml")) { 205 // renaming model groups so they will be rendered correctly by the browser 206 String targetPath = cms.getSitePath(group); 207 targetPath = targetPath.substring(0, targetPath.length() - 4) + ".html"; 208 cms.renameResource(cms.getSitePath(group), targetPath); 209 group = cms.readResource(group.getStructureId()); 210 } 211 tryUnlock(cms, group); 212 return true; 213 } 214 return false; 215 216 } catch (CmsException e) { 217 LOG.error(e.getLocalizedMessage(), e); 218 return false; 219 } 220 221 } 222 223 /** 224 * Updates model group resources to the changed data structure.<p> 225 * This step is necessary when updating from version 10.0.x to 10.5.x.<p> 226 * 227 * @param request the request 228 * @param response the response 229 * @param basePath the path to the model group, or the base path to search for model groups 230 * @param baseContainerName the new base container name 231 * 232 * @throws IOException in case writing to the response fails 233 */ 234 @SuppressWarnings("resource") 235 public static void updateModelGroupResources( 236 HttpServletRequest request, 237 HttpServletResponse response, 238 String basePath, 239 String baseContainerName) 240 throws IOException { 241 242 if (CmsFlexController.isCmsRequest(request)) { 243 244 try { 245 CmsFlexController controller = CmsFlexController.getController(request); 246 CmsObject cms = controller.getCmsObject(); 247 CmsResource base = cms.readResource(basePath); 248 List<CmsResource> resources; 249 I_CmsResourceType groupType = OpenCms.getResourceManager().getResourceType( 250 CmsResourceTypeXmlContainerPage.MODEL_GROUP_TYPE_NAME); 251 if (base.isFolder()) { 252 resources = cms.readResources( 253 basePath, 254 CmsResourceFilter.ONLY_VISIBLE_NO_DELETED.addRequireType(groupType)); 255 } else if (OpenCms.getResourceManager().getResourceType(base).equals(groupType)) { 256 resources = Collections.singletonList(base); 257 } else { 258 resources = Collections.emptyList(); 259 } 260 261 if (resources.isEmpty()) { 262 response.getWriter().println("No model group resources found at " + CmsEncoder.escapeXml(basePath) + "<br />"); 263 } else { 264 for (CmsResource group : resources) { 265 boolean updated = updateModelGroupResource(cms, group, baseContainerName); 266 response.getWriter().println( 267 "Group '" + group.getRootPath() + "' was updated " + updated + "<br />"); 268 } 269 } 270 } catch (CmsException e) { 271 LOG.error(e.getLocalizedMessage(), e); 272 e.printStackTrace(response.getWriter()); 273 } 274 } 275 } 276 277 /** 278 * Locks the given resource.<p> 279 * 280 * @param cms the cms context 281 * @param resource the resource to lock 282 * 283 * @throws CmsException in case locking fails 284 */ 285 private static void ensureLock(CmsObject cms, CmsResource resource) throws CmsException { 286 287 CmsUser user = cms.getRequestContext().getCurrentUser(); 288 CmsLock lock = cms.getLock(resource); 289 if (!lock.isOwnedBy(user)) { 290 cms.lockResourceTemporary(resource); 291 } else if (!lock.isOwnedInProjectBy(user, cms.getRequestContext().getCurrentProject())) { 292 cms.changeLock(resource); 293 } 294 } 295 296 /** 297 * Tries to unlock a resource.<p> 298 * 299 * @param cms the cms context 300 * @param resource the resource to unlock 301 */ 302 private static void tryUnlock(CmsObject cms, CmsResource resource) { 303 304 try { 305 cms.unlockResource(resource); 306 } catch (CmsException e) { 307 LOG.debug("Unable to unlock " + resource.getRootPath(), e); 308 } 309 } 310 311 /** 312 * Adds the model group elements to the page.<p> 313 * 314 * @param elements the requested elements 315 * @param foundGroups list to add the found group element client ids to 316 * @param page the page 317 * @param alwaysCopy <code>true</code> to create element copies in case of non model groups and createNew is set 318 * @param locale the content locale 319 * @param createContextPath the context path to pass to CmsResourceTypeConfig#createNewElement 320 * 321 * @return the adjusted page 322 * 323 * @throws CmsException in case something goes wrong 324 */ 325 public CmsContainerPageBean prepareforModelGroupContent( 326 Map<String, CmsContainerElementBean> elements, 327 List<String> foundGroups, 328 CmsContainerPageBean page, 329 boolean alwaysCopy, 330 Locale locale, 331 String createContextPath) 332 throws CmsException { 333 334 for (Entry<String, CmsContainerElementBean> entry : elements.entrySet()) { 335 CmsContainerElementBean element = entry.getValue(); 336 CmsContainerPageBean modelPage = null; 337 String modelInstanceId = null; 338 boolean foundInstance = false; 339 if (CmsModelGroupHelper.isModelGroupResource(element.getResource())) { 340 modelPage = getContainerPageBean(element.getResource()); 341 CmsContainerElementBean baseElement = getModelBaseElement(modelPage, element.getResource()); 342 if (baseElement == null) { 343 break; 344 } 345 String baseInstanceId = baseElement.getInstanceId(); 346 String originalInstanceId = element.getInstanceId(); 347 element = getModelReplacementElement(element, baseElement, true); 348 List<CmsContainerBean> modelContainers = readModelContainers( 349 baseInstanceId, 350 originalInstanceId, 351 modelPage, 352 baseElement.isCopyModel()); 353 if (!m_isEditingModelGroups && baseElement.isCopyModel()) { 354 modelContainers = createNewElementsForModelGroup(m_cms, modelContainers, locale, createContextPath); 355 } 356 modelContainers.addAll(page.getContainers().values()); 357 page = new CmsContainerPageBean(modelContainers); 358 // update the entry element value, as the settings will have changed 359 entry.setValue(element); 360 if (m_sessionCache != null) { 361 // also update the session cache 362 m_sessionCache.setCacheContainerElement(element.editorHash(), element); 363 } 364 } else { 365 // here we need to make sure to remove the source container page setting and to set a new element instance id 366 367 Map<String, String> settings = new HashMap<String, String>(element.getIndividualSettings()); 368 String source = settings.get(CmsContainerpageService.SOURCE_CONTAINERPAGE_ID_SETTING); 369 settings.remove(CmsContainerpageService.SOURCE_CONTAINERPAGE_ID_SETTING); 370 // TODO: Make sure source id is available for second call 371 372 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(source)) { 373 try { 374 CmsUUID sourceId = new CmsUUID(source); 375 CmsResource sourcePage = m_cms.readResource(sourceId); 376 if (CmsResourceTypeXmlContainerPage.isContainerPage(sourcePage)) { 377 CmsXmlContainerPage xmlCnt = CmsXmlContainerPageFactory.unmarshal( 378 m_cms, 379 m_cms.readFile(sourcePage)); 380 modelPage = xmlCnt.getContainerPage(m_cms); 381 modelInstanceId = element.getInstanceId(); 382 } 383 384 settings.remove(CmsContainerElement.ELEMENT_INSTANCE_ID); 385 386 boolean copyRoot = false; 387 if (alwaysCopy && (modelInstanceId != null) && (modelPage != null)) { 388 for (CmsContainerElementBean el : modelPage.getElements()) { 389 if (modelInstanceId.equals(el.getInstanceId())) { 390 copyRoot = el.isCreateNew(); 391 break; 392 } 393 } 394 } 395 396 if (copyRoot) { 397 CmsObject cloneCms = OpenCms.initCmsObject(m_cms); 398 cloneCms.getRequestContext().setLocale(locale); 399 String typeName = OpenCms.getResourceManager().getResourceType( 400 element.getResource()).getTypeName(); 401 CmsResourceTypeConfig typeConfig = m_configData.getResourceType(typeName); 402 if (typeConfig == null) { 403 throw new IllegalArgumentException( 404 "Can not copy template model element '" 405 + element.getResource().getRootPath() 406 + "' because the resource type '" 407 + typeName 408 + "' is not available in this sitemap."); 409 } 410 CmsResource newResource = typeConfig.createNewElement( 411 cloneCms, 412 element.getResource(), 413 createContextPath); 414 415 element = new CmsContainerElementBean( 416 newResource.getStructureId(), 417 element.getFormatterId(), 418 settings, 419 false); 420 } else { 421 element = CmsContainerElementBean.cloneWithSettings(element, settings); 422 } 423 if (modelPage != null) { 424 Map<String, List<CmsContainerBean>> containerByParent = new HashMap<String, List<CmsContainerBean>>(); 425 426 for (CmsContainerBean container : modelPage.getContainers().values()) { 427 if (container.getParentInstanceId() != null) { 428 if (!containerByParent.containsKey(container.getParentInstanceId())) { 429 containerByParent.put( 430 container.getParentInstanceId(), 431 new ArrayList<CmsContainerBean>()); 432 } 433 containerByParent.get(container.getParentInstanceId()).add(container); 434 } 435 if (!foundInstance) { 436 for (CmsContainerElementBean child : container.getElements()) { 437 if (modelInstanceId == null) { 438 if (child.getId().equals(element.getId())) { 439 modelInstanceId = child.getInstanceId(); 440 foundInstance = true; 441 // we also want to keep the settings of the model group 442 Map<String, String> setting = new HashMap<String, String>( 443 child.getIndividualSettings()); 444 setting.remove(CmsContainerElement.ELEMENT_INSTANCE_ID); 445 element = CmsContainerElementBean.cloneWithSettings(element, setting); 446 break; 447 } 448 } else { 449 if (modelInstanceId.equals(child.getInstanceId())) { 450 foundInstance = true; 451 break; 452 } 453 } 454 } 455 } 456 } 457 if (foundInstance && containerByParent.containsKey(modelInstanceId)) { 458 List<CmsContainerBean> modelContainers = collectModelStructure( 459 modelInstanceId, 460 element.getInstanceId(), 461 containerByParent, 462 true); 463 if (alwaysCopy) { 464 modelContainers = createNewElementsForModelGroup( 465 m_cms, 466 modelContainers, 467 locale, 468 createContextPath); 469 } 470 foundGroups.add(element.editorHash()); 471 modelContainers.addAll(page.getContainers().values()); 472 page = new CmsContainerPageBean(modelContainers); 473 } 474 } 475 476 // update the entry element value, as the settings will have changed 477 entry.setValue(element); 478 if (m_sessionCache != null) { 479 // also update the session cache 480 m_sessionCache.setCacheContainerElement(element.editorHash(), element); 481 } 482 } catch (Exception e) { 483 LOG.info(e.getLocalizedMessage(), e); 484 } 485 486 } 487 } 488 489 } 490 return page; 491 } 492 493 /** 494 * Reads the present model groups and merges their containers into the page.<p> 495 * 496 * @param page the container page 497 * 498 * @return the resulting container page 499 */ 500 public CmsContainerPageBean readModelGroups(CmsContainerPageBean page) { 501 502 List<CmsContainerBean> resultContainers = new ArrayList<CmsContainerBean>(); 503 for (CmsContainerBean container : page.getContainers().values()) { 504 boolean hasModels = false; 505 List<CmsContainerElementBean> elements = new ArrayList<CmsContainerElementBean>(); 506 for (CmsContainerElementBean element : container.getElements()) { 507 try { 508 element.initResource(m_cms); 509 if (isModelGroupResource(element.getResource())) { 510 hasModels = true; 511 CmsContainerPageBean modelGroupPage = getContainerPageBean(element.getResource()); 512 CmsContainerElementBean baseElement = getModelBaseElement( 513 modelGroupPage, 514 element.getResource()); 515 if (baseElement == null) { 516 LOG.error( 517 "Error rendering model group '" 518 + element.getResource().getRootPath() 519 + "', base element could not be determind."); 520 continue; 521 } 522 String baseInstanceId = baseElement.getInstanceId(); 523 CmsContainerElementBean replaceElement = getModelReplacementElement( 524 element, 525 baseElement, 526 false); 527 if (m_sessionCache != null) { 528 m_sessionCache.setCacheContainerElement(replaceElement.editorHash(), replaceElement); 529 } 530 elements.add(replaceElement); 531 resultContainers.addAll( 532 readModelContainers( 533 baseInstanceId, 534 element.getInstanceId(), 535 modelGroupPage, 536 baseElement.isCopyModel())); 537 } else { 538 elements.add(element); 539 } 540 } catch (CmsException e) { 541 LOG.info(e.getLocalizedMessage(), e); 542 } 543 } 544 if (hasModels) { 545 resultContainers.add(container.copyWithNewElements(elements)); 546 } else { 547 resultContainers.add(container); 548 } 549 } 550 return new CmsContainerPageBean(resultContainers); 551 } 552 553 /** 554 * Removes the model group containers.<p> 555 * 556 * @param page the container page state 557 * 558 * @return the container page without the model group containers 559 */ 560 public CmsContainerPageBean removeModelGroupContainers(CmsContainerPageBean page) { 561 562 Map<String, List<CmsContainerBean>> containersByParent = getContainerByParent(page); 563 Set<String> modelInstances = new HashSet<String>(); 564 for (CmsContainerElementBean element : page.getElements()) { 565 if (element.getIndividualSettings().containsKey(CmsContainerElement.MODEL_GROUP_ID)) { 566 modelInstances.add(element.getInstanceId()); 567 } 568 } 569 570 Set<String> descendingInstances = new HashSet<String>(); 571 for (String modelInstance : modelInstances) { 572 descendingInstances.addAll(collectDescendingInstances(modelInstance, containersByParent)); 573 } 574 List<CmsContainerBean> containers = new ArrayList<CmsContainerBean>(); 575 for (CmsContainerBean container : page.getContainers().values()) { 576 if ((container.getParentInstanceId() == null) 577 || !descendingInstances.contains(container.getParentInstanceId())) { 578 // iterate the container elements to replace the model group elements 579 List<CmsContainerElementBean> elements = new ArrayList<CmsContainerElementBean>(); 580 for (CmsContainerElementBean element : container.getElements()) { 581 if (modelInstances.contains(element.getInstanceId())) { 582 CmsUUID modelId = new CmsUUID( 583 element.getIndividualSettings().get(CmsContainerElement.MODEL_GROUP_ID)); 584 CmsContainerElementBean replacer = new CmsContainerElementBean( 585 modelId, 586 element.getFormatterId(), 587 element.getIndividualSettings(), 588 false); 589 elements.add(replacer); 590 } else { 591 elements.add(element); 592 } 593 } 594 containers.add(container.copyWithNewElements(elements)); 595 } 596 } 597 return new CmsContainerPageBean(containers); 598 } 599 600 /** 601 * Saves the model groups of the given container page.<p> 602 * 603 * @param page the container page 604 * @param pageResource the model group resource 605 * 606 * @return the container page referencing the saved model groups 607 * 608 * @throws CmsException in case writing the page properties fails 609 */ 610 public CmsContainerPageBean saveModelGroups(CmsContainerPageBean page, CmsResource pageResource) 611 throws CmsException { 612 613 CmsUUID modelElementId = null; 614 CmsContainerElementBean baseElement = null; 615 for (CmsContainerElementBean element : page.getElements()) { 616 if (element.isModelGroup()) { 617 modelElementId = element.getId(); 618 baseElement = element; 619 break; 620 621 } 622 } 623 List<CmsContainerBean> containers = new ArrayList<CmsContainerBean>(); 624 for (CmsContainerBean container : page.getContainers().values()) { 625 List<CmsContainerElementBean> elements = new ArrayList<CmsContainerElementBean>(); 626 boolean hasChanges = false; 627 for (CmsContainerElementBean element : container.getElements()) { 628 if (element.isModelGroup() && !element.getId().equals(modelElementId)) { 629 // there should not be another model group element, remove the model group settings 630 Map<String, String> settings = new HashMap<String, String>(element.getIndividualSettings()); 631 settings.remove(CmsContainerElement.MODEL_GROUP_ID); 632 settings.remove(CmsContainerElement.MODEL_GROUP_STATE); 633 elements.add( 634 new CmsContainerElementBean(element.getId(), element.getFormatterId(), settings, false)); 635 hasChanges = true; 636 } else { 637 elements.add(element); 638 } 639 } 640 if (hasChanges) { 641 containers.add(container.copyWithNewElements(elements)); 642 } else { 643 containers.add(container); 644 } 645 646 } 647 648 List<CmsProperty> changedProps = new ArrayList<CmsProperty>(); 649 if (baseElement != null) { 650 String val = Boolean.parseBoolean( 651 baseElement.getIndividualSettings().get(CmsContainerElement.USE_AS_COPY_MODEL)) 652 ? CmsContainerElement.USE_AS_COPY_MODEL 653 : ""; 654 changedProps.add(new CmsProperty(CmsPropertyDefinition.PROPERTY_TEMPLATE_ELEMENTS, val, val)); 655 } 656 m_cms.writePropertyObjects(pageResource, changedProps); 657 658 return new CmsContainerPageBean(containers); 659 660 } 661 662 /** 663 * Adjusts formatter settings and initializes a new instance id for the given container element.<p> 664 * 665 * @param element the container element 666 * @param originalContainer the original parent container name 667 * @param adjustedContainer the target container name 668 * @param setNewInstanceId <code>true</code> to set a new instance id 669 * 670 * @return the new element instance 671 */ 672 private CmsContainerElementBean adjustSettings( 673 CmsContainerElementBean element, 674 String originalContainer, 675 String adjustedContainer, 676 boolean setNewInstanceId) { 677 678 Map<String, String> settings = new HashMap<String, String>(element.getIndividualSettings()); 679 if (setNewInstanceId) { 680 settings.put(CmsContainerElement.ELEMENT_INSTANCE_ID, new CmsUUID().toString()); 681 } 682 String formatterId = settings.remove(CmsFormatterConfig.getSettingsKeyForContainer(originalContainer)); 683 settings.put(CmsFormatterConfig.getSettingsKeyForContainer(adjustedContainer), formatterId); 684 return CmsContainerElementBean.cloneWithSettings(element, settings); 685 } 686 687 /** 688 * Returns the descending instance id's to the given element instance.<p> 689 * 690 * @param instanceId the instance id 691 * @param containersByParent the container page containers by parent instance id 692 * 693 * @return the containers 694 */ 695 private Set<String> collectDescendingInstances( 696 String instanceId, 697 Map<String, List<CmsContainerBean>> containersByParent) { 698 699 Set<String> descendingInstances = new HashSet<String>(); 700 descendingInstances.add(instanceId); 701 if (containersByParent.containsKey(instanceId)) { 702 for (CmsContainerBean container : containersByParent.get(instanceId)) { 703 for (CmsContainerElementBean element : container.getElements()) { 704 descendingInstances.addAll(collectDescendingInstances(element.getInstanceId(), containersByParent)); 705 } 706 } 707 } 708 return descendingInstances; 709 } 710 711 /** 712 * Collects the model group structure.<p> 713 * 714 * @param modelInstanceId the model instance id 715 * @param replaceModelId the local instance id 716 * @param containerByParent the model group page containers by parent instance id 717 * @param isCopyGroup <code>true</code> in case of a copy group 718 * 719 * @return the collected containers 720 */ 721 private List<CmsContainerBean> collectModelStructure( 722 String modelInstanceId, 723 String replaceModelId, 724 Map<String, List<CmsContainerBean>> containerByParent, 725 boolean isCopyGroup) { 726 727 List<CmsContainerBean> result = new ArrayList<CmsContainerBean>(); 728 729 if (containerByParent.containsKey(modelInstanceId)) { 730 for (CmsContainerBean container : containerByParent.get(modelInstanceId)) { 731 String adjustedContainerName = replaceModelId + container.getName().substring(modelInstanceId.length()); 732 733 List<CmsContainerElementBean> elements = new ArrayList<CmsContainerElementBean>(); 734 for (CmsContainerElementBean element : container.getElements()) { 735 CmsContainerElementBean copyElement = adjustSettings( 736 element, 737 container.getName(), 738 adjustedContainerName, 739 isCopyGroup); 740 if (m_sessionCache != null) { 741 m_sessionCache.setCacheContainerElement(copyElement.editorHash(), copyElement); 742 } 743 elements.add(copyElement); 744 result.addAll( 745 collectModelStructure( 746 element.getInstanceId(), 747 copyElement.getInstanceId(), 748 containerByParent, 749 isCopyGroup)); 750 } 751 752 result.add( 753 new CmsContainerBean( 754 adjustedContainerName, 755 container.getType(), 756 replaceModelId, 757 container.isRootContainer(), 758 container.getMaxElements(), 759 elements)); 760 } 761 } 762 return result; 763 } 764 765 /** 766 * Creates new resources for elements marked with create as new.<p> 767 * 768 * @param cms the cms context 769 * @param modelContainers the model containers 770 * @param locale the content locale 771 * @param createContext the context path to pass to CmsResourceTypeConfig#createNewElement 772 * 773 * @return the updated model containers 774 * 775 * @throws CmsException in case something goes wrong 776 */ 777 private List<CmsContainerBean> createNewElementsForModelGroup( 778 CmsObject cms, 779 List<CmsContainerBean> modelContainers, 780 Locale locale, 781 String createContext) 782 throws CmsException { 783 784 Map<CmsUUID, CmsResource> newResources = new HashMap<CmsUUID, CmsResource>(); 785 CmsObject cloneCms = OpenCms.initCmsObject(cms); 786 cloneCms.getRequestContext().setLocale(locale); 787 for (CmsContainerBean container : modelContainers) { 788 for (CmsContainerElementBean element : container.getElements()) { 789 if (element.isCreateNew() && !newResources.containsKey(element.getId())) { 790 element.initResource(cms); 791 String typeName = OpenCms.getResourceManager().getResourceType(element.getResource()).getTypeName(); 792 CmsResourceTypeConfig typeConfig = m_configData.getResourceType(typeName); 793 if (typeConfig == null) { 794 throw new IllegalArgumentException( 795 "Can not copy template model element '" 796 + element.getResource().getRootPath() 797 + "' because the resource type '" 798 + typeName 799 + "' is not available in this sitemap."); 800 } 801 CmsResource newResource = typeConfig.createNewElement( 802 cloneCms, 803 element.getResource(), 804 createContext); 805 newResources.put(element.getId(), newResource); 806 } 807 } 808 } 809 if (!newResources.isEmpty()) { 810 List<CmsContainerBean> updatedContainers = new ArrayList<CmsContainerBean>(); 811 for (CmsContainerBean container : modelContainers) { 812 List<CmsContainerElementBean> updatedElements = new ArrayList<CmsContainerElementBean>(); 813 for (CmsContainerElementBean element : container.getElements()) { 814 if (newResources.containsKey(element.getId())) { 815 CmsContainerElementBean newBean = new CmsContainerElementBean( 816 newResources.get(element.getId()).getStructureId(), 817 element.getFormatterId(), 818 element.getIndividualSettings(), 819 false); 820 updatedElements.add(newBean); 821 } else { 822 updatedElements.add(element); 823 } 824 } 825 CmsContainerBean updatedContainer = container.copyWithNewElements(updatedElements); 826 updatedContainers.add(updatedContainer); 827 } 828 modelContainers = updatedContainers; 829 } 830 return modelContainers; 831 } 832 833 /** 834 * Collects the page containers by parent instance id.<p> 835 * 836 * @param page the page 837 * 838 * @return the containers by parent id 839 */ 840 private Map<String, List<CmsContainerBean>> getContainerByParent(CmsContainerPageBean page) { 841 842 Map<String, List<CmsContainerBean>> containerByParent = new HashMap<String, List<CmsContainerBean>>(); 843 844 for (CmsContainerBean container : page.getContainers().values()) { 845 if (container.getParentInstanceId() != null) { 846 if (!containerByParent.containsKey(container.getParentInstanceId())) { 847 containerByParent.put(container.getParentInstanceId(), new ArrayList<CmsContainerBean>()); 848 } 849 containerByParent.get(container.getParentInstanceId()).add(container); 850 } 851 } 852 return containerByParent; 853 } 854 855 /** 856 * Unmarshals the given resource.<p> 857 * 858 * @param resource the resource 859 * 860 * @return the container page bean 861 * 862 * @throws CmsException in case unmarshalling fails 863 */ 864 private CmsContainerPageBean getContainerPageBean(CmsResource resource) throws CmsException { 865 866 CmsXmlContainerPage xmlCnt = CmsXmlContainerPageFactory.unmarshal(m_cms, m_cms.readFile(resource)); 867 return xmlCnt.getContainerPage(m_cms); 868 } 869 870 /** 871 * Returns the model group base element.<p> 872 * 873 * @param modelGroupPage the model group page 874 * @param modelGroupResource the model group resource 875 * 876 * @return the base element 877 */ 878 private CmsContainerElementBean getModelBaseElement( 879 CmsContainerPageBean modelGroupPage, 880 CmsResource modelGroupResource) { 881 882 CmsContainerElementBean result = null; 883 for (CmsContainerElementBean element : modelGroupPage.getElements()) { 884 if (CmsContainerElement.ModelGroupState.isModelGroup.name().equals( 885 element.getIndividualSettings().get(CmsContainerElement.MODEL_GROUP_STATE))) { 886 result = element; 887 break; 888 } 889 } 890 return result; 891 } 892 893 /** 894 * Returns the the element to be rendered as the model group base.<p> 895 * 896 * @param element the original element 897 * @param baseElement the model group base 898 * @param allowCopyModel if copy models are allowed 899 * 900 * @return the element 901 */ 902 private CmsContainerElementBean getModelReplacementElement( 903 CmsContainerElementBean element, 904 CmsContainerElementBean baseElement, 905 boolean allowCopyModel) { 906 907 boolean resetSettings = false; 908 if (!baseElement.isCopyModel() && hasIncompatibleFormatters(baseElement, element)) { 909 I_CmsFormatterBean formatter = m_configData.findFormatter(element.getFormatterId()); 910 resetSettings = (formatter == null) 911 || !formatter.getResourceTypeNames().contains( 912 OpenCms.getResourceManager().getResourceType(baseElement.getResource()).getTypeName()); 913 } 914 Map<String, String> settings; 915 if (resetSettings) { 916 settings = new HashMap<String, String>(); 917 for (String id : KEEP_SETTING_IDS) { 918 if (element.getIndividualSettings().containsKey(id)) { 919 settings.put(id, element.getIndividualSettings().get(id)); 920 } 921 } 922 settings.put(CmsContainerElement.MODEL_GROUP_ID, element.getId().toString()); 923 // transfer all other settings 924 for (Entry<String, String> settingEntry : baseElement.getIndividualSettings().entrySet()) { 925 if (!settings.containsKey(settingEntry.getKey())) { 926 settings.put(settingEntry.getKey(), settingEntry.getValue()); 927 } 928 } 929 } else { 930 settings = new HashMap<String, String>(element.getIndividualSettings()); 931 if (!(baseElement.isCopyModel() && allowCopyModel)) { 932 // skip the model id in case of copy models 933 settings.put(CmsContainerElement.MODEL_GROUP_ID, element.getId().toString()); 934 if (allowCopyModel) { 935 // transfer all other settings 936 for (Entry<String, String> settingEntry : baseElement.getIndividualSettings().entrySet()) { 937 if (!settings.containsKey(settingEntry.getKey())) { 938 settings.put(settingEntry.getKey(), settingEntry.getValue()); 939 } 940 } 941 } 942 943 } else if (baseElement.isCopyModel()) { 944 settings.put(CmsContainerElement.MODEL_GROUP_STATE, ModelGroupState.wasModelGroup.name()); 945 } 946 } 947 return CmsContainerElementBean.cloneWithSettings(baseElement, settings); 948 } 949 950 private boolean hasIncompatibleFormatters(CmsContainerElementBean baseElement, CmsContainerElementBean element) { 951 952 if ((baseElement == null) || (element == null)) { 953 return false; 954 } 955 if ((baseElement.getFormatterId() != null) 956 && (element.getFormatterId() != null) 957 && !baseElement.getFormatterId().equals(element.getFormatterId())) { 958 return true; 959 } 960 Set<String> baseFormatterKeys = CmsFormatterUtils.getAllFormatterKeys(m_configData, baseElement); 961 Set<String> elementFormatterKeys = CmsFormatterUtils.getAllFormatterKeys(m_configData, element); 962 boolean hasCommonKeys = baseFormatterKeys.stream().anyMatch(elementFormatterKeys::contains); 963 return !hasCommonKeys; 964 } 965 966 /** 967 * Returns the model containers.<p> 968 * 969 * @param modelInstanceId the model instance id 970 * @param localInstanceId the local instance id 971 * @param modelPage the model page bean 972 * @param isCopyGroup <code>true</code> in case of a copy group 973 * 974 * @return the model group containers 975 */ 976 private List<CmsContainerBean> readModelContainers( 977 String modelInstanceId, 978 String localInstanceId, 979 CmsContainerPageBean modelPage, 980 boolean isCopyGroup) { 981 982 Map<String, List<CmsContainerBean>> containerByParent = getContainerByParent(modelPage); 983 List<CmsContainerBean> modelContainers; 984 if (containerByParent.containsKey(modelInstanceId)) { 985 modelContainers = collectModelStructure(modelInstanceId, localInstanceId, containerByParent, isCopyGroup); 986 } else { 987 modelContainers = new ArrayList<CmsContainerBean>(); 988 } 989 return modelContainers; 990 } 991}