001/* 002 * Copyright 2011 Anyware Services 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.ametys.web.repository.page; 017 018import java.io.InputStream; 019import java.io.OutputStream; 020import java.util.ArrayList; 021import java.util.Arrays; 022import java.util.HashMap; 023import java.util.List; 024import java.util.Map; 025import java.util.Optional; 026import java.util.Properties; 027import java.util.Set; 028import java.util.stream.Stream; 029 030import javax.jcr.Node; 031import javax.jcr.RepositoryException; 032import javax.xml.transform.OutputKeys; 033import javax.xml.transform.TransformerFactory; 034import javax.xml.transform.sax.SAXTransformerFactory; 035import javax.xml.transform.sax.TransformerHandler; 036import javax.xml.transform.stream.StreamResult; 037 038import org.apache.avalon.framework.component.Component; 039import org.apache.avalon.framework.logger.AbstractLogEnabled; 040import org.apache.avalon.framework.service.ServiceException; 041import org.apache.avalon.framework.service.ServiceManager; 042import org.apache.avalon.framework.service.Serviceable; 043import org.apache.commons.lang3.StringUtils; 044import org.apache.excalibur.xml.sax.SAXParser; 045import org.apache.xml.serializer.OutputPropertiesFactory; 046import org.xml.sax.ContentHandler; 047import org.xml.sax.InputSource; 048 049import org.ametys.cms.CmsConstants; 050import org.ametys.cms.content.references.OutgoingReferences; 051import org.ametys.cms.content.references.OutgoingReferencesExtractor; 052import org.ametys.cms.contenttype.RichTextUpdater; 053import org.ametys.cms.data.ContentDataHelper; 054import org.ametys.cms.data.ExplorerFile; 055import org.ametys.cms.data.File; 056import org.ametys.cms.data.RichText; 057import org.ametys.cms.data.type.RichTextElementType; 058import org.ametys.cms.repository.Content; 059import org.ametys.cms.repository.ModifiableContent; 060import org.ametys.cms.repository.WorkflowAwareContent; 061import org.ametys.plugins.repository.AmetysObject; 062import org.ametys.plugins.repository.AmetysObjectIterable; 063import org.ametys.plugins.repository.AmetysObjectResolver; 064import org.ametys.plugins.repository.AmetysRepositoryException; 065import org.ametys.plugins.repository.ModifiableAmetysObject; 066import org.ametys.plugins.repository.RepositoryConstants; 067import org.ametys.plugins.repository.TraversableAmetysObject; 068import org.ametys.plugins.repository.UnknownAmetysObjectException; 069import org.ametys.plugins.repository.data.UnknownDataException; 070import org.ametys.plugins.repository.data.holder.DataHolder; 071import org.ametys.plugins.repository.data.holder.ModelAwareDataHolder; 072import org.ametys.plugins.repository.data.holder.ModifiableDataHolder; 073import org.ametys.plugins.repository.data.holder.group.Repeater; 074import org.ametys.plugins.repository.data.holder.group.RepeaterEntry; 075import org.ametys.plugins.repository.data.holder.impl.DataHolderHelper; 076import org.ametys.plugins.repository.data.type.ModelItemTypeConstants; 077import org.ametys.plugins.repository.version.VersionableAmetysObject; 078import org.ametys.plugins.workflow.repository.WorkflowAwareAmetysObjectHelper; 079import org.ametys.plugins.workflow.support.WorkflowProvider; 080import org.ametys.plugins.workflow.support.WorkflowProvider.AmetysObjectWorkflow; 081import org.ametys.runtime.model.ElementDefinition; 082import org.ametys.runtime.model.ModelItem; 083import org.ametys.runtime.model.exception.BadItemTypeException; 084import org.ametys.runtime.model.exception.NotUniqueTypeException; 085import org.ametys.runtime.model.exception.UndefinedItemPathException; 086import org.ametys.runtime.model.exception.UnknownTypeException; 087import org.ametys.web.repository.ModifiableSiteAwareAmetysObject; 088import org.ametys.web.repository.content.WebContent; 089import org.ametys.web.repository.content.jcr.DefaultSharedContent; 090import org.ametys.web.repository.content.shared.SharedContentManager; 091import org.ametys.web.repository.page.ZoneItem.ZoneType; 092import org.ametys.web.repository.site.Site; 093import org.ametys.web.repository.sitemap.Sitemap; 094import org.ametys.web.site.CopyUpdaterExtensionPoint; 095 096import com.opensymphony.workflow.spi.Step; 097 098/** 099 * Component for copying site or pages 100 * 101 */ 102public class CopySiteComponent extends AbstractLogEnabled implements Component, Serviceable 103{ 104 /** Avalon Role */ 105 public static final String ROLE = CopySiteComponent.class.getName(); 106 107 /** The service manager. */ 108 protected ServiceManager _manager; 109 110 private AmetysObjectResolver _resolver; 111 private WorkflowProvider _workflowProvider; 112 113 private CopyUpdaterExtensionPoint _updaterEP; 114 private OutgoingReferencesExtractor _outgoingReferencesExtractor; 115 private SharedContentManager _sharedContentManager; 116 117 @Override 118 public void service(ServiceManager serviceManager) throws ServiceException 119 { 120 _manager = serviceManager; 121 _resolver = (AmetysObjectResolver) serviceManager.lookup(AmetysObjectResolver.ROLE); 122 _workflowProvider = (WorkflowProvider) serviceManager.lookup(WorkflowProvider.ROLE); 123 _updaterEP = (CopyUpdaterExtensionPoint) serviceManager.lookup(CopyUpdaterExtensionPoint.ROLE); 124 _outgoingReferencesExtractor = (OutgoingReferencesExtractor) serviceManager.lookup(OutgoingReferencesExtractor.ROLE); 125 _sharedContentManager = (SharedContentManager) serviceManager.lookup(SharedContentManager.ROLE); 126 } 127 128 /** 129 * This methods must be used after calling <code>copyTo</code> on a Page. 130 * Its updates references to ametys objects for metadata of new created pages and contents 131 * @param originalPage the original page 132 * @param createdPage the created page after a copy 133 * @throws AmetysRepositoryException if an error occurs 134 */ 135 public void updateReferencesAfterCopy (Page originalPage, Page createdPage) throws AmetysRepositoryException 136 { 137 // Update references to ametys object on metadata 138 _updateReferencesToAmetysObjects (createdPage, originalPage, createdPage); 139 140 for (Zone zone : createdPage.getZones()) 141 { 142 _updateReferencesToAmetysObjects (zone, originalPage, createdPage); 143 144 try (AmetysObjectIterable<? extends ZoneItem> zoneItems = zone.getZoneItems()) 145 { 146 for (ZoneItem zoneItem : zoneItems) 147 { 148 _updateReferencesToAmetysObjects (zoneItem, originalPage, createdPage); 149 150 if (zoneItem.getType().equals(ZoneType.SERVICE)) 151 { 152 _updateReferencesToAmetysObjects (zoneItem, originalPage, createdPage); 153 } 154 else 155 { 156 Content content = zoneItem.getContent(); 157 _updateReferencesToAmetysObjects(content, originalPage, createdPage); 158 } 159 } 160 } 161 } 162 163 // Browse child pages 164 for (Page childPage : createdPage.getChildrenPages()) 165 { 166 updateReferencesAfterCopy ((Page) originalPage.getChild(childPage.getName()), childPage); 167 } 168 } 169 170 /** 171 * This method must be used after calling <code>copyTo</code> on a Site. 172 * Its updates contents and pages after a site copy 173 * @param originalSite the original site 174 * @param createdSite the created site after copy 175 * @throws AmetysRepositoryException if an error occurs 176 */ 177 public void updateSiteAfterCopy (Site originalSite, Site createdSite) throws AmetysRepositoryException 178 { 179 updateContentsAfterCopy (originalSite, createdSite); 180 updatePagesAfterCopy (originalSite, createdSite); 181 182 Set<String> ids = _updaterEP.getExtensionsIds(); 183 for (String id : ids) 184 { 185 _updaterEP.getExtension(id).updateSite(originalSite, createdSite); 186 } 187 } 188 189 /** 190 * This method re-initializes workflow, updates the site name for web content and updates references to ametys objects on metadata after a site copy 191 * @param initialSite the original site 192 * @param createdSite the created site after copy 193 * @throws AmetysRepositoryException if an error occurs 194 */ 195 public void updateContentsAfterCopy (Site initialSite, Site createdSite) throws AmetysRepositoryException 196 { 197 AmetysObjectIterable<Content> contents = createdSite.getContents(); 198 for (Content content : contents) 199 { 200 String relPath = content.getPath().substring(createdSite.getPath().length() + 1); 201 WebContent initialContent = initialSite.getChild(relPath); 202 203 try 204 { 205 // Re-init workflow 206 if (content instanceof WorkflowAwareContent) 207 { 208 _reinitWorkflow ((WorkflowAwareContent) content); 209 } 210 211 // Update site name 212 if (content instanceof ModifiableSiteAwareAmetysObject) 213 { 214 ((ModifiableSiteAwareAmetysObject) content).setSiteName(createdSite.getName()); 215 } 216 217 if (content instanceof ModifiableContent) 218 { 219 // Update references to ametys object on attributes 220 _updateReferencesToAmetysObjects(content, initialSite, createdSite); 221 222 // Update links in RichText 223 updateLinksInRichText (initialSite, createdSite, initialContent, content); 224 } 225 226 // Updaters 227 Set<String> ids = _updaterEP.getExtensionsIds(); 228 for (String id : ids) 229 { 230 _updaterEP.getExtension(id).updateContent(initialSite, createdSite, initialContent, content); 231 } 232 233 boolean needsSaveAndCheckpoint = true; 234 235 // Validate the shared content if the original content is validated. 236 if (content instanceof DefaultSharedContent sharedContent) 237 { 238 Content referenceContent = sharedContent.getInitialContent(); 239 if (referenceContent != null && referenceContent instanceof VersionableAmetysObject vaContent) 240 { 241 if (Arrays.asList(vaContent.getAllLabels()).contains(CmsConstants.LIVE_LABEL)) 242 { 243 _sharedContentManager.validateContent(sharedContent); 244 needsSaveAndCheckpoint = false; // validateContent already saved and checkpointed the content 245 } 246 } 247 } 248 249 if (needsSaveAndCheckpoint) 250 { 251 // save 252 if (content instanceof ModifiableAmetysObject) 253 { 254 ((ModifiableAmetysObject) content).saveChanges(); 255 } 256 257 // Creates the first version 258 if (content instanceof VersionableAmetysObject) 259 { 260 ((VersionableAmetysObject) content).checkpoint(); 261 } 262 } 263 } 264 catch (Exception e) 265 { 266 // Do not make the copy fail. 267 getLogger().warn("[Site copy] An error occured while updating content '" + content.getId() + " after copy from initial content '" + initialContent.getId() + "'", e); 268 } 269 } 270 271 if (createdSite.needsSave()) 272 { 273 createdSite.saveChanges(); 274 } 275 } 276 277 /** 278 * Updates references all references in a content to another one. 279 * @param initialContent the initial content. 280 * @param destContent the destination content. 281 */ 282 public void updateSharedContent(WebContent initialContent, WebContent destContent) 283 { 284 updateSharedContent(initialContent, destContent, true); 285 } 286 287 /** 288 * Updates references all references in a content to another one. 289 * @param initialContent the initial content. 290 * @param destContent the destination content. 291 * @param reinitWorkflow set to 'true' to reinitialize the workflow 292 */ 293 public void updateSharedContent(WebContent initialContent, WebContent destContent, boolean reinitWorkflow) 294 { 295 Site initialSite = initialContent.getSite(); 296 Site createdSite = destContent.getSite(); 297 298 // Re-init workflow 299 if (reinitWorkflow && destContent instanceof WorkflowAwareContent) 300 { 301 _reinitWorkflow((WorkflowAwareContent) destContent); 302 } 303 304 // Update references to ametys object on attributes 305 _updateReferencesToAmetysObjects(destContent, initialContent, destContent); 306 307 // Update links in RichText 308 updateLinksInRichText(initialContent, destContent, initialContent, destContent); 309 310 // Updaters 311 Set<String> ids = _updaterEP.getExtensionsIds(); 312 for (String id : ids) 313 { 314 _updaterEP.getExtension(id).updateContent(initialSite, createdSite, initialContent, destContent); 315 } 316 } 317 318 /** 319 * This method analyzes content rich texts and update links if necessary 320 * @param initialAO The initial object copied 321 * @param createdAO The target object 322 * @param initialContent The initial content 323 * @param createdContent The created content after copy to update 324 * @throws AmetysRepositoryException if an error occurs 325 */ 326 public void updateLinksInRichText (TraversableAmetysObject initialAO, TraversableAmetysObject createdAO, Content initialContent, Content createdContent) throws AmetysRepositoryException 327 { 328 SAXParser saxParser = null; 329 try 330 { 331 if (createdContent instanceof ModifiableContent) 332 { 333 saxParser = (SAXParser) _manager.lookup(SAXParser.ROLE); 334 335 Map<String, Object> params = new HashMap<>(); 336 params.put("initialContent", initialContent); 337 params.put("createdContent", createdContent); 338 params.put("initialAO", initialAO); 339 params.put("createdAO", createdAO); 340 341 Map<String, Object> richTexts = DataHolderHelper.findEditableItemsByType(createdContent, org.ametys.cms.data.type.ModelItemTypeConstants.RICH_TEXT_ELEMENT_TYPE_ID); 342 for (Map.Entry<String, Object> entry : richTexts.entrySet()) 343 { 344 Object value = entry.getValue(); 345 if (value != null) 346 { 347 String attributePath = entry.getKey(); 348 ModelItem attributeDefinition = createdContent.getDefinition(attributePath); 349 RichTextElementType type = (RichTextElementType) attributeDefinition.getType(); 350 RichTextUpdater richTextUpdater = type.getRichTextUpdater(); 351 352 if (value instanceof RichText) 353 { 354 _updateRichText((RichText) value, richTextUpdater, params, saxParser); 355 } 356 else if (value instanceof RichText[]) 357 { 358 for (RichText richText : (RichText[]) value) 359 { 360 _updateRichText(richText, richTextUpdater, params, saxParser); 361 } 362 } 363 364 ((ModifiableContent) createdContent).setValue(attributePath, value); 365 } 366 } 367 368 // Outgoing references 369 Map<String, OutgoingReferences> outgoingReferencesByPath = _outgoingReferencesExtractor.getOutgoingReferences(createdContent); 370 ((ModifiableContent) createdContent).setOutgoingReferences(outgoingReferencesByPath); 371 } 372 } 373 catch (Exception e) 374 { 375 // Do not failed the copy 376 getLogger().warn("An error occured while updating links in RichText for content '" + createdContent.getId() + " after copy from initial content '" + initialContent.getId() + "'", e); 377 } 378 finally 379 { 380 _manager.release(saxParser); 381 } 382 } 383 384 private void _updateRichText(RichText richText, RichTextUpdater richTextUpdater, Map<String, Object> params, SAXParser saxParser) throws Exception 385 { 386 try (InputStream is = richText.getInputStream(); OutputStream os = richText.getOutputStream()) 387 { 388 // create a transformer for saving sax into a file 389 TransformerHandler th = ((SAXTransformerFactory) TransformerFactory.newInstance()).newTransformerHandler(); 390 391 // create the result where to write 392 StreamResult result = new StreamResult(os); 393 th.setResult(result); 394 395 // create the format of result 396 Properties format = new Properties(); 397 format.put(OutputKeys.METHOD, "xml"); 398 format.put(OutputKeys.INDENT, "yes"); 399 format.put(OutputKeys.ENCODING, "UTF-8"); 400 format.put(OutputPropertiesFactory.S_KEY_INDENT_AMOUNT, "2"); 401 th.getTransformer().setOutputProperties(format); 402 403 ContentHandler richTextHandler = richTextUpdater.getContentHandler(th, th, params); 404 saxParser.parse(new InputSource(is), richTextHandler); 405 } 406 } 407 408 /** 409 * This method updates the site name of pages and updates references to ametys objects on page's metadata after a site copy 410 * @param originalSite the original site 411 * @param createdSite the created site after copy 412 * @throws AmetysRepositoryException if an error occurs 413 */ 414 public void updatePagesAfterCopy (Site originalSite, Site createdSite) throws AmetysRepositoryException 415 { 416 AmetysObjectIterable<Sitemap> sitemaps = createdSite.getSitemaps(); 417 418 for (Sitemap sitemap : sitemaps) 419 { 420 for (Page page : sitemap.getChildrenPages()) 421 { 422 _updatePageAfterCopy (originalSite, createdSite, page); 423 } 424 } 425 } 426 427 private void _updatePageAfterCopy (Site originalSite, Site createdSite, Page page) throws AmetysRepositoryException 428 { 429 try 430 { 431 // Update site name 432 if (page instanceof ModifiablePage) 433 { 434 ((ModifiablePage) page).setSiteName(createdSite.getName()); 435 } 436 437 // Update references to ametys object on metadata 438 _updateReferencesToAmetysObjects (page, originalSite, createdSite); 439 440 for (Zone zone : page.getZones()) 441 { 442 _updateReferencesToAmetysObjects (zone, originalSite, createdSite); 443 444 try (AmetysObjectIterable<? extends ZoneItem> zoneItems = zone.getZoneItems()) 445 { 446 for (ZoneItem zoneItem : zoneItems) 447 { 448 try 449 { 450 _updateZoneItemAfterCopy(originalSite, createdSite, zoneItem); 451 } 452 catch (Exception e) 453 { 454 getLogger().warn("An error occured while updating zone item '" + zoneItem.getId() + "' (" + zoneItem.getPath() + ") after copy", e); 455 } 456 } 457 } 458 } 459 } 460 catch (AmetysRepositoryException e) 461 { 462 // Do not failed the copy 463 getLogger().warn("An error occured while updating page '" + page.getId() + "' (" + page.getPathInSitemap() + ") after copy", e); 464 } 465 466 // Browse child pages 467 for (Page childPage : page.getChildrenPages()) 468 { 469 _updatePageAfterCopy (originalSite, createdSite, childPage); 470 } 471 472 // Updaters 473 Set<String> ids = _updaterEP.getExtensionsIds(); 474 for (String id : ids) 475 { 476 _updaterEP.getExtension(id).updatePage(originalSite, createdSite, page); 477 } 478 } 479 480 private void _updateZoneItemAfterCopy(Site originalSite, Site createdSite, ZoneItem zoneItem) 481 { 482 _updateReferencesToAmetysObjects (zoneItem, originalSite, createdSite); 483 484 if (zoneItem.getType().equals(ZoneType.SERVICE)) 485 { 486 _updateReferencesToAmetysObjects (zoneItem.getServiceParameters(), originalSite, createdSite); 487 } 488 else if (zoneItem.getType().equals(ZoneType.CONTENT) && zoneItem instanceof ModifiableZoneItem) 489 { 490 Content content = zoneItem.getContent(); 491 String path = content.getPath(); 492 493 String originalPath = originalSite.getPath(); 494 if (path.startsWith(originalPath)) 495 { 496 String relPath = path.substring(originalPath.length() + 1); 497 try 498 { 499 // Find symmetric object on copied sub-tree 500 Content child = createdSite.getChild(relPath); 501 ((ModifiableZoneItem) zoneItem).setContent(child); 502 } 503 catch (UnknownAmetysObjectException e) 504 { 505 // Nothing 506 } 507 } 508 } 509 } 510 511 512 513 private void _updateReferencesToAmetysObjects (DataHolder dataHolder, TraversableAmetysObject originalAO, TraversableAmetysObject createdAO) 514 { 515 try 516 { 517 for (String dataName : dataHolder.getDataNames()) 518 { 519 String typeId = dataHolder.getType(dataName).getId(); 520 if (ModelItemTypeConstants.COMPOSITE_TYPE_ID.equals(typeId)) 521 { 522 _updateReferencesToAmetysObjects(dataHolder.getComposite(dataName), originalAO, createdAO); 523 } 524 else if (ModelItemTypeConstants.REPEATER_TYPE_ID.equals(typeId) && dataHolder instanceof ModelAwareDataHolder) 525 { 526 // Repeaters are only available on model aware data holders 527 Repeater repeater = ((ModelAwareDataHolder) dataHolder).getRepeater(dataName); 528 for (RepeaterEntry entry : repeater.getEntries()) 529 { 530 _updateReferencesToAmetysObjects(entry, originalAO, createdAO); 531 } 532 } 533 else if (_isAmetysObject(dataHolder, dataName)) 534 { 535 _updateReferenceToAmetysObject(dataHolder, dataName, originalAO, createdAO); 536 } 537 } 538 } 539 catch (UndefinedItemPathException | UnknownDataException | UnknownTypeException | NotUniqueTypeException e) 540 { 541 // The type of the data has not been determined so there is no way to determine if it is a reference to an ametys object 542 } 543 544 } 545 546 private void _updateReferenceToAmetysObject (DataHolder dataHolder, String dataName, TraversableAmetysObject originalAO, TraversableAmetysObject createdAO) throws AmetysRepositoryException 547 { 548 boolean isDataModifiable = dataHolder instanceof ModifiableDataHolder; 549 if (isDataModifiable && dataHolder instanceof ModelAwareDataHolder modelAwareDataHolder) 550 { 551 ElementDefinition definition = (ElementDefinition) modelAwareDataHolder.getDefinition(dataName); // definition can be casted hare. No way we can have groups here. 552 isDataModifiable = definition.isEditable(); 553 } 554 555 if (isDataModifiable) 556 { 557 if (dataHolder.isMultiple(dataName)) 558 { 559 String[] ids = _getValue(dataHolder, dataName); 560 List<String> newReferences = new ArrayList<>(); 561 boolean hasNewReference = false; 562 for (String id : ids) 563 { 564 String newReference = _getNewReferenceToAmetysObject(originalAO, createdAO, id).orElse(id); 565 newReferences.add(newReference); 566 hasNewReference |= !newReference.equals(id); 567 } 568 569 if (hasNewReference) 570 { 571 ((ModifiableDataHolder) dataHolder).setValue(dataName, newReferences.toArray(new String[newReferences.size()])); 572 } 573 } 574 else 575 { 576 String id = _getValue(dataHolder, dataName); 577 _getNewReferenceToAmetysObject(originalAO, createdAO, id) 578 .ifPresent(newReference -> ((ModifiableDataHolder) dataHolder).setValue(dataName, newReference)); 579 } 580 } 581 } 582 583 /** 584 * Retrieves the updated reference to an ametys object. 585 * If the object has been copied into the created site, the new reference is the symmetric object on created site 586 * Otherwise, the reference does not change, there is no new reference to retrieve 587 * @param originalAO the original ametys object containing the reference 588 * @param createdAO the created ametys object where to update the reference 589 * @param id the id of the referenced ametys object to potentially update 590 * @return the new reference to the ametys object, an empty {@link Optional} if the reference does not to change 591 */ 592 private Optional<String> _getNewReferenceToAmetysObject(TraversableAmetysObject originalAO, TraversableAmetysObject createdAO, String id) 593 { 594 AmetysObject ametysObject = _resolver.resolveById(id); 595 String path = ametysObject.getPath(); 596 597 String originalPath = originalAO.getPath(); 598 if (path.startsWith(originalPath + "/")) 599 { 600 String relPath = path.substring(originalPath.length() + 1); 601 try 602 { 603 // Find symmetric object on copied sub-tree 604 AmetysObject child = createdAO.getChild(relPath); 605 return Optional.of(child.getId()); 606 } 607 catch (UnknownAmetysObjectException e) 608 { 609 getLogger().warn("Object of path " + relPath + " was not found on copied sub-tree " + createdAO.getPath(), e); 610 return Optional.empty(); 611 } 612 catch (AmetysRepositoryException e) 613 { 614 getLogger().error("Unable to retrieve object of path " + relPath + " on copied sub-tree " + createdAO.getPath(), e); 615 return Optional.empty(); 616 } 617 } 618 else 619 { 620 return Optional.empty(); 621 } 622 } 623 624 private boolean _isAmetysObject (DataHolder dataHolder, String dataName) 625 { 626 try 627 { 628 String typeId = dataHolder.getType(dataName).getId(); 629 switch (typeId) 630 { 631 case org.ametys.cms.data.type.ModelItemTypeConstants.CONTENT_ELEMENT_TYPE_ID: 632 return dataHolder.hasValue(dataName); 633 case org.ametys.runtime.model.type.ModelItemTypeConstants.STRING_TYPE_ID: 634 case org.ametys.cms.data.type.ModelItemTypeConstants.FILE_ELEMENT_TYPE_ID: 635 if (dataHolder.hasValue(dataName)) 636 { 637 if (dataHolder.isMultiple(dataName)) 638 { 639 String[] values = _getValue(dataHolder, dataName); 640 for (String value : values) 641 { 642 if (_resolver.hasAmetysObjectForId(value)) 643 { 644 return true; 645 } 646 } 647 648 // None of the value of the multiple data is a reference 649 return false; 650 } 651 else 652 { 653 String value = _getValue(dataHolder, dataName); 654 return _resolver.hasAmetysObjectForId(value); 655 } 656 } 657 else 658 { 659 return false; 660 } 661 default: 662 return false; 663 } 664 } 665 catch (AmetysRepositoryException | UndefinedItemPathException | UnknownTypeException | NotUniqueTypeException e) 666 { 667 return false; 668 } 669 } 670 671 @SuppressWarnings("unchecked") 672 private <T> T _getValue(DataHolder dataHolder, String dataName) 673 { 674 if (dataHolder instanceof ModelAwareDataHolder modelAwareDataHolder) 675 { 676 String typeId = dataHolder.getType(dataName).getId(); 677 if (org.ametys.cms.data.type.ModelItemTypeConstants.CONTENT_ELEMENT_TYPE_ID.equals(typeId)) 678 { 679 return dataHolder.isMultiple(dataName) 680 ? (T) ContentDataHelper.getContentIdsArrayFromMultipleContentData(modelAwareDataHolder, dataName) 681 : (T) ContentDataHelper.getContentIdFromContentData(modelAwareDataHolder, dataName); 682 } 683 else if (org.ametys.cms.data.type.ModelItemTypeConstants.FILE_ELEMENT_TYPE_ID.equals(typeId)) 684 { 685 return dataHolder.isMultiple(dataName) 686 ? (T) _getResourceIdsArrayFromMultipleFileData(modelAwareDataHolder, dataName) 687 : (T) _getResourceIdFromFileData(modelAwareDataHolder, dataName); 688 } 689 else 690 { 691 return modelAwareDataHolder.getValue(dataName); 692 } 693 } 694 else 695 { 696 return dataHolder.getValue(dataName); 697 } 698 } 699 700 private String _getResourceIdFromFileData(ModelAwareDataHolder dataHolder, String dataPath) throws BadItemTypeException 701 { 702 File value = dataHolder.getValue(dataPath); 703 return Optional.ofNullable(value) 704 .filter(ExplorerFile.class::isInstance) 705 .map(ExplorerFile.class::cast) 706 .map(ExplorerFile::getResourceId) 707 .orElse(StringUtils.EMPTY); 708 } 709 710 private String[] _getResourceIdsArrayFromMultipleFileData(ModelAwareDataHolder dataHolder, String dataPath) throws BadItemTypeException 711 { 712 File[] value = dataHolder.getValue(dataPath); 713 return Optional.ofNullable(value) 714 .map(v -> Arrays.stream(v)) 715 .orElse(Stream.empty()) 716 .filter(ExplorerFile.class::isInstance) 717 .map(ExplorerFile.class::cast) 718 .map(ExplorerFile::getResourceId) 719 .toArray(String[]::new); 720 } 721 722 private void _reinitWorkflow (WorkflowAwareContent content) throws AmetysRepositoryException 723 { 724 try 725 { 726 long wId = content.getWorkflowId(); 727 728 AmetysObjectWorkflow workflow = _workflowProvider.getAmetysObjectWorkflow(content); 729 String workflowName = workflow.getWorkflowName(wId); 730 731 // 1 - Delete the cloned workflow 732 WorkflowAwareAmetysObjectHelper.removeWorkflowId(content); 733 734 // For legacy purpose, delete the workflow reference property if exists (only for contents created on 3.x versions) 735 Node node = content.getNode(); 736 try 737 { 738 if (node.hasProperty(RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":workflowRef")) 739 { 740 node.getProperty(RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":workflowRef").remove(); 741 } 742 } 743 catch (RepositoryException e) 744 { 745 throw new AmetysRepositoryException("Unable to remove workflowId property", e); 746 } 747 748 workflow.removeWorkflow(wId); 749 750 // 2 - Initialize new workflow instance 751 HashMap<String, Object> inputs = new HashMap<>(); 752 long workflowId = workflow.initialize(workflowName, 0, inputs); 753 content.setWorkflowId(workflowId); 754 755 // Update current step property 756 Step currentStep = (Step) workflow.getCurrentSteps(workflowId).iterator().next(); 757 content.setCurrentStepId(currentStep.getStepId()); 758 } 759 catch (Exception e) 760 { 761 throw new AmetysRepositoryException("Unable to initialize workflow for content " + content.getId(), e); 762 } 763 764 } 765}