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