001/* 002 * Copyright 2010 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.jcr; 017 018import java.util.ArrayList; 019import java.util.Collections; 020import java.util.HashMap; 021import java.util.HashSet; 022import java.util.List; 023import java.util.Map; 024import java.util.Set; 025import java.util.function.Consumer; 026 027import javax.jcr.ItemExistsException; 028import javax.jcr.ItemNotFoundException; 029import javax.jcr.Node; 030import javax.jcr.Property; 031import javax.jcr.PropertyType; 032import javax.jcr.RepositoryException; 033import javax.jcr.Value; 034 035import org.apache.commons.lang3.StringUtils; 036 037import org.ametys.plugins.explorer.resources.ResourceCollection; 038import org.ametys.plugins.explorer.resources.jcr.JCRResourcesCollectionFactory; 039import org.ametys.plugins.repository.AmetysObject; 040import org.ametys.plugins.repository.AmetysObjectIterable; 041import org.ametys.plugins.repository.AmetysRepositoryException; 042import org.ametys.plugins.repository.CopiableAmetysObject; 043import org.ametys.plugins.repository.ModifiableTraversableAmetysObject; 044import org.ametys.plugins.repository.RepositoryConstants; 045import org.ametys.plugins.repository.RepositoryIntegrityViolationException; 046import org.ametys.plugins.repository.UnknownAmetysObjectException; 047import org.ametys.plugins.repository.data.holder.ModifiableDataHolder; 048import org.ametys.plugins.repository.data.holder.impl.DataHolderHelper; 049import org.ametys.plugins.repository.jcr.JCRTraversableAmetysObject; 050import org.ametys.plugins.repository.jcr.NodeHelper; 051import org.ametys.plugins.repository.jcr.SimpleAmetysObject; 052import org.ametys.plugins.repository.tag.TaggableAmetysObjectHelper; 053import org.ametys.plugins.repository.trash.TrashElement; 054import org.ametys.plugins.repository.trash.TrashableAmetysObject; 055import org.ametys.plugins.repository.trash.UnknownParentException; 056import org.ametys.runtime.model.exception.BadItemTypeException; 057import org.ametys.runtime.model.exception.NotUniqueTypeException; 058import org.ametys.runtime.model.exception.UnknownTypeException; 059import org.ametys.runtime.model.type.DataContext; 060import org.ametys.runtime.model.type.ModelItemTypeConstants; 061import org.ametys.web.parameters.view.ViewParametersManager; 062import org.ametys.web.repository.page.LockablePage; 063import org.ametys.web.repository.page.ModifiableZone; 064import org.ametys.web.repository.page.MoveablePage; 065import org.ametys.web.repository.page.Page; 066import org.ametys.web.repository.page.PageDAO; 067import org.ametys.web.repository.page.SitemapElement; 068import org.ametys.web.repository.page.Zone; 069import org.ametys.web.repository.page.ZoneItem; 070import org.ametys.web.repository.site.Site; 071import org.ametys.web.repository.sitemap.Sitemap; 072 073/** 074 * {@link Page} implementation stored into a JCR node using 075 * <code>ametys:defaultPage</code> node type. 076 * 077 * @param <F> the actual type of factory. 078 */ 079public class DefaultPage<F extends DefaultPageFactory> extends AbstractSitemapElement<F> implements LockablePage, CopiableAmetysObject, MoveablePage, TrashableAmetysObject 080{ 081 /** Constant for template metadata. */ 082 public static final String METADATA_TEMPLATE = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":template"; 083 084 /** Constant for title metadata. */ 085 public static final String METADATA_TITLE = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":title"; 086 087 /** Constant for the initial title metadata. */ 088 public static final String METADATA_INITIAL_TITLE = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":title-initial"; 089 090 /** Constant for long title metadata. */ 091 public static final String METADATA_LONG_TITLE = "long-title"; 092 093 /** Constant for title metadata. */ 094 public static final String METADATA_TYPE = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":type"; 095 096 /** Constant for title metadata. */ 097 public static final String METADATA_URL = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":url"; 098 099 /** Constant for title metadata. */ 100 public static final String METADATA_URLTYPE = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":url-type"; 101 102 /** Constant for referers metadata. */ 103 public static final String METADATA_REFERERS = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":referers"; 104 105 /** Constant for the visible attribute. */ 106 public static final String METADATA_VISIBLE = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":visible"; 107 108 /** Constant for the locked attribute. */ 109 public static final String METADATA_LOCKED = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":locked"; 110 111 /** Constant for the visible attribute. */ 112 public static final String METADATA_INDEXABLE = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":indexable"; 113 114 /** Constant for robots metadata. */ 115 public static final String METADATA_ROBOTS_DISALLOW = "robots-disallow"; 116 117 /** Constant for the attachment node name. */ 118 public static final String ATTACHMENTS_NODE_NAME = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":attachments"; 119 120 /** Constants for sitemap Metadata* */ 121 public static final String METADATA_SITEMAP = "sitemap"; 122 123 /** Constant for publication start date metadata. */ 124 public static final String METADATA_PUBLICATION_START_DATE = "publicationStartDate"; 125 126 /** Constant for publication end date metadata. */ 127 public static final String METADATA_PUBLICATION_END_DATE = "publicationEndDate"; 128 129 /** 130 * Creates a {@link DefaultPage}. 131 * 132 * @param node the node backing this {@link AmetysObject}. 133 * @param parentPath the parent path in the Ametys hierarchy. 134 * @param factory the {@link DefaultPageFactory} which creates the 135 * AmetysObject. 136 */ 137 public DefaultPage(Node node, String parentPath, F factory) 138 { 139 super(node, parentPath, factory); 140 } 141 142 @Override 143 public String getTemplate() throws AmetysRepositoryException 144 { 145 if (getType() != PageType.CONTAINER) 146 { 147 return null; 148 } 149 150 try 151 { 152 Node node = getNode(); 153 154 if (node.hasProperty(METADATA_TEMPLATE)) 155 { 156 return node.getProperty(METADATA_TEMPLATE).getString(); 157 } 158 else 159 { 160 return null; 161 } 162 } 163 catch (RepositoryException e) 164 { 165 throw new AmetysRepositoryException("Unable to get template property", e); 166 } 167 } 168 169 @Override 170 public void setTemplate(String template) throws AmetysRepositoryException 171 { 172 try 173 { 174 getNode().setProperty(METADATA_TEMPLATE, template); 175 } 176 catch (RepositoryException e) 177 { 178 throw new AmetysRepositoryException("Unable to set template property", e); 179 } 180 } 181 182 @Override 183 public Set<String> getTags() throws AmetysRepositoryException 184 { 185 return TaggableAmetysObjectHelper.getTags(this); 186 } 187 188 @Override 189 public void tag(String tag) throws AmetysRepositoryException 190 { 191 TaggableAmetysObjectHelper.tag(this, tag); 192 } 193 194 @Override 195 public void untag(String tag) throws AmetysRepositoryException 196 { 197 TaggableAmetysObjectHelper.untag(this, tag); 198 } 199 200 @Override 201 public String getTitle() throws AmetysRepositoryException 202 { 203 try 204 { 205 return getNode().getProperty(METADATA_TITLE).getString(); 206 } 207 catch (RepositoryException e) 208 { 209 throw new AmetysRepositoryException("Unable to get title property", e); 210 } 211 } 212 213 @Override 214 public void setTitle(String title) throws AmetysRepositoryException 215 { 216 try 217 { 218 getNode().setProperty(METADATA_TITLE, title); 219 } 220 catch (RepositoryException e) 221 { 222 throw new AmetysRepositoryException("Unable to set title property", e); 223 } 224 setInitialTitle(title); 225 } 226 227 /** 228 * Get the title that was set by the user (and not set by a copy adding a number after) 229 * @return The initial title 230 * @throws AmetysRepositoryException if an error occurs 231 */ 232 public String getInitialTitle() throws AmetysRepositoryException 233 { 234 try 235 { 236 if (getNode().hasProperty(METADATA_INITIAL_TITLE)) 237 { 238 return getNode().getProperty(METADATA_INITIAL_TITLE).getString(); 239 } 240 else 241 { 242 return getTitle(); 243 } 244 } 245 catch (RepositoryException e) 246 { 247 throw new AmetysRepositoryException("Unable to get initial title property", e); 248 } 249 } 250 251 /** 252 * Set the title that was set by the user (and not set by a copy adding a number after) 253 * @param initialTitle The initial title to set 254 * @throws AmetysRepositoryException if an error occurs 255 */ 256 public void setInitialTitle(String initialTitle) throws AmetysRepositoryException 257 { 258 try 259 { 260 getNode().setProperty(METADATA_INITIAL_TITLE, initialTitle); 261 } 262 catch (RepositoryException e) 263 { 264 throw new AmetysRepositoryException("Unable to set initial title property", e); 265 } 266 } 267 268 @Override 269 public String getLongTitle() throws AmetysRepositoryException 270 { 271 String longTitle = getValue(METADATA_LONG_TITLE); 272 if (StringUtils.isNotBlank(longTitle)) 273 { 274 return longTitle; 275 } 276 else 277 { 278 return this.getTitle(); 279 } 280 } 281 282 @Override 283 public void setLongTitle(String title) throws AmetysRepositoryException 284 { 285 String newTitle = StringUtils.trimToNull(title); 286 setValue(METADATA_LONG_TITLE, newTitle, ModelItemTypeConstants.STRING_TYPE_ID); 287 } 288 289 @Override 290 public Sitemap getSitemap() throws AmetysRepositoryException 291 { 292 try 293 { 294 Node node = getNode(); 295 296 do 297 { 298 node = node.getParent(); 299 } 300 while (!node.getPrimaryNodeType().getName().equals(Sitemap.NODE_TYPE)); 301 302 return _getFactory().resolveAmetysObject(node); 303 } 304 catch (RepositoryException e) 305 { 306 throw new AmetysRepositoryException("Unable to get sitemap", e); 307 } 308 } 309 310 @Override 311 public int getDepth() throws AmetysRepositoryException 312 { 313 try 314 { 315 int depth = 1; 316 Node node = getNode().getParent(); 317 while (!node.getPrimaryNodeType().getName().equals(Sitemap.NODE_TYPE)) 318 { 319 depth++; 320 node = node.getParent(); 321 } 322 323 return depth; 324 } 325 catch (RepositoryException e) 326 { 327 throw new AmetysRepositoryException("Unable to get depth", e); 328 } 329 } 330 331 @Override 332 public Site getSite() throws AmetysRepositoryException 333 { 334 return getSitemap().getSite(); 335 } 336 337 @Override 338 public String getSiteName() 339 { 340 return getValue(METADATA_SITE); 341 } 342 343 @Override 344 public void setSiteName(String siteName) 345 { 346 setValue(METADATA_SITE, siteName); 347 } 348 349 @Override 350 public String getSitemapName() throws AmetysRepositoryException 351 { 352 return getValue(METADATA_SITEMAP); 353 } 354 355 @Override 356 public void setSitemapName(String sitemapName) throws AmetysRepositoryException 357 { 358 setValue(METADATA_SITEMAP, sitemapName); 359 } 360 361 @Override 362 public String getPathInSitemap() throws AmetysRepositoryException 363 { 364 String nodePath = getPath(); 365 String sitemapPath = getSitemap().getPath(); 366 return nodePath.substring(sitemapPath.length() + 1); 367 } 368 369 @Override 370 public PageType getType() throws AmetysRepositoryException 371 { 372 try 373 { 374 return PageType.valueOf(getNode().getProperty(METADATA_TYPE).getString()); 375 } 376 catch (RepositoryException e) 377 { 378 throw new AmetysRepositoryException("Unable to get type property", e); 379 } 380 } 381 382 @Override 383 public void setType(PageType type) throws AmetysRepositoryException 384 { 385 try 386 { 387 getNode().setProperty(METADATA_TYPE, type.name()); 388 } 389 catch (RepositoryException e) 390 { 391 throw new AmetysRepositoryException("Unable to set type property", e); 392 } 393 } 394 395 @Override 396 public String getURL() throws AmetysRepositoryException 397 { 398 try 399 { 400 return getNode().getProperty(METADATA_URL).getString(); 401 } 402 catch (RepositoryException e) 403 { 404 throw new AmetysRepositoryException("Unable to get url property", e); 405 } 406 } 407 408 @Override 409 public LinkType getURLType() throws AmetysRepositoryException 410 { 411 try 412 { 413 return LinkType.valueOf(getNode().getProperty(METADATA_URLTYPE).getString()); 414 } 415 catch (RepositoryException e) 416 { 417 throw new AmetysRepositoryException("Unable to get url type property", e); 418 } 419 } 420 421 @Override 422 public void setURL(LinkType type, String url) throws AmetysRepositoryException 423 { 424 try 425 { 426 getNode().setProperty(METADATA_URLTYPE, type.toString()); 427 getNode().setProperty(METADATA_URL, url); 428 } 429 catch (RepositoryException e) 430 { 431 throw new AmetysRepositoryException("Unable to set url property", e); 432 } 433 } 434 435 @Override 436 public ResourceCollection getRootAttachments() throws AmetysRepositoryException 437 { 438 ResourceCollection attachments; 439 if (hasChild(ATTACHMENTS_NODE_NAME)) 440 { 441 attachments = getChild(ATTACHMENTS_NODE_NAME); 442 } 443 else 444 { 445 attachments = createChild(ATTACHMENTS_NODE_NAME, JCRResourcesCollectionFactory.RESOURCESCOLLECTION_NODETYPE); 446 447 try 448 { 449 getNode().getSession().save(); 450 } 451 catch (RepositoryException e) 452 { 453 throw new AmetysRepositoryException("Unable to save session", e); 454 } 455 } 456 457 return attachments; 458 } 459 460 @Override 461 public Set<String> getReferers() throws AmetysRepositoryException 462 { 463 try 464 { 465 Node node = getNode(); 466 467 if (!node.hasProperty(METADATA_REFERERS)) 468 { 469 return Collections.emptySet(); 470 } 471 472 Value[] values = getNode().getProperty(METADATA_REFERERS).getValues(); 473 Set<String> ids = new HashSet<>(values.length); 474 475 for (Value value : values) 476 { 477 ids.add(value.getString()); 478 } 479 480 return ids; 481 } 482 catch (RepositoryException e) 483 { 484 throw new AmetysRepositoryException("Unable to get referers property", e); 485 } 486 } 487 488 @Override 489 public void addReferer(String ametysObjectId) throws AmetysRepositoryException 490 { 491 try 492 { 493 Set<String> ids = null; 494 Node node = getNode(); 495 496 if (!node.hasProperty(METADATA_REFERERS)) 497 { 498 ids = Collections.singleton(ametysObjectId); 499 } 500 else 501 { 502 Value[] values = getNode().getProperty(METADATA_REFERERS).getValues(); 503 ids = new HashSet<>(values.length + 1); 504 505 for (Value value : values) 506 { 507 ids.add(value.getString()); 508 } 509 510 ids.add(ametysObjectId); 511 } 512 513 node.setProperty(METADATA_REFERERS, ids.toArray(new String[ids.size()])); 514 } 515 catch (RepositoryException e) 516 { 517 throw new AmetysRepositoryException("Unable to add an id into referers property", e); 518 } 519 } 520 521 @Override 522 public void removeReferer(String ametysObjectId) throws AmetysRepositoryException 523 { 524 try 525 { 526 Node node = getNode(); 527 528 if (node.hasProperty(METADATA_REFERERS)) 529 { 530 Value[] values = getNode().getProperty(METADATA_REFERERS).getValues(); 531 Set<String> ids = new HashSet<>(values.length + 1); 532 533 for (Value value : values) 534 { 535 ids.add(value.getString()); 536 } 537 538 ids.remove(ametysObjectId); 539 node.setProperty(METADATA_REFERERS, ids.toArray(new String[ids.size()])); 540 } 541 } 542 catch (RepositoryException e) 543 { 544 throw new AmetysRepositoryException("Unable to remove an id from referers property", e); 545 } 546 } 547 548 @Override 549 public DefaultPage copyTo(ModifiableTraversableAmetysObject parent, String name, List<String> restrictTo) throws AmetysRepositoryException 550 { 551 try 552 { 553 String pageName = PageDAO.findFreePageName(parent, name != null ? name : getName()); 554 555 DefaultPage cPage = parent.createChild(pageName, "ametys:defaultPage"); 556 cPage.setType(getType()); 557 cPage.setTitle(getTitle()); 558 cPage.setInitialTitle(getInitialTitle()); 559 560 if (PageType.CONTAINER.equals(getType())) 561 { 562 cPage.setTemplate(getTemplate()); 563 } 564 else if (PageType.LINK.equals(getType())) 565 { 566 cPage.setURL(getURLType(), getURL()); 567 } 568 569 // Copy metadata 570 copyTo(cPage); 571 572 // Copy view parameters 573 getTemplateParametersHolder().copyTo(cPage.getTemplateParametersHolder()); 574 575 // Copy zones 576 for (ModifiableZone zone : getZones()) 577 { 578 if (zone instanceof CopiableAmetysObject copiableZone) 579 { 580 copiableZone.copyTo(cPage, (String) null); 581 } 582 } 583 584 // Copy tags 585 Set<String> tags = getTags(); 586 for (String tag : tags) 587 { 588 cPage.tag(tag); 589 } 590 591 // Update sitemap name 592 if (parent instanceof SitemapElement) 593 { 594 cPage.setSitemapName(((SitemapElement) parent).getSitemapName()); 595 } 596 597 parent.saveChanges(); 598 599 // Copy attachments 600 ResourceCollection rootAttachments = getRootAttachments(); 601 if (rootAttachments instanceof SimpleAmetysObject) 602 { 603 Node resourcesNode = ((SimpleAmetysObject) rootAttachments).getNode(); 604 getNode().getSession().getWorkspace().copy(resourcesNode.getPath(), cPage.getNode().getPath() + "/" + resourcesNode.getName()); 605 } 606 607 // Copy sub-pages 608 AmetysObjectIterable< ? extends Page> childrenPages = getChildrenPages(); 609 for (Page page : childrenPages) 610 { 611 // Avoid infinite loop : do not copy the page itself 612 if (page instanceof CopiableAmetysObject && restrictTo.contains(page.getId())) 613 { 614 ((CopiableAmetysObject) page).copyTo(cPage, null, restrictTo); 615 } 616 } 617 618 return cPage; 619 } 620 catch (RepositoryException e) 621 { 622 throw new AmetysRepositoryException(e); 623 } 624 } 625 626 @Override 627 public DefaultPage copyTo(ModifiableTraversableAmetysObject parent, String name) throws AmetysRepositoryException 628 { 629 return copyTo (parent, name, new ArrayList<>()); 630 } 631 632 @Override 633 public void copyTo(ModifiableDataHolder dataHolder) throws UnknownTypeException, NotUniqueTypeException, BadItemTypeException 634 { 635 for (String name : getDataNames()) 636 { 637 if (!_getExcludedDataFromCopy().contains(name)) 638 { 639 DataHolderHelper.copyTo(this, dataHolder, name, DataContext.newInstance()); 640 } 641 } 642 } 643 644 /** 645 * Get the set of data name to exclude from the copy 646 * @return the set of data name to exclude from the copy 647 */ 648 protected Set<String> _getExcludedDataFromCopy() 649 { 650 return Set.of( 651 ViewParametersManager.VIEW_PARAMETERS_COMPOSITE_NAME, 652 METADATA_PUBLICATION_START_DATE, 653 METADATA_PUBLICATION_END_DATE 654 ); 655 } 656 657 @Override 658 public void moveTo(AmetysObject newParent, boolean renameIfExist) throws AmetysRepositoryException, RepositoryIntegrityViolationException 659 { 660 Node node = getNode(); 661 662 try 663 { 664 if (getParent().equals(newParent)) 665 { 666 // Just order current node to the end 667 node.getParent().orderBefore(node.getName(), null); 668 } 669 else 670 { 671 if (!canMoveTo(newParent)) 672 { 673 throw new AmetysRepositoryException("DefaultPage " + toString() + " can only be moved to a CompositePages and "); 674 } 675 SitemapElement newParentPage = (SitemapElement) newParent; 676 677 String sitemapNodePath = getSitemap().getNode().getPath(); 678 String newPath = sitemapNodePath + (newParentPage instanceof Sitemap ? "" : '/' + newParentPage.getPathInSitemap()); 679 680 String name = node.getName(); 681 682 if (renameIfExist) 683 { 684 // Find unused name on new parent node 685 int index = 1; 686 while (node.getSession().getRootNode().hasNode(newPath.substring(1) + "/" + name)) 687 { 688 name = name + "-" + index++; 689 } 690 } 691 692 try 693 { 694 // Move node 695 node.getSession().move(node.getPath(), newPath + "/" + name); 696 } 697 catch (ItemExistsException e) 698 { 699 throw new AmetysRepositoryException(String.format("A page already exists for in parent path '%s'", newPath), e); 700 } 701 702 // recompute name in case it has changed 703 _invalidateName(); 704 705 // Invalidate parent path as the parent path has changed 706 _invalidateParentPath(); 707 } 708 } 709 catch (RepositoryException e) 710 { 711 throw new AmetysRepositoryException(String.format("Unable to move page '%s' to node '%s'", this, newParent.getId()), e); 712 } 713 } 714 715 @Override 716 public boolean canMoveTo(AmetysObject newParent) throws AmetysRepositoryException 717 { 718 return newParent instanceof SitemapElement && ((SitemapElement) newParent).getSitemap().equals(getSitemap()); 719 } 720 721 @Override 722 public void orderBefore(AmetysObject siblingNode) throws AmetysRepositoryException 723 { 724 Node node = getNode(); 725 try 726 { 727 node.getParent().orderBefore(node.getName(), siblingNode != null ? siblingNode.getName() : null); 728 } 729 catch (RepositoryException e) 730 { 731 throw new AmetysRepositoryException(String.format("Unable to order page '%s' before sibling '%s'", this, siblingNode != null ? siblingNode.getName() : ""), e); 732 } 733 } 734 735 @Override 736 public boolean isVisible() throws AmetysRepositoryException 737 { 738 try 739 { 740 if (getNode().hasProperty(METADATA_VISIBLE)) 741 { 742 return getNode().getProperty(METADATA_VISIBLE).getBoolean(); 743 } 744 else 745 { 746 return true; 747 } 748 } 749 catch (RepositoryException e) 750 { 751 throw new AmetysRepositoryException("Unable to get visible property", e); 752 } 753 } 754 755 @Override 756 public void setVisible(boolean isVisible) throws AmetysRepositoryException 757 { 758 try 759 { 760 getNode().setProperty(METADATA_VISIBLE, isVisible); 761 } 762 catch (RepositoryException e) 763 { 764 throw new AmetysRepositoryException("Unable to set visible property", e); 765 } 766 } 767 768 public boolean isIndexable() 769 { 770 try 771 { 772 if (getNode().hasProperty(METADATA_INDEXABLE)) 773 { 774 return getNode().getProperty(METADATA_INDEXABLE).getBoolean(); 775 } 776 else 777 { 778 return true; 779 } 780 } 781 catch (RepositoryException e) 782 { 783 throw new AmetysRepositoryException("Unable to get indexable property", e); 784 } 785 } 786 787 @Override 788 public void setIndexable(boolean indexable) throws AmetysRepositoryException 789 { 790 try 791 { 792 getNode().setProperty(METADATA_INDEXABLE, indexable); 793 } 794 catch (RepositoryException e) 795 { 796 throw new AmetysRepositoryException("Unable to set indexable property", e); 797 } 798 } 799 800 @Override 801 public Map<String, Object> dataToJSON(DataContext context) throws UnknownTypeException, NotUniqueTypeException, BadItemTypeException 802 { 803 Map<String, Object> result = new HashMap<>(); 804 for (String dataName : getDataNames()) 805 { 806 DataContext newContext = context.cloneContext().addSegmentToDataPath(dataName); 807 result.put(dataName, dataToJSON(dataName, newContext)); 808 } 809 810 return result; 811 } 812 813 @Override 814 public Object dataToJSON(String dataPath, DataContext context) throws UnknownTypeException, NotUniqueTypeException, BadItemTypeException 815 { 816 // Do not convert view parameters 817 return !ViewParametersManager.VIEW_PARAMETERS_COMPOSITE_NAME.equals(dataPath) 818 ? super.dataToJSON(dataPath, context) 819 : null; 820 } 821 822 public boolean isLocked() throws AmetysRepositoryException 823 { 824 try 825 { 826 if (getNode().hasProperty(METADATA_LOCKED)) 827 { 828 return getNode().getProperty(METADATA_LOCKED).getBoolean(); 829 } 830 else 831 { 832 return false; 833 } 834 } 835 catch (RepositoryException e) 836 { 837 throw new AmetysRepositoryException("Unable to get locked property", e); 838 } 839 } 840 841 public void lock() throws AmetysRepositoryException 842 { 843 try 844 { 845 getNode().setProperty(METADATA_LOCKED, true); 846 } 847 catch (RepositoryException e) 848 { 849 throw new AmetysRepositoryException("Unable to set locked property", e); 850 } 851 } 852 853 public void unlock() throws AmetysRepositoryException 854 { 855 try 856 { 857 getNode().setProperty(METADATA_LOCKED, false); 858 } 859 catch (RepositoryException e) 860 { 861 throw new AmetysRepositoryException("Unable to set locked property", e); 862 } 863 } 864 865 public TrashElement moveToTrash() 866 { 867 TrashElement trashElement = _getFactory()._getTrashElementDAO().createTrashElement(this, getTitle()); 868 869 // Clone the ametys object from the original session to trash session 870 Node storedNode = NodeHelper.cloneNode(getNode(), trashElement.getNode()); 871 872 try 873 { 874 storedNode.setProperty("ametys-internal:path", getParentPath()); 875 // Update strong reference data to avoid NoSuchElementException 876 DefaultPage<?> page = trashElement.getChild(storedNode.getName()); 877 page._updateContentReferences(this::_saveContentReference); 878 } 879 catch (RepositoryException e) 880 { 881 throw new AmetysRepositoryException("failed to store the content path", e); 882 } 883 884 // Remove the node from the original session 885 remove(); 886 887 return trashElement; 888 } 889 890 public DefaultPage restoreFromTrash(MergeComputationMode mergeComputationMode) throws UnknownParentException 891 { 892 try 893 { 894 Property property = getNode().getProperty("ametys-internal:path"); 895 String parentPath = property.getValue().getString(); 896 JCRTraversableAmetysObject parent; 897 try 898 { 899 parent = _getFactory()._getAOResolver().resolveByPath(parentPath); 900 } 901 catch (UnknownAmetysObjectException e) 902 { 903 throw new UnknownParentException("No parent available at path " + parentPath, e); 904 } 905 906 // remove trash data before restoration 907 property.remove(); 908 909 // Get the node name, can be adjust if another content has already the same node name 910 String nodeName = parent.hasChild(getName()) ? PageDAO.findFreePageNameByTitle(parent, getTitle()) : getName(); 911 // Clone the ametys object from the trash session to default session 912 Node restoredNode = NodeHelper.cloneNode(getNode(), parent.getNode(), nodeName); 913 914 // Update strong reference data to avoid NoSuchElementException 915 DefaultPage<?> restoredPage = parent.getChild(restoredNode.getName()); 916 restoredPage._updateContentReferences(this::_restoreContentReference); 917 918 // Remove the node from the trash session 919 remove(); 920 921 restoredPage.saveChanges(); 922 923 return restoredPage; 924 } 925 catch (RepositoryException e) 926 { 927 throw new AmetysRepositoryException("failed to retrieve the page path", e); 928 } 929 } 930 931 private void _updateContentReferences(Consumer<DefaultZoneItem> updateConsumer) 932 { 933 // Get contents referencing into the pages 934 getZones().stream() 935 .map(Zone::getZoneItems) 936 .flatMap(AmetysObjectIterable::stream) 937 .filter(DefaultZoneItem.class::isInstance) 938 .filter(zi -> zi.getType() == ZoneItem.ZoneType.CONTENT) 939 .map(DefaultZoneItem.class::cast) 940 .forEach(updateConsumer); 941 942 // Explore children pages 943 getChildrenPages().stream() 944 .filter(DefaultPage.class::isInstance) 945 .map(p -> (DefaultPage<?>) p) 946 .forEach(p -> p._updateContentReferences(updateConsumer)); 947 } 948 949 private void _saveContentReference(DefaultZoneItem zoneItem) 950 { 951 try 952 { 953 Node zoneItemNode = zoneItem.getNode(); 954 if (zoneItemNode.hasProperty(DefaultZoneItem.METADATA_CONTENT)) 955 { 956 Property oldProperty = zoneItemNode.getProperty(DefaultZoneItem.METADATA_CONTENT); 957 zoneItemNode.setProperty("ametys-internal:trash-content", oldProperty.getValue(), PropertyType.WEAKREFERENCE); 958 oldProperty.remove(); 959 } 960 else 961 { 962 // If the content property is not there, zone item is obsolete, remove it 963 zoneItem.remove(); 964 } 965 } 966 catch (RepositoryException e) 967 { 968 throw new AmetysRepositoryException(e); 969 } 970 } 971 972 private void _restoreContentReference(DefaultZoneItem zoneItem) 973 { 974 try 975 { 976 Node zoneItemNode = zoneItem.getNode(); 977 Property oldProperty = zoneItemNode.getProperty("ametys-internal:trash-content"); 978 try 979 { 980 // Check if the node is in the target session 981 oldProperty.getNode(); 982 983 // Update the reference 984 zoneItemNode.setProperty(DefaultZoneItem.METADATA_CONTENT, oldProperty.getValue(), PropertyType.REFERENCE); 985 oldProperty.remove(); 986 } 987 catch (ItemNotFoundException e) 988 { 989 // The content is not in the target session, remove the zone item 990 zoneItem.remove(); 991 } 992 } 993 catch (RepositoryException e) 994 { 995 throw new AmetysRepositoryException(e); 996 } 997 } 998}