001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (c) Alkacon Software GmbH & Co. KG (https://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: https://www.alkacon.com 019 * 020 * For further information about OpenCms, please see the 021 * project website: https://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 org.apache.commons.logging.Log; 072 073import jakarta.servlet.http.HttpServletRequest; 074import jakarta.servlet.http.HttpServletResponse; 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( 263 "No model group resources found at " + CmsEncoder.escapeXml(basePath) + "<br />"); 264 } else { 265 for (CmsResource group : resources) { 266 boolean updated = updateModelGroupResource(cms, group, baseContainerName); 267 response.getWriter().println( 268 "Group '" + group.getRootPath() + "' was updated " + updated + "<br />"); 269 } 270 } 271 } catch (CmsException e) { 272 LOG.error(e.getLocalizedMessage(), e); 273 e.printStackTrace(response.getWriter()); 274 } 275 } 276 } 277 278 /** 279 * Locks the given resource.<p> 280 * 281 * @param cms the cms context 282 * @param resource the resource to lock 283 * 284 * @throws CmsException in case locking fails 285 */ 286 private static void ensureLock(CmsObject cms, CmsResource resource) throws CmsException { 287 288 CmsUser user = cms.getRequestContext().getCurrentUser(); 289 CmsLock lock = cms.getLock(resource); 290 if (!lock.isOwnedBy(user)) { 291 cms.lockResourceTemporary(resource); 292 } else if (!lock.isOwnedInProjectBy(user, cms.getRequestContext().getCurrentProject())) { 293 cms.changeLock(resource); 294 } 295 } 296 297 /** 298 * Tries to unlock a resource.<p> 299 * 300 * @param cms the cms context 301 * @param resource the resource to unlock 302 */ 303 private static void tryUnlock(CmsObject cms, CmsResource resource) { 304 305 try { 306 cms.unlockResource(resource); 307 } catch (CmsException e) { 308 LOG.debug("Unable to unlock " + resource.getRootPath(), e); 309 } 310 } 311 312 /** 313 * Adds the model group elements to the page.<p> 314 * 315 * @param elements the requested elements 316 * @param foundGroups list to add the found group element client ids to 317 * @param page the page 318 * @param alwaysCopy <code>true</code> to create element copies in case of non model groups and createNew is set 319 * @param locale the content locale 320 * @param createContextPath the context path to pass to CmsResourceTypeConfig#createNewElement 321 * 322 * @return the adjusted page 323 * 324 * @throws CmsException in case something goes wrong 325 */ 326 public CmsContainerPageBean prepareforModelGroupContent( 327 Map<String, CmsContainerElementBean> elements, 328 List<String> foundGroups, 329 CmsContainerPageBean page, 330 boolean alwaysCopy, 331 Locale locale, 332 String createContextPath) 333 throws CmsException { 334 335 for (Entry<String, CmsContainerElementBean> entry : elements.entrySet()) { 336 CmsContainerElementBean element = entry.getValue(); 337 CmsContainerPageBean modelPage = null; 338 String modelInstanceId = null; 339 boolean foundInstance = false; 340 if (CmsModelGroupHelper.isModelGroupResource(element.getResource())) { 341 modelPage = getContainerPageBean(element.getResource()); 342 CmsContainerElementBean baseElement = getModelBaseElement(modelPage, element.getResource()); 343 if (baseElement == null) { 344 break; 345 } 346 String baseInstanceId = baseElement.getInstanceId(); 347 String originalInstanceId = element.getInstanceId(); 348 element = getModelReplacementElement(element, baseElement, true); 349 List<CmsContainerBean> modelContainers = readModelContainers( 350 baseInstanceId, 351 originalInstanceId, 352 modelPage, 353 baseElement.isCopyModel()); 354 if (!m_isEditingModelGroups && baseElement.isCopyModel()) { 355 modelContainers = createNewElementsForModelGroup(m_cms, modelContainers, locale, createContextPath); 356 } 357 modelContainers.addAll(page.getContainers().values()); 358 page = new CmsContainerPageBean(modelContainers); 359 // update the entry element value, as the settings will have changed 360 entry.setValue(element); 361 if (m_sessionCache != null) { 362 // also update the session cache 363 m_sessionCache.setCacheContainerElement(element.editorHash(), element); 364 } 365 } else { 366 // here we need to make sure to remove the source container page setting and to set a new element instance id 367 368 Map<String, String> settings = new HashMap<String, String>(element.getIndividualSettings()); 369 String source = settings.get(CmsContainerpageService.SOURCE_CONTAINERPAGE_ID_SETTING); 370 settings.remove(CmsContainerpageService.SOURCE_CONTAINERPAGE_ID_SETTING); 371 // TODO: Make sure source id is available for second call 372 373 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(source)) { 374 try { 375 CmsUUID sourceId = new CmsUUID(source); 376 CmsResource sourcePage = m_cms.readResource(sourceId); 377 if (CmsResourceTypeXmlContainerPage.isContainerPage(sourcePage)) { 378 CmsXmlContainerPage xmlCnt = CmsXmlContainerPageFactory.unmarshal( 379 m_cms, 380 m_cms.readFile(sourcePage)); 381 modelPage = xmlCnt.getContainerPage(m_cms); 382 modelInstanceId = element.getInstanceId(); 383 } 384 385 settings.remove(CmsContainerElement.ELEMENT_INSTANCE_ID); 386 387 boolean copyRoot = false; 388 if (alwaysCopy && (modelInstanceId != null) && (modelPage != null)) { 389 for (CmsContainerElementBean el : modelPage.getElements()) { 390 if (modelInstanceId.equals(el.getInstanceId())) { 391 copyRoot = el.isCreateNew(); 392 break; 393 } 394 } 395 } 396 397 if (copyRoot) { 398 CmsObject cloneCms = OpenCms.initCmsObject(m_cms); 399 cloneCms.getRequestContext().setLocale(locale); 400 String typeName = OpenCms.getResourceManager().getResourceType( 401 element.getResource()).getTypeName(); 402 CmsResourceTypeConfig typeConfig = m_configData.getResourceType(typeName); 403 if (typeConfig == null) { 404 throw new IllegalArgumentException( 405 "Can not copy template model element '" 406 + element.getResource().getRootPath() 407 + "' because the resource type '" 408 + typeName 409 + "' is not available in this sitemap."); 410 } 411 CmsResource newResource = typeConfig.createNewElement( 412 cloneCms, 413 element.getResource(), 414 createContextPath); 415 416 element = new CmsContainerElementBean( 417 newResource.getStructureId(), 418 element.getFormatterId(), 419 settings, 420 false); 421 } else { 422 element = CmsContainerElementBean.cloneWithSettings(element, settings); 423 } 424 if (modelPage != null) { 425 Map<String, List<CmsContainerBean>> containerByParent = new HashMap<String, List<CmsContainerBean>>(); 426 427 for (CmsContainerBean container : modelPage.getContainers().values()) { 428 if (container.getParentInstanceId() != null) { 429 if (!containerByParent.containsKey(container.getParentInstanceId())) { 430 containerByParent.put( 431 container.getParentInstanceId(), 432 new ArrayList<CmsContainerBean>()); 433 } 434 containerByParent.get(container.getParentInstanceId()).add(container); 435 } 436 if (!foundInstance) { 437 for (CmsContainerElementBean child : container.getElements()) { 438 if (modelInstanceId == null) { 439 if (child.getId().equals(element.getId())) { 440 modelInstanceId = child.getInstanceId(); 441 foundInstance = true; 442 // we also want to keep the settings of the model group 443 Map<String, String> setting = new HashMap<String, String>( 444 child.getIndividualSettings()); 445 setting.remove(CmsContainerElement.ELEMENT_INSTANCE_ID); 446 element = CmsContainerElementBean.cloneWithSettings(element, setting); 447 break; 448 } 449 } else { 450 if (modelInstanceId.equals(child.getInstanceId())) { 451 foundInstance = true; 452 break; 453 } 454 } 455 } 456 } 457 } 458 if (foundInstance && containerByParent.containsKey(modelInstanceId)) { 459 List<CmsContainerBean> modelContainers = collectModelStructure( 460 modelInstanceId, 461 element.getInstanceId(), 462 containerByParent, 463 true); 464 if (alwaysCopy) { 465 modelContainers = createNewElementsForModelGroup( 466 m_cms, 467 modelContainers, 468 locale, 469 createContextPath); 470 } 471 foundGroups.add(element.editorHash()); 472 modelContainers.addAll(page.getContainers().values()); 473 page = new CmsContainerPageBean(modelContainers); 474 } 475 } 476 477 // update the entry element value, as the settings will have changed 478 entry.setValue(element); 479 if (m_sessionCache != null) { 480 // also update the session cache 481 m_sessionCache.setCacheContainerElement(element.editorHash(), element); 482 } 483 } catch (Exception e) { 484 LOG.info(e.getLocalizedMessage(), e); 485 } 486 487 } 488 } 489 490 } 491 return page; 492 } 493 494 /** 495 * Reads the present model groups and merges their containers into the page.<p> 496 * 497 * @param page the container page 498 * 499 * @return the resulting container page 500 */ 501 public CmsContainerPageBean readModelGroups(CmsContainerPageBean page) { 502 503 List<CmsContainerBean> resultContainers = new ArrayList<CmsContainerBean>(); 504 for (CmsContainerBean container : page.getContainers().values()) { 505 boolean hasModels = false; 506 List<CmsContainerElementBean> elements = new ArrayList<CmsContainerElementBean>(); 507 for (CmsContainerElementBean element : container.getElements()) { 508 try { 509 element.initResource(m_cms); 510 if (isModelGroupResource(element.getResource())) { 511 hasModels = true; 512 CmsContainerPageBean modelGroupPage = getContainerPageBean(element.getResource()); 513 CmsContainerElementBean baseElement = getModelBaseElement( 514 modelGroupPage, 515 element.getResource()); 516 if (baseElement == null) { 517 LOG.error( 518 "Error rendering model group '" 519 + element.getResource().getRootPath() 520 + "', base element could not be determind."); 521 continue; 522 } 523 String baseInstanceId = baseElement.getInstanceId(); 524 CmsContainerElementBean replaceElement = getModelReplacementElement( 525 element, 526 baseElement, 527 false); 528 if (m_sessionCache != null) { 529 m_sessionCache.setCacheContainerElement(replaceElement.editorHash(), replaceElement); 530 } 531 elements.add(replaceElement); 532 resultContainers.addAll( 533 readModelContainers( 534 baseInstanceId, 535 element.getInstanceId(), 536 modelGroupPage, 537 baseElement.isCopyModel())); 538 } else { 539 elements.add(element); 540 } 541 } catch (CmsException e) { 542 LOG.info(e.getLocalizedMessage(), e); 543 } 544 } 545 if (hasModels) { 546 resultContainers.add(container.copyWithNewElements(elements)); 547 } else { 548 resultContainers.add(container); 549 } 550 } 551 return new CmsContainerPageBean(resultContainers); 552 } 553 554 /** 555 * Removes the model group containers.<p> 556 * 557 * @param page the container page state 558 * 559 * @return the container page without the model group containers 560 */ 561 public CmsContainerPageBean removeModelGroupContainers(CmsContainerPageBean page) { 562 563 Map<String, List<CmsContainerBean>> containersByParent = getContainerByParent(page); 564 Set<String> modelInstances = new HashSet<String>(); 565 for (CmsContainerElementBean element : page.getElements()) { 566 if (element.getIndividualSettings().containsKey(CmsContainerElement.MODEL_GROUP_ID)) { 567 modelInstances.add(element.getInstanceId()); 568 } 569 } 570 571 Set<String> descendingInstances = new HashSet<String>(); 572 for (String modelInstance : modelInstances) { 573 descendingInstances.addAll(collectDescendingInstances(modelInstance, containersByParent)); 574 } 575 List<CmsContainerBean> containers = new ArrayList<CmsContainerBean>(); 576 for (CmsContainerBean container : page.getContainers().values()) { 577 if ((container.getParentInstanceId() == null) 578 || !descendingInstances.contains(container.getParentInstanceId())) { 579 // iterate the container elements to replace the model group elements 580 List<CmsContainerElementBean> elements = new ArrayList<CmsContainerElementBean>(); 581 for (CmsContainerElementBean element : container.getElements()) { 582 if (modelInstances.contains(element.getInstanceId())) { 583 CmsUUID modelId = new CmsUUID( 584 element.getIndividualSettings().get(CmsContainerElement.MODEL_GROUP_ID)); 585 CmsContainerElementBean replacer = new CmsContainerElementBean( 586 modelId, 587 element.getFormatterId(), 588 element.getIndividualSettings(), 589 false); 590 elements.add(replacer); 591 } else { 592 elements.add(element); 593 } 594 } 595 containers.add(container.copyWithNewElements(elements)); 596 } 597 } 598 return new CmsContainerPageBean(containers); 599 } 600 601 /** 602 * Saves the model groups of the given container page.<p> 603 * 604 * @param page the container page 605 * @param pageResource the model group resource 606 * 607 * @return the container page referencing the saved model groups 608 * 609 * @throws CmsException in case writing the page properties fails 610 */ 611 public CmsContainerPageBean saveModelGroups(CmsContainerPageBean page, CmsResource pageResource) 612 throws CmsException { 613 614 CmsUUID modelElementId = null; 615 CmsContainerElementBean baseElement = null; 616 for (CmsContainerElementBean element : page.getElements()) { 617 if (element.isModelGroup()) { 618 modelElementId = element.getId(); 619 baseElement = element; 620 break; 621 622 } 623 } 624 List<CmsContainerBean> containers = new ArrayList<CmsContainerBean>(); 625 for (CmsContainerBean container : page.getContainers().values()) { 626 List<CmsContainerElementBean> elements = new ArrayList<CmsContainerElementBean>(); 627 boolean hasChanges = false; 628 for (CmsContainerElementBean element : container.getElements()) { 629 if (element.isModelGroup() && !element.getId().equals(modelElementId)) { 630 // there should not be another model group element, remove the model group settings 631 Map<String, String> settings = new HashMap<String, String>(element.getIndividualSettings()); 632 settings.remove(CmsContainerElement.MODEL_GROUP_ID); 633 settings.remove(CmsContainerElement.MODEL_GROUP_STATE); 634 elements.add( 635 new CmsContainerElementBean(element.getId(), element.getFormatterId(), settings, false)); 636 hasChanges = true; 637 } else { 638 elements.add(element); 639 } 640 } 641 if (hasChanges) { 642 containers.add(container.copyWithNewElements(elements)); 643 } else { 644 containers.add(container); 645 } 646 647 } 648 649 List<CmsProperty> changedProps = new ArrayList<CmsProperty>(); 650 if (baseElement != null) { 651 String val = Boolean.parseBoolean( 652 baseElement.getIndividualSettings().get(CmsContainerElement.USE_AS_COPY_MODEL)) 653 ? CmsContainerElement.USE_AS_COPY_MODEL 654 : ""; 655 changedProps.add(new CmsProperty(CmsPropertyDefinition.PROPERTY_TEMPLATE_ELEMENTS, val, val)); 656 } 657 m_cms.writePropertyObjects(pageResource, changedProps); 658 659 return new CmsContainerPageBean(containers); 660 661 } 662 663 /** 664 * Adjusts formatter settings and initializes a new instance id for the given container element.<p> 665 * 666 * @param element the container element 667 * @param originalContainer the original parent container name 668 * @param adjustedContainer the target container name 669 * @param setNewInstanceId <code>true</code> to set a new instance id 670 * 671 * @return the new element instance 672 */ 673 private CmsContainerElementBean adjustSettings( 674 CmsContainerElementBean element, 675 String originalContainer, 676 String adjustedContainer, 677 boolean setNewInstanceId) { 678 679 Map<String, String> settings = new HashMap<String, String>(element.getIndividualSettings()); 680 if (setNewInstanceId) { 681 settings.put(CmsContainerElement.ELEMENT_INSTANCE_ID, new CmsUUID().toString()); 682 } 683 String formatterId = settings.remove(CmsFormatterConfig.getSettingsKeyForContainer(originalContainer)); 684 settings.put(CmsFormatterConfig.getSettingsKeyForContainer(adjustedContainer), formatterId); 685 return CmsContainerElementBean.cloneWithSettings(element, settings); 686 } 687 688 /** 689 * Returns the descending instance id's to the given element instance.<p> 690 * 691 * @param instanceId the instance id 692 * @param containersByParent the container page containers by parent instance id 693 * 694 * @return the containers 695 */ 696 private Set<String> collectDescendingInstances( 697 String instanceId, 698 Map<String, List<CmsContainerBean>> containersByParent) { 699 700 Set<String> descendingInstances = new HashSet<String>(); 701 descendingInstances.add(instanceId); 702 if (containersByParent.containsKey(instanceId)) { 703 for (CmsContainerBean container : containersByParent.get(instanceId)) { 704 for (CmsContainerElementBean element : container.getElements()) { 705 descendingInstances.addAll(collectDescendingInstances(element.getInstanceId(), containersByParent)); 706 } 707 } 708 } 709 return descendingInstances; 710 } 711 712 /** 713 * Collects the model group structure.<p> 714 * 715 * @param modelInstanceId the model instance id 716 * @param replaceModelId the local instance id 717 * @param containerByParent the model group page containers by parent instance id 718 * @param isCopyGroup <code>true</code> in case of a copy group 719 * 720 * @return the collected containers 721 */ 722 private List<CmsContainerBean> collectModelStructure( 723 String modelInstanceId, 724 String replaceModelId, 725 Map<String, List<CmsContainerBean>> containerByParent, 726 boolean isCopyGroup) { 727 728 List<CmsContainerBean> result = new ArrayList<CmsContainerBean>(); 729 730 if (containerByParent.containsKey(modelInstanceId)) { 731 for (CmsContainerBean container : containerByParent.get(modelInstanceId)) { 732 String adjustedContainerName = replaceModelId + container.getName().substring(modelInstanceId.length()); 733 734 List<CmsContainerElementBean> elements = new ArrayList<CmsContainerElementBean>(); 735 for (CmsContainerElementBean element : container.getElements()) { 736 CmsContainerElementBean copyElement = adjustSettings( 737 element, 738 container.getName(), 739 adjustedContainerName, 740 isCopyGroup); 741 if (m_sessionCache != null) { 742 m_sessionCache.setCacheContainerElement(copyElement.editorHash(), copyElement); 743 } 744 elements.add(copyElement); 745 result.addAll( 746 collectModelStructure( 747 element.getInstanceId(), 748 copyElement.getInstanceId(), 749 containerByParent, 750 isCopyGroup)); 751 } 752 753 result.add( 754 new CmsContainerBean( 755 adjustedContainerName, 756 container.getType(), 757 replaceModelId, 758 container.isRootContainer(), 759 container.getMaxElements(), 760 elements)); 761 } 762 } 763 return result; 764 } 765 766 /** 767 * Creates new resources for elements marked with create as new.<p> 768 * 769 * @param cms the cms context 770 * @param modelContainers the model containers 771 * @param locale the content locale 772 * @param createContext the context path to pass to CmsResourceTypeConfig#createNewElement 773 * 774 * @return the updated model containers 775 * 776 * @throws CmsException in case something goes wrong 777 */ 778 private List<CmsContainerBean> createNewElementsForModelGroup( 779 CmsObject cms, 780 List<CmsContainerBean> modelContainers, 781 Locale locale, 782 String createContext) 783 throws CmsException { 784 785 Map<CmsUUID, CmsResource> newResources = new HashMap<CmsUUID, CmsResource>(); 786 CmsObject cloneCms = OpenCms.initCmsObject(cms); 787 cloneCms.getRequestContext().setLocale(locale); 788 for (CmsContainerBean container : modelContainers) { 789 for (CmsContainerElementBean element : container.getElements()) { 790 if (element.isCreateNew() && !newResources.containsKey(element.getId())) { 791 element.initResource(cms); 792 String typeName = OpenCms.getResourceManager().getResourceType(element.getResource()).getTypeName(); 793 CmsResourceTypeConfig typeConfig = m_configData.getResourceType(typeName); 794 if (typeConfig == null) { 795 throw new IllegalArgumentException( 796 "Can not copy template model element '" 797 + element.getResource().getRootPath() 798 + "' because the resource type '" 799 + typeName 800 + "' is not available in this sitemap."); 801 } 802 CmsResource newResource = typeConfig.createNewElement( 803 cloneCms, 804 element.getResource(), 805 createContext); 806 newResources.put(element.getId(), newResource); 807 } 808 } 809 } 810 if (!newResources.isEmpty()) { 811 List<CmsContainerBean> updatedContainers = new ArrayList<CmsContainerBean>(); 812 for (CmsContainerBean container : modelContainers) { 813 List<CmsContainerElementBean> updatedElements = new ArrayList<CmsContainerElementBean>(); 814 for (CmsContainerElementBean element : container.getElements()) { 815 if (newResources.containsKey(element.getId())) { 816 CmsContainerElementBean newBean = new CmsContainerElementBean( 817 newResources.get(element.getId()).getStructureId(), 818 element.getFormatterId(), 819 element.getIndividualSettings(), 820 false); 821 updatedElements.add(newBean); 822 } else { 823 updatedElements.add(element); 824 } 825 } 826 CmsContainerBean updatedContainer = container.copyWithNewElements(updatedElements); 827 updatedContainers.add(updatedContainer); 828 } 829 modelContainers = updatedContainers; 830 } 831 return modelContainers; 832 } 833 834 /** 835 * Collects the page containers by parent instance id.<p> 836 * 837 * @param page the page 838 * 839 * @return the containers by parent id 840 */ 841 private Map<String, List<CmsContainerBean>> getContainerByParent(CmsContainerPageBean page) { 842 843 Map<String, List<CmsContainerBean>> containerByParent = new HashMap<String, List<CmsContainerBean>>(); 844 845 for (CmsContainerBean container : page.getContainers().values()) { 846 if (container.getParentInstanceId() != null) { 847 if (!containerByParent.containsKey(container.getParentInstanceId())) { 848 containerByParent.put(container.getParentInstanceId(), new ArrayList<CmsContainerBean>()); 849 } 850 containerByParent.get(container.getParentInstanceId()).add(container); 851 } 852 } 853 return containerByParent; 854 } 855 856 /** 857 * Unmarshals the given resource.<p> 858 * 859 * @param resource the resource 860 * 861 * @return the container page bean 862 * 863 * @throws CmsException in case unmarshalling fails 864 */ 865 private CmsContainerPageBean getContainerPageBean(CmsResource resource) throws CmsException { 866 867 CmsXmlContainerPage xmlCnt = CmsXmlContainerPageFactory.unmarshal(m_cms, m_cms.readFile(resource)); 868 return xmlCnt.getContainerPage(m_cms); 869 } 870 871 /** 872 * Returns the model group base element.<p> 873 * 874 * @param modelGroupPage the model group page 875 * @param modelGroupResource the model group resource 876 * 877 * @return the base element 878 */ 879 private CmsContainerElementBean getModelBaseElement( 880 CmsContainerPageBean modelGroupPage, 881 CmsResource modelGroupResource) { 882 883 CmsContainerElementBean result = null; 884 for (CmsContainerElementBean element : modelGroupPage.getElements()) { 885 if (CmsContainerElement.ModelGroupState.isModelGroup.name().equals( 886 element.getIndividualSettings().get(CmsContainerElement.MODEL_GROUP_STATE))) { 887 result = element; 888 break; 889 } 890 } 891 return result; 892 } 893 894 /** 895 * Returns the the element to be rendered as the model group base.<p> 896 * 897 * @param element the original element 898 * @param baseElement the model group base 899 * @param allowCopyModel if copy models are allowed 900 * 901 * @return the element 902 */ 903 private CmsContainerElementBean getModelReplacementElement( 904 CmsContainerElementBean element, 905 CmsContainerElementBean baseElement, 906 boolean allowCopyModel) { 907 908 boolean resetSettings = false; 909 if (!baseElement.isCopyModel() && hasIncompatibleFormatters(baseElement, element)) { 910 I_CmsFormatterBean formatter = m_configData.findFormatter(element.getFormatterId()); 911 resetSettings = (formatter == null) 912 || !formatter.getResourceTypeNames().contains( 913 OpenCms.getResourceManager().getResourceType(baseElement.getResource()).getTypeName()); 914 } 915 Map<String, String> settings; 916 if (resetSettings) { 917 settings = new HashMap<String, String>(); 918 for (String id : KEEP_SETTING_IDS) { 919 if (element.getIndividualSettings().containsKey(id)) { 920 settings.put(id, element.getIndividualSettings().get(id)); 921 } 922 } 923 settings.put(CmsContainerElement.MODEL_GROUP_ID, element.getId().toString()); 924 // transfer all other settings 925 for (Entry<String, String> settingEntry : baseElement.getIndividualSettings().entrySet()) { 926 if (!settings.containsKey(settingEntry.getKey())) { 927 settings.put(settingEntry.getKey(), settingEntry.getValue()); 928 } 929 } 930 } else { 931 settings = new HashMap<String, String>(element.getIndividualSettings()); 932 if (!(baseElement.isCopyModel() && allowCopyModel)) { 933 // skip the model id in case of copy models 934 settings.put(CmsContainerElement.MODEL_GROUP_ID, element.getId().toString()); 935 if (allowCopyModel) { 936 // transfer all other settings 937 for (Entry<String, String> settingEntry : baseElement.getIndividualSettings().entrySet()) { 938 if (!settings.containsKey(settingEntry.getKey())) { 939 settings.put(settingEntry.getKey(), settingEntry.getValue()); 940 } 941 } 942 } 943 944 } else if (baseElement.isCopyModel()) { 945 settings.put(CmsContainerElement.MODEL_GROUP_STATE, ModelGroupState.wasModelGroup.name()); 946 } 947 } 948 return CmsContainerElementBean.cloneWithSettings(baseElement, settings); 949 } 950 951 private boolean hasIncompatibleFormatters(CmsContainerElementBean baseElement, CmsContainerElementBean element) { 952 953 if ((baseElement == null) || (element == null)) { 954 return false; 955 } 956 if ((baseElement.getFormatterId() != null) 957 && (element.getFormatterId() != null) 958 && !baseElement.getFormatterId().equals(element.getFormatterId())) { 959 return true; 960 } 961 Set<String> baseFormatterKeys = CmsFormatterUtils.getAllFormatterKeys(m_configData, baseElement); 962 Set<String> elementFormatterKeys = CmsFormatterUtils.getAllFormatterKeys(m_configData, element); 963 boolean hasCommonKeys = baseFormatterKeys.stream().anyMatch(elementFormatterKeys::contains); 964 return !hasCommonKeys; 965 } 966 967 /** 968 * Returns the model containers.<p> 969 * 970 * @param modelInstanceId the model instance id 971 * @param localInstanceId the local instance id 972 * @param modelPage the model page bean 973 * @param isCopyGroup <code>true</code> in case of a copy group 974 * 975 * @return the model group containers 976 */ 977 private List<CmsContainerBean> readModelContainers( 978 String modelInstanceId, 979 String localInstanceId, 980 CmsContainerPageBean modelPage, 981 boolean isCopyGroup) { 982 983 Map<String, List<CmsContainerBean>> containerByParent = getContainerByParent(modelPage); 984 List<CmsContainerBean> modelContainers; 985 if (containerByParent.containsKey(modelInstanceId)) { 986 modelContainers = collectModelStructure(modelInstanceId, localInstanceId, containerByParent, isCopyGroup); 987 } else { 988 modelContainers = new ArrayList<CmsContainerBean>(); 989 } 990 return modelContainers; 991 } 992}