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