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